Skip to content

Commit 634951a

Browse files
committed
Add option to stop chain validation when a trusted path is found
This option allows the Merkle Tree CA to validate cross-signed bootstrap chains, where the final cert in the chain is not necessarily trusted. This option is disabled for CT where every cert in the submitted chain needs to be part of the validated path. Also enforce path length constraints in validate_chain_lax. RFC 6962 doesn't mention invalid path length constraints as a reason to reject a certificate chain, but it is required in RFC 9162. Seems fine to reject these chains in CT. For MTC we do want to enforce this check.
1 parent ece8531 commit 634951a

4 files changed

Lines changed: 309 additions & 130 deletions

File tree

crates/mtc_api/src/lib.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use x509_cert::{
4040
time::Validity,
4141
Certificate, TbsCertificate,
4242
};
43-
use x509_util::{validate_chain_lax, CertPool};
43+
use x509_util::{validate_chain_lax, CertPool, ValidationOptions};
4444

4545
// The OID to use for experimentaion. Eventually, we'll switch to "1.3.6.1.5.5.7.TBD1.TBD2"
4646
// as described in <https://www.ietf.org/archive/id/draft-davidben-tls-merkle-tree-certs-05.html#name-log-ids>.
@@ -639,7 +639,17 @@ pub fn validate_correspondence(
639639
// Run the validation logic with the above validation hook. We do
640640
// not give `validate_chain_lax` a window for the `not_after` validity,
641641
// since validity is checked within the validator hook.
642-
validate_chain_lax(raw_chain, roots, None, None, validator_hook).map_err(|e| match e {
642+
validate_chain_lax(
643+
raw_chain,
644+
roots,
645+
&ValidationOptions {
646+
stop_on_first_trusted_cert: true,
647+
not_after_start: None,
648+
not_after_end: None,
649+
},
650+
validator_hook,
651+
)
652+
.map_err(|e| match e {
643653
x509_util::HookOrValidationError::Validation(ve) => ve.into(),
644654
x509_util::HookOrValidationError::Hook(he) => he,
645655
})
@@ -742,7 +752,16 @@ pub fn validate_chain(
742752
// Run the validation and return the hook-constructed pending entry. We do
743753
// not give `validate_chain_lax` a window for the `not_after` validity,
744754
// since validity is checked within the validator hook.
745-
let pending_entry = validate_chain_lax(raw_chain, roots, None, None, validator_hook);
755+
let pending_entry = validate_chain_lax(
756+
raw_chain,
757+
roots,
758+
&ValidationOptions {
759+
stop_on_first_trusted_cert: true,
760+
not_after_start: None,
761+
not_after_end: None,
762+
},
763+
validator_hook,
764+
);
746765
pending_entry.map_err(|e| match e {
747766
x509_util::HookOrValidationError::Validation(ve) => ve.into(),
748767
x509_util::HookOrValidationError::Hook(he) => he,

crates/static_ct_api/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub enum StaticCTError {
3333
UnknownType,
3434
#[error("trailing data")]
3535
TrailingData,
36+
#[error("invalid certificate chain per CT")]
37+
InvalidChain,
3638
#[error("invalid leaf certificate per CT")]
3739
InvalidLeaf,
3840
#[error("CT poison extension is not critical or invalid")]

crates/static_ct_api/src/rfc6962.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use x509_cert::{
4141
},
4242
impl_newtype, Certificate, TbsCertificate,
4343
};
44-
use x509_util::{validate_chain_lax, CertPool};
44+
use x509_util::{validate_chain_lax, CertPool, ValidationOptions};
4545

4646
// Data structures for the [Static CT Submission APIs](https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#submission-apis),
4747
// a subset of the APIs from [RFC 6962](https://datatracker.ietf.org/doc/html/rfc6962).
@@ -164,8 +164,11 @@ pub fn partially_validate_chain(
164164
let pending_entry = validate_chain_lax(
165165
raw_chain,
166166
roots,
167-
not_after_start,
168-
not_after_end,
167+
&ValidationOptions {
168+
stop_on_first_trusted_cert: false,
169+
not_after_start,
170+
not_after_end,
171+
},
169172
validator_hook,
170173
);
171174
pending_entry.map_err(|e| match e {
@@ -352,14 +355,8 @@ mod tests {
352355
($name:ident; $($root_file:expr),+; $($chain_file:expr),+; $not_after_start:expr; $not_after_end:expr; $expect_precert:expr; $require_server_auth_eku:expr; $want_err:expr; $want_chain_len:expr) => {
353356
#[test]
354357
fn $name() {
355-
let mut roots = Vec::new();
356-
$(
357-
roots.append(&mut Certificate::load_pem_chain(include_bytes!($root_file)).unwrap());
358-
)*
359-
let mut chain = Vec::new();
360-
$(
361-
chain.append(&mut Certificate::load_pem_chain(include_bytes!($chain_file)).unwrap());
362-
)*
358+
let roots = x509_util::build_chain!($($root_file),*);
359+
let chain = x509_util::build_chain!($($chain_file),*);
363360

364361
let result = partially_validate_chain(
365362
&x509_util::certs_to_bytes(&chain).unwrap(),
@@ -400,6 +397,11 @@ mod tests {
400397

401398
test_validate_chain!(leaf_as_accepted_root; "../tests/leaf-signed-by-fake-intermediate-cert.pem"; "../tests/leaf-signed-by-fake-intermediate-cert.pem"; None; None; false; true; false; 0);
402399

400+
test_validate_chain!(valid_chain_inc_root; "../../static_ct_api/tests/fake-ca-cert.pem"; "../tests/leaf-signed-by-fake-intermediate-cert.pem", "../tests/fake-intermediate-cert.pem", "../tests/fake-ca-cert.pem"; None; None; false; true; false; 2);
401+
402+
// CT does not allow extra certs at the end of the chain.
403+
test_validate_chain!(unrelated_cert_after_chain_inc_root; "../../static_ct_api/tests/fake-ca-cert.pem"; "../tests/leaf-signed-by-fake-intermediate-cert.pem", "../tests/fake-intermediate-cert.pem", "../tests/fake-ca-cert.pem", "../tests/test-cert.pem"; None; None; false; true; true; 0);
404+
403405
#[test]
404406
fn test_build_precert_tbs() {
405407
let precert_chain =

0 commit comments

Comments
 (0)