Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions x509-cert/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ pub enum Error {

/// Not all required elements were specified
MissingAttributes,

/// More than one extension with the same OID (RFC 5280 4.2: "A certificate MUST NOT include
/// more than one instance of a particular extension").
DuplicateExtension {
/// Offending [`ObjectIdentifier`]
oid: ObjectIdentifier,
},
}

impl core::error::Error for Error {}
Expand All @@ -92,6 +99,9 @@ impl fmt::Display for Error {
"Non-ordered attribute or invalid attribute found (oid={oid})"
),
Error::MissingAttributes => write!(f, "Not all required elements were specified"),
Error::DuplicateExtension { oid } => {
write!(f, "more than one extension with OID {oid}")
}
}
}
}
Expand Down Expand Up @@ -381,6 +391,18 @@ where

self.extensions.append(&mut default_extensions);

// RFC 5280 4.2: a certificate MUST NOT include more than one instance of a particular
// extension. This also catches an extension added with `add_extension` that the profile
// adds as well.
for (i, ext) in self.extensions.iter().enumerate() {
if self.extensions[..i]
.iter()
.any(|e| e.extn_id == ext.extn_id)
{
return Err(Error::DuplicateExtension { oid: ext.extn_id });
}
}

if !self.extensions.is_empty() {
self.tbs.extensions = Some(self.extensions.clone());
}
Expand Down
33 changes: 33 additions & 0 deletions x509-cert/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,36 @@ async fn async_builder() {
let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
println!("{}", openssl::check_certificate(pem.as_bytes()));
}

/// RFC 5280 4.2: "A certificate MUST NOT include more than one instance of a particular
/// extension." An extension added by the user that the profile also adds is an error.
#[test]
fn reject_duplicate_extension() {
use x509_cert::{builder::Error, ext::pkix::BasicConstraints};

let subject = Name::from_str("CN=root,O=World domination Inc,C=US").unwrap();
let profile = profile::cabf::Root::new(false, subject).expect("create root profile");
let pub_key = SubjectPublicKeyInfo::try_from(PKCS8_PUBLIC_KEY_DER).expect("get ecdsa pub key");
let mut builder = CertificateBuilder::new(
profile,
SerialNumber::from(42u32),
Validity::from_now(Duration::new(5, 0)).unwrap(),
pub_key,
)
.expect("Create certificate");

// The Root profile adds BasicConstraints too.
builder
.add_extension(&BasicConstraints {
ca: true,
path_len_constraint: Some(1),
})
.unwrap();

let err = builder
.build::<_, DerSignature>(&ecdsa_signer())
.unwrap_err();
assert!(
matches!(err, Error::DuplicateExtension { oid } if oid == const_oid::db::rfc5280::ID_CE_BASIC_CONSTRAINTS)
);
}
Loading