Skip to content

Commit d1cac63

Browse files
authored
x509-cert: rework the profile of builder (#1306)
This is now providing a trait to be implemented by the consumer. A number of implementation are available, including ones trying to abide by CABF Baseline Requirements. Fixes #1281
1 parent 16fb786 commit d1cac63

File tree

8 files changed

+958
-236
lines changed

8 files changed

+958
-236
lines changed

x509-cert/src/builder.rs

Lines changed: 45 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@ use der::{asn1::BitString, referenced::OwnedToRef, Encode};
77
use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer};
88
use spki::{
99
AlgorithmIdentifier, DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier,
10-
SignatureBitStringEncoding, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
10+
SignatureBitStringEncoding, SubjectPublicKeyInfoOwned,
1111
};
1212

1313
use crate::{
1414
certificate::{Certificate, TbsCertificate, Version},
15-
ext::{
16-
pkix::{
17-
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
18-
},
19-
AsExtension, Extension, Extensions,
20-
},
15+
ext::{AsExtension, Extensions},
2116
name::Name,
2217
request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
2318
serial_number::SerialNumber,
2419
time::Validity,
2520
};
2621

22+
pub mod profile;
23+
24+
use self::profile::Profile;
25+
2726
const NULL_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("0.0.0");
2827

2928
/// Error type
@@ -38,6 +37,23 @@ pub enum Error {
3837

3938
/// Signing error propagated for the [`signature::Error`] type.
4039
Signature(signature::Error),
40+
41+
/// Each RelativeDistinguishedName MUST contain exactly one AttributeTypeAndValue.
42+
NonUniqueRdn,
43+
44+
/// Each Name MUST NOT contain more than one instance of a given
45+
/// AttributeTypeAndValue across all RelativeDistinguishedNames unless explicitly
46+
/// allowed in these Requirements
47+
NonUniqueATV,
48+
49+
/// Non-ordered attribute or invalid attribute
50+
InvalidAttribute {
51+
/// Offending [`ObjectIdentifier`]
52+
oid: ObjectIdentifier,
53+
},
54+
55+
/// Not all required elements were specified
56+
MissingAttributes,
4157
}
4258

