Skip to content

Commit b2b6b6d

Browse files
committed
rework CA builder profiles
1 parent fc19e66 commit b2b6b6d

File tree

4 files changed

+407
-178
lines changed

4 files changed

+407
-178
lines changed

x509-cert/src/builder.rs

Lines changed: 11 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! X509 Certificate builder
22
33
use alloc::vec;
4-
use core::fmt;
4+
use core::{fmt, marker::PhantomData};
55
use der::{asn1::BitString, referenced::OwnedToRef, Encode};
66
use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer};
77
use spki::{
@@ -23,6 +23,10 @@ use crate::{
2323
time::Validity,
2424
};
2525

26+
pub mod profile;
27+
28+
use self::profile::Profile;
29+
2630
/// Error type
2731
#[derive(Debug)]
2832
#[non_exhaustive]
@@ -70,180 +74,6 @@ impl From<signature::Error> for Error {
7074

7175
type Result<T> = core::result::Result<T, Error>;
7276

73-
/// The type of certificate to build
74-
#[derive(Clone, Debug, Eq, PartialEq)]
75-
pub enum Profile {
76-
/// Build a root CA certificate
77-
Root,
78-
/// Build an intermediate sub CA certificate
79-
SubCA {
80-
/// issuer Name,
81-
/// represents the name signing the certificate
82-
issuer: Name,
83-
/// pathLenConstraint INTEGER (0..MAX) OPTIONAL
84-
/// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
85-
path_len_constraint: Option<u8>,
86-
},
87-
/// Build an end certificate
88-
Leaf {
89-
/// issuer Name,
90-
/// represents the name signing the certificate
91-
issuer: Name,
92-
/// Usage of the leaf certificate
93-
usage: Usage,
94-
/// should the subject key identifier extension be included
95-
///
96-
/// From [RFC 5280 Section 4.2.1.2]:
97-
/// For end entity certificates, subject key identifiers SHOULD be
98-
/// derived from the public key. Two common methods for generating key
99-
/// identifiers from the public key are identified above.
100-
#[cfg(feature = "hazmat")]
101-
include_subject_key_identifier: bool,
102-
},
103-
#[cfg(feature = "hazmat")]
104-
/// Opt-out of the default extensions
105-
Manual {
106-
/// issuer Name,
107-
/// represents the name signing the certificate
108-
/// A `None` will make it a self-signed certificate
109-
issuer: Option<Name>,
110-
},
111-
}
112-
113-
impl Profile {
114-
fn get_issuer(&self, subject: &Name) -> Name {
115-
match self {
116-
Profile::Root => subject.clone(),
117-
Profile::SubCA { issuer, .. } => issuer.clone(),
118-
Profile::Leaf { issuer, .. } => issuer.clone(),
119-
#[cfg(feature = "hazmat")]
120-
Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(),
121-
}
122-
}
123-
124-
fn build_extensions(
125-
&self,
126-
spk: SubjectPublicKeyInfoRef<'_>,
127-
issuer_spk: SubjectPublicKeyInfoRef<'_>,
128-
tbs: &TbsCertificate,
129-
) -> Result<vec::Vec<Extension>> {
130-
#[cfg(feature = "hazmat")]
131-
// User opted out of default extensions set.
132-
if let Profile::Manual { .. } = self {
133-
return Ok(vec::Vec::default());
134-
}
135-
136-
let mut extensions: vec::Vec<Extension> = vec::Vec::new();
137-
138-
match self {
139-
#[cfg(feature = "hazmat")]
140-
Profile::Leaf {
141-
include_subject_key_identifier: false,
142-
..
143-
} => {}
144-
_ => extensions.push(
145-
SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?,
146-
),
147-
}
148-
149-
// Build Authority Key Identifier
150-
match self {
151-
Profile::Root => {}
152-
_ => {
153-
extensions.push(
154-
AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
155-
.to_extension(&tbs.subject, &extensions)?,
156-
);
157-
}
158-
}
159-
160-
// Build Basic Contraints extensions
161-
extensions.push(match self {
162-
Profile::Root => BasicConstraints {
163-
ca: true,
164-
path_len_constraint: None,
165-
}
166-
.to_extension(&tbs.subject, &extensions)?,
167-
Profile::SubCA {
168-
path_len_constraint,
169-
..
170-
} => BasicConstraints {
171-
ca: true,
172-
path_len_constraint: *path_len_constraint,
173-
}
174-
.to_extension(&tbs.subject, &extensions)?,
175-
Profile::Leaf { .. } => BasicConstraints {
176-
ca: false,
177-
path_len_constraint: None,
178-
}
179-
.to_extension(&tbs.subject, &extensions)?,
180-
#[cfg(feature = "hazmat")]
181-
Profile::Manual { .. } => unreachable!(),
182-
});
183-
184-
// Build Key Usage extension
185-
match self {
186-
Profile::Root | Profile::SubCA { .. } => {
187-
extensions.push(
188-
KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign)
189-
.to_extension(&tbs.subject, &extensions)?,
190-
);
191-
}
192-
Profile::Leaf { usage, .. } => {
193-
let key_usage = usage.key_usage().into();
194-
195-
extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
196-
}
197-
#[cfg(feature = "hazmat")]
198-
Profile::Manual { .. } => unreachable!(),
199-
}
200-
201-
Ok(extensions)
202-
}
203-
}
204-
205-
/// [`Usage`] describes the usage of a Leaf certificate.
206-
///
207-
/// This is designed in accordance with [ETSI EN 319 412-2 § 4.3.2 Key usage].
208-
///
209-
/// The various fields will refer to [RFC 5280 § 4.2.1.3 Key Usage] definitions.
210-
///
211-
/// [RFC 5280 § 4.2.1.3 Key Usage]: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
212-
/// [ETSI EN 319 412-2 § 4.3.2 Key usage]: https://www.etsi.org/deliver/etsi_en/319400_319499/31941202/02.03.01_60/en_31941202v020301p.pdf#page=11
213-
#[derive(Clone, Debug, PartialEq, Eq)]
214-
pub enum Usage {
215-
/// [`Usage::NonRepudiation`] will set the NonRepudiation (also known as
216-
/// contentCommitment) bit of KeyUsage.
217-
NonRepudiation,
218-
/// [`Usage::DigitalSignature`] will set the digitalSignature bit.
219-
///
220-
/// This is meant to be used in an entity authentication service, a data
221-
/// origin authentication service, and/or an integrity service.
222-
DigitalSignature,
223-
/// [`Usage::KeyAgreement`] will set the `keyAgreement` bit.
224-
///
225-
/// This is meant to be used on Certificates when a Diffie-Hellman key is
226-
/// to be used for key management.
227-
KeyAgreement,
228-
/// [`Usage::KeyEncipherment`] will set the `keyEncipherment` bit.
229-
///
230-
/// This is meant to be used on Certificates when an RSA public
231-
/// key is to be used for encrypting a symmetric content-decryption
232-
/// key or an asymmetric private key.
233-
KeyEncipherment,
234-
}
235-
236-
impl Usage {
237-
fn key_usage(&self) -> KeyUsages {
238-
match self {
239-
Self::NonRepudiation => KeyUsages::NonRepudiation,
240-
Self::DigitalSignature => KeyUsages::DigitalSignature,
241-
Self::KeyAgreement => KeyUsages::KeyAgreement,
242-
Self::KeyEncipherment => KeyUsages::KeyEncipherment,
243-
}
244-
}
245-
}
246-
24777
/// X509 Certificate builder
24878
///
24979
/// ```
@@ -297,14 +127,17 @@ where
297127
S::VerifyingKey: EncodePublicKey,
298128
{
299129
/// Creates a new certificate builder
300-
pub fn new(
301-
profile: Profile,
130+
pub fn new<P>(
131+
profile: P,
302132
serial_number: SerialNumber,
303133
mut validity: Validity,
304134
subject: Name,
305135
subject_public_key_info: SubjectPublicKeyInfoOwned,
306136
cert_signer: &'s S,
307-
) -> Result<Self> {
137+
) -> Result<Self>
138+
where
139+
P: Profile,
140+
{
308141
let verifying_key = cert_signer.verifying_key();
309142
let signer_pub = SubjectPublicKeyInfoOwned::from_key(verifying_key)?;
310143

x509-cert/src/builder/profile.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use alloc::vec;
2+
use spki::{
3+
DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
4+
SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
5+
};
6+
7+
use crate::{
8+
builder::Result,
9+
certificate::{Certificate, TbsCertificate, Version},
10+
ext::{
11+
pkix::{
12+
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
13+
},
14+
AsExtension, Extension, Extensions,
15+
},
16+
name::Name,
17+
request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
18+
serial_number::SerialNumber,
19+
time::Validity,
20+
};
21+
22+
pub mod cabf;
23+
pub mod piv;
24+
25+
pub(crate) mod sealed {
26+
use alloc::vec;
27+
use spki::{
28+
DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
29+
SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
30+
};
31+
32+
use crate::{
33+
builder::Result,
34+
certificate::{Certificate, TbsCertificate, Version},
35+
ext::{
36+
pkix::{
37+
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
38+
},
39+
AsExtension, Extension, Extensions,
40+
},
41+
name::Name,
42+
request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
43+
serial_number::SerialNumber,
44+
time::Validity,
45+
};
46+
47+
pub trait Profile {
48+
fn get_issuer(&self, subject: &Name) -> Name;
49+
50+
fn build_extensions(
51+
&self,
52+
spk: SubjectPublicKeyInfoRef<'_>,
53+
issuer_spk: SubjectPublicKeyInfoRef<'_>,
54+
tbs: &TbsCertificate,
55+
) -> Result<vec::Vec<Extension>>;
56+
}
57+
}
58+
59+
#[cfg(feature = "hazmat")]
60+
pub use sealed::Profile;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//! https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-v2.0.1.pdf
2+
use alloc::vec;
3+
use core::marker::PhantomData;
4+
5+
use crate::{
6+
builder::{Profile, Result},
7+
certificate::{Certificate, TbsCertificate, Version},
8+
ext::{
9+
pkix::{
10+
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
11+
},
12+
AsExtension, Extension, Extensions,
13+
},
14+
name::Name,
15+
};
16+
use spki::{EncodePublicKey, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef};
17+
18+
pub struct Root {
19+
pub emits_ocsp_response: bool,
20+
}
21+
22+
impl Default for Root {
23+
fn default() -> Self {
24+
Self {
25+
emits_ocsp_response: false,
26+
}
27+
}
28+
}
29+
30+
impl Profile for Root {
31+
fn get_issuer(&self, subject: &Name) -> Name {
32+
subject.clone()
33+
}
34+
35+
fn build_extensions(
36+
&self,
37+
spk: SubjectPublicKeyInfoRef<'_>,
38+
issuer_spk: SubjectPublicKeyInfoRef<'_>,
39+
tbs: &TbsCertificate,
40+
) -> Result<vec::Vec<Extension>> {
41+
let mut extensions: vec::Vec<Extension> = vec::Vec::new();
42+
43+
// 7.1.2.1.2 Root CA Extensions
44+
45+
// subjectKeyIdentifier MUST
46+
//
47+
// TODO: from 7.1.2.11.4 Subject Key Identifier
48+
// The CA MUST generate a subjectKeyIdentifier that is unique within the scope of all
49+
// Certificates it has issued for each unique public key (the subjectPublicKeyInfo field of the
50+
// tbsCertificate). For example, CAs may generate the subject key identifier using an algorithm
51+
// derived from the public key, or may generate a sufficiently‐large unique number, such by using a
52+
// CSPRNG.
53+
let ski = SubjectKeyIdentifier::try_from(spk)?;
54+
extensions.push(ski.to_extension(&tbs.subject, &extensions)?);
55+
56+
// authorityKeyIdentifier RECOMMENDED
57+
// 7.1.2.1.3 Root CA Authority Key Identifier
58+
extensions.push(
59+
AuthorityKeyIdentifier {
60+
// KeyIdentifier must be the same as subjectKeyIdentifier
61+
key_identifier: Some(ski.0),
62+
// other fields must not be present.
63+
..Default::default()
64+
}
65+
.to_extension(&tbs.subject, &extensions)?,
66+
);
67+
68+
// Spec: 7.1.2.1.4 Root CA Basic Constraints
69+
extensions.push(
70+
BasicConstraints {
71+
ca: true,
72+
path_len_constraint: None,
73+
}
74+
.to_extension(&tbs.subject, &extensions)?,
75+
);
76+
77+
// Spec: 7.1.2.10.7 CA Certificate Key Usage
78+
let mut key_usage = KeyUsages::KeyCertSign | KeyUsages::CRLSign;
79+
if self.emits_ocsp_response {
80+
key_usage |= KeyUsages::DigitalSignature;
81+
}
82+
extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
83+
84+
Ok(extensions)
85+
}
86+
87+
// 7.1.2.1 Root CA Certificate Profile
88+
// TODO:
89+
// - issuerUniqueID MUST NOT be present
90+
// - subjectUniqueID MUST NOT be present
91+
// NOTE(baloo): we never build those?
92+
//
93+
// 7.1.2.1.1 Root CA Validity
94+
// TODO:
95+
// - Minimum 2922 days (approx. 8 years)
96+
// - Max 9132 days (approx. 25 years)
97+
//
98+
//
99+
}
100+
101+
pub mod tls;
102+
103+
pub mod codesigning {
104+
//! https://cabforum.org/wp-content/uploads/Baseline-Requirements-for-the-Issuance-and-Management-of-Code-Signing.v2.8.pdf
105+
}

0 commit comments

Comments
 (0)