From b5b6d041e368cdf2d25d9e4ecc8773d7936117fc Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Mon, 23 Feb 2026 09:20:13 +0800 Subject: [PATCH] fix(validate): reject unsupported critical extensions per RFC 5280 TbsCertificateStructureValidator now reports an error (and returns false) when a certificate contains a critical extension that the parser does not recognize, as required by RFC 5280 Section 4.2. Non-critical unsupported extensions continue to produce a warning only. Add test certificates and unit tests covering both cases. --- assets/unsupported_critical_ext.der | Bin 0 -> 794 bytes assets/unsupported_noncritical_ext.der | Bin 0 -> 791 bytes src/validate/structure.rs | 88 ++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 assets/unsupported_critical_ext.der create mode 100644 assets/unsupported_noncritical_ext.der diff --git a/assets/unsupported_critical_ext.der b/assets/unsupported_critical_ext.der new file mode 100644 index 0000000000000000000000000000000000000000..5ae76ea13bd8aedfe8aaa39ef51d69daff6bb03f GIT binary patch literal 794 zcmXqLViq%KV*Iy&nTe5!NhG(Vb?4=uGjHXjn(cYrc<_ANMETzaylk9WZ60mkc^MhG zSs4uY4S5Z?*_cCFn0Z)2Qj1Fr1JesO{Pwf5eDg7%}?AV%e zM}${l+a>e0i*<81>McCR!}N){NsiCwJauG(J<7 zlfNT)_4<%|hnWIcPc?0sz4IF12U~-xrE@p`_*Js@*R`mx_w8=8zf9YA(|2bN%g1+Z zlUCoF{&88`|Kk6hJ=%Ykloe*qiJkgo%H-vgA4;%U^vV2v52vV-29VOC4ERI zym`ad`ai;-FI+pp&}kqKl2&GsFc53Nu7Dq;K$wy7KMSh?GmtV6W#iCdW?^Mx=img1 zvhlE#fT9pJih;4g$ROO+R@3|MZG%Gc^#a}rtFM{gWJvycC~(o=ud_9$O%rF{{q|v2 zyQIf`@sATj*G=qrzfw*?=hKdLlU`WO4l_Tr?sbjf-+I%SJ6=n6nK_)B``Bb*-4VX6 zu4e+jPLuy+{b^PCPU$_n<{kZ#Fr)0uiaieR^(P1QXH0qTf9=Jc+e zi?5jrc#k<+d`YU$nH5X6LxF0svG-RQvz{ literal 0 HcmV?d00001 diff --git a/assets/unsupported_noncritical_ext.der b/assets/unsupported_noncritical_ext.der new file mode 100644 index 0000000000000000000000000000000000000000..9d43226b590157696f1cbf71b5b3713f5ff9f206 GIT binary patch literal 791 zcmXqLViq=NV*I^;nTe5!Nu*SH&V?+e4Gbmne7|0ukU66uwt(A!myJ`a&7QA+G^98*?ZNGY?BhYH^8yoH(zMnSqg!v4NqXxv@bMkZX>_#njNmsDx}NBP#=Q z6C*zZ&>dV%O^l2TcXaB%Fh{hn5P6bXBA77cKthR>#nUOmnv-W-cWZobV1;Y7%i6RT zON5MPr=>RfSV^5-P^1-ioa3Lc_O2T;>1-YHJ;A+p?4C^bUrj#Y_idNcz3JsO%2&Qv z|60x9Wvja>qE-0edBI;+lCxJn-e7X(WICs%l*6?d7eenJPuiX4`^EopQ|B71LwtW9 z>dxP)evC(Fk?M{3or~J9t$C~x6kL(k{rJTGXAY@0>fFBk420{e#Y1YUhot4E$CJKDan-%Qwjvyf5aZ%-%fXj`D)* z_0k!O<=v;&^?bS|5EgF9aM&^3S-w1V{soiO&Fd?RMFR~!ELuEo$(bGRFK0U@%IP0{6b)6qAzKL$+OiB9C_F?Jo>BX}XCQ3Z6wg}v=`Z#I& znXE^a1sAWqloOl3BC^wd<7KJ#MHiOn=_?-IxanBdzoRWCOOI`~tT(Z~|Bva$wi|YT ze=Vr!*m=)ZT7dWQ_bnE0S1>+(_3AK>X-T-G-{nnAQ>InM3z`VB{!)DjLP|(#4af4*+zXLEQiV literal 0 HcmV?d00001 diff --git a/src/validate/structure.rs b/src/validate/structure.rs index 1b3a358..871d2db 100644 --- a/src/validate/structure.rs +++ b/src/validate/structure.rs @@ -124,9 +124,16 @@ impl<'a> Validator<'a> for TbsCertificateStructureValidator { res = false; } // check for parse errors or unsupported extensions + // RFC 5280 4.2: "if a certificate contains a critical extension that + // is not recognized, it MUST be rejected" for ext in item.extensions() { if let ParsedExtension::UnsupportedExtension { .. } = &ext.parsed_extension { - l.warn(&format!("Unsupported extension {}", ext.oid)); + if ext.critical { + l.err(&format!("Unsupported critical extension {}", ext.oid)); + res = false; + } else { + l.warn(&format!("Unsupported extension {}", ext.oid)); + } } if let ParsedExtension::ParseError { error } = &ext.parsed_extension { l.err(&format!("Parse error in extension {}: {}", ext.oid, error)); @@ -185,3 +192,82 @@ impl<'a> Validator<'a> for X509PublicKeyValidator { res } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + + #[test] + fn validate_unsupported_noncritical_extension_warns() { + let der = include_bytes!("../../assets/unsupported_noncritical_ext.der"); + let (_, cert) = X509Certificate::from_der(der).expect("could not parse certificate"); + let mut logger = VecLogger::default(); + let res = TbsCertificateStructureValidator.validate(&cert.tbs_certificate, &mut logger); + + // Unsupported non-critical extension should produce a warning but not fail + assert!( + res, + "validator should return true for non-critical unsupported extension" + ); + assert!( + logger + .warnings() + .iter() + .any(|w| w.contains("Unsupported extension")), + "expected warning about unsupported extension, got: {:?}", + logger.warnings() + ); + assert!( + !logger + .errors() + .iter() + .any(|e| e.contains("Unsupported critical extension")), + "should not have critical extension error for non-critical extension" + ); + } + + #[test] + fn validate_unsupported_critical_extension_errors() { + let der = include_bytes!("../../assets/unsupported_critical_ext.der"); + let (_, cert) = X509Certificate::from_der(der).expect("could not parse certificate"); + let mut logger = VecLogger::default(); + let res = TbsCertificateStructureValidator.validate(&cert.tbs_certificate, &mut logger); + + // Unsupported critical extension must cause validation failure per RFC 5280 4.2 + assert!( + !res, + "validator should return false for critical unsupported extension" + ); + assert!( + logger + .errors() + .iter() + .any(|e| e.contains("Unsupported critical extension")), + "expected error about unsupported critical extension, got: {:?}", + logger.errors() + ); + } + + #[test] + fn validate_known_good_certificate() { + let der = include_bytes!("../../assets/IGC_A.der"); + let (_, cert) = X509Certificate::from_der(der).expect("could not parse certificate"); + let mut logger = VecLogger::default(); + let res = X509StructureValidator.validate(&cert, &mut logger); + + // Known good certificate should pass validation + assert!( + res, + "IGC_A.der should pass validation, errors: {:?}", + logger.errors() + ); + assert!( + !logger + .errors() + .iter() + .any(|e| e.contains("Unsupported critical extension")), + "known good cert should not have unsupported critical extension errors" + ); + } +}