4359
#[cfg(feature = "std")]
@@ -49,6 +65,13 @@ impl fmt::Display for Error {
4965
Error::Asn1(err) => write!(f, "ASN.1 error: {}", err),
5066
Error::PublicKey(err) => write!(f, "public key error: {}", err),
5167
Error::Signature(err) => write!(f, "signature error: {}", err),
68+
Error::NonUniqueRdn => write!(
69+
f,
70+
"Each RelativeDistinguishedName MUST contain exactly one AttributeTypeAndValue."
71+
),
72+
Error::NonUniqueATV => write!(f, "Each Name MUST NOT contain more than one instance of a given AttributeTypeAndValue"),
73+
Error::InvalidAttribute{oid} => write!(f, "Non-ordered attribute or invalid attribute found (oid={oid})"),
74+
Error::MissingAttributes => write!(f, "Not all required elements were specified"),
5275
}
5376
}
5477
}
@@ -74,156 +97,12 @@ impl From<signature::Error> for Error {
7497
/// Result type
7598
pub type Result<T> = core::result::Result<T, Error>;
7699

77-
/// The type of certificate to build
78-
#[derive(Clone, Debug, Eq, PartialEq)]
79-
pub enum Profile {
80-
/// Build a root CA certificate
81-
Root,
82-
/// Build an intermediate sub CA certificate
83-
SubCA {
84-
/// issuer Name,
85-
/// represents the name signing the certificate
86-
issuer: Name,
87-
/// pathLenConstraint INTEGER (0..MAX) OPTIONAL
88-
/// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
89-
path_len_constraint: Option<u8>,
90-
},
91-
/// Build an end certificate
92-
Leaf {
93-
/// issuer Name,
94-
/// represents the name signing the certificate
95-
issuer: Name,
96-
/// should the key agreement flag of KeyUsage be enabled
97-
enable_key_agreement: bool,
98-
/// should the key encipherment flag of KeyUsage be enabled
99-
enable_key_encipherment: bool,
100-
/// should the subject key identifier extension be included
101-
///
102-
/// From [RFC 5280 Section 4.2.1.2]:
103-
/// For end entity certificates, subject key identifiers SHOULD be
104-
/// derived from the public key. Two common methods for generating key
105-
/// identifiers from the public key are identified above.
106-
#[cfg(feature = "hazmat")]
107-
include_subject_key_identifier: bool,
108-
},
109-
#[cfg(feature = "hazmat")]
110-
/// Opt-out of the default extensions
111-
Manual {
112-
/// issuer Name,
113-
/// represents the name signing the certificate
114-
/// A `None` will make it a self-signed certificate
115-
issuer: Option<Name>,
116-
},
117-
}
118-
119-
impl Profile {
120-
fn get_issuer(&self, subject: &Name) -> Name {
121-
match self {
122-
Profile::Root => subject.clone(),
123-
Profile::SubCA { issuer, .. } => issuer.clone(),
124-
Profile::Leaf { issuer, .. } => issuer.clone(),
125-
#[cfg(feature = "hazmat")]
126-
Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(),
127-
}
128-
}
129-
130-
fn build_extensions(
131-
&self,
132-
spk: SubjectPublicKeyInfoRef<'_>,
133-
issuer_spk: SubjectPublicKeyInfoRef<'_>,
134-
tbs: &TbsCertificate,
135-
) -> Result<vec::Vec<Extension>> {
136-
#[cfg(feature = "hazmat")]
137-
// User opted out of default extensions set.
138-
if let Profile::Manual { .. } = self {
139-
return Ok(vec::Vec::default());
140-
}
141-
142-
let mut extensions: vec::Vec<Extension> = vec::Vec::new();
143-
144-
match self {
145-
#[cfg(feature = "hazmat")]
146-
Profile::Leaf {
147-
include_subject_key_identifier: false,
148-
..
149-
} => {}
150-
_ => extensions.push(
151-
SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?,
152-
),
153-
}
154-
155-
// Build Authority Key Identifier
156-
match self {
157-
Profile::Root => {}
158-
_ => {
159-
extensions.push(
160-
AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
161-
.to_extension(&tbs.subject, &extensions)?,
162-
);
163-
}
164-
}
165-
166-
// Build Basic Contraints extensions
167-
extensions.push(match self {
168-
Profile::Root => BasicConstraints {
169-
ca: true,
170-
path_len_constraint: None,
171-
}
172-
.to_extension(&tbs.subject, &extensions)?,
173-
Profile::SubCA {
174-
path_len_constraint,
175-
..
176-
} => BasicConstraints {
177-
ca: true,
178-
path_len_constraint: *path_len_constraint,
179-
}
180-
.to_extension(&tbs.subject, &extensions)?,
181-
Profile::Leaf { .. } => BasicConstraints {
182-
ca: false,
183-
path_len_constraint: None,
184-
}
185-
.to_extension(&tbs.subject, &extensions)?,
186-
#[cfg(feature = "hazmat")]
187-
Profile::Manual { .. } => unreachable!(),
188-
});
189-
190-
// Build Key Usage extension
191-
match self {
192-
Profile::Root | Profile::SubCA { .. } => {
193-
extensions.push(
194-
KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign)
195-
.to_extension(&tbs.subject, &extensions)?,
196-
);
197-
}
198-
Profile::Leaf {
199-
enable_key_agreement,
200-
enable_key_encipherment,
201-
..
202-
} => {
203-
let mut key_usage = KeyUsages::DigitalSignature | KeyUsages::NonRepudiation;
204-
if *enable_key_encipherment {
205-
key_usage |= KeyUsages::KeyEncipherment;
206-
}
207-
if *enable_key_agreement {
208-
key_usage |= KeyUsages::KeyAgreement;
209-
}
210-
211-
extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
212-
}
213-
#[cfg(feature = "hazmat")]
214-
Profile::Manual { .. } => unreachable!(),
215-
}
216-
217-
Ok(extensions)
218-
}
219-
}
220-
221100
/// X509 Certificate builder
222101
///
223102
/// ```
224103
/// use der::Decode;
225104
/// use x509_cert::spki::SubjectPublicKeyInfoOwned;
226-
/// use x509_cert::builder::{CertificateBuilder, Profile, Builder};
105+
/// use x509_cert::builder::{CertificateBuilder, Builder, profile};
227106
/// use x509_cert::name::Name;
228107
/// use x509_cert::serial_number::SerialNumber;
229108
/// use x509_cert::time::Validity;
@@ -237,14 +116,14 @@ impl Profile {
237116
/// # use der::referenced::RefToOwned;
238117
/// # fn rsa_signer() -> SigningKey<Sha256> {
239118
/// # let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap();
240-
/// # let signing_key = SigningKey::<Sha256>::new_with_prefix(private_key);
119+
/// # let signing_key = SigningKey::<Sha256>::new(private_key);
241120
/// # signing_key
242121
/// # }
243122
///
244123
/// let serial_number = SerialNumber::from(42u32);
245124
/// let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
246-
/// let profile = Profile::Root;
247125
/// let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
126+
/// let profile = profile::cabf::Root::new(false,subject).expect("Create root profile");
248127
///
249128
/// let pub_key = SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key");
250129
///
@@ -253,33 +132,35 @@ impl Profile {
253132
/// profile,
254133
/// serial_number,
255134
/// validity,
256-
/// subject,
257135
/// pub_key,
258136
/// )
259137
/// .expect("Create certificate builder");
260138
///
261139
/// let cert = builder.build(&signer).expect("Create certificate");
262140
/// ```
263-
pub struct CertificateBuilder {
141+
pub struct CertificateBuilder<P> {
264142
tbs: TbsCertificate,
265143
extensions: Extensions,
266-
profile: Profile,
144+
profile: P,
267145
}
268146

269-
impl CertificateBuilder {
147+
impl<P> CertificateBuilder<P>
148+
where
149+
P: Profile,
150+
{
270151
/// Creates a new certificate builder
271152
pub fn new(
272-
profile: Profile,
153+
profile: P,
273154
serial_number: SerialNumber,
274155
mut validity: Validity,
275-
subject: Name,
276156
subject_public_key_info: SubjectPublicKeyInfoOwned,
277157
) -> Result<Self> {
278158
let signature_alg = AlgorithmIdentifier {
279159
oid: NULL_OID,
280160
parameters: None,
281161
};
282162

163+
let subject = profile.get_subject();
283164
let issuer = profile.get_issuer(&subject);
284165

285166
validity.not_before.rfc5280_adjust_utc_time()?;
@@ -455,7 +336,10 @@ pub trait Builder: Sized {
455336
}
456337
}
457338

458-
impl Builder for CertificateBuilder {
339+
impl<P> Builder for CertificateBuilder<P>
340+
where
341+
P: Profile,
342+
{
459343
type Output = Certificate;
460344

461345
fn finalize<S>(&mut self, cert_signer: &S) -> Result<vec::Vec<u8>>

x509-cert/src/builder/profile.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! Certificate profiles
2+
//!
3+
//! Profiles need implement by the [`Profile`] trait.
4+
//! They may then be consumed by a [`builder::CertificateBuilder`].
5+
//!
6+
//!
7+
//! Multiple profiles are provided and you may select one depending on your use-case:
8+
//! - [`cabf`] implements the Baseline Requirement from the CA Browser Forum as close as it can be
9+
//! done.
10+
//! - [`devid`] implements the specification for IEEE 802.1 AR. Certificates for Secure
11+
//! Device Identity.
12+
//!
13+
//! Please follow each sub-module documentation and select a profile that may suit your needs, or
14+
//! you may implement your own profile, if need be.
15+
16+
#[cfg(doc)]
17+
use crate::builder;
18+
19+
use crate::{builder::Result, certificate::TbsCertificate, ext::Extension, name::Name};
20+
use alloc::vec;
21+
use spki::SubjectPublicKeyInfoRef;
22+
23+
pub mod cabf;
24+
pub mod devid;
25+
26+
/// Profile for certificates
27+
pub trait Profile {
28+
/// Issuer to be used for issued certificates
29+
fn get_issuer(&self, subject: &Name) -> Name;
30+
31+
/// Subject for the certificate to be used.
32+
fn get_subject(&self) -> Name;
33+
34+
/// X509v3 extensions to be added in the certificates.
35+
fn build_extensions(
36+
&self,
37+
spk: SubjectPublicKeyInfoRef<'_>,
38+
issuer_spk: SubjectPublicKeyInfoRef<'_>,
39+
tbs: &TbsCertificate,
40+
) -> Result<vec::Vec<Extension>>;
41+
}

0 commit comments

Comments
 (0)