Skip to content

Commit b0209bb

Browse files
committed
x509-cert: wip: adds a builder for certificate
1 parent f855ff6 commit b0209bb

20 files changed

+1068
-34
lines changed

Cargo.lock

+205-31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

der/src/asn1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub use self::{
5555
sequence_of::{SequenceOf, SequenceOfIter},
5656
set_of::{SetOf, SetOfIter},
5757
teletex_string::TeletexStringRef,
58-
utc_time::UtcTime,
58+
utc_time::{UtcTime, MAX_YEAR},
5959
utf8_string::Utf8StringRef,
6060
videotex_string::VideotexStringRef,
6161
};

x509-cert/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,22 @@ const-oid = { version = "=0.10.0-pre", features = ["db"], path = "../const-oid"
2020
der = { version = "=0.7.0-pre", features = ["derive", "alloc", "flagset"], path = "../der" }
2121
flagset = { version = "0.4.3" }
2222
spki = { version = "=0.7.0-pre", path = "../spki", features = ["alloc"] }
23+
sha-1 = { version = "0.10.0", optional = true }
2324

2425
[dev-dependencies]
2526
hex-literal = "0.3"
27+
rand = "0.8.5"
28+
rsa = "0.7.2"
2629
rstest = "0.16"
30+
serde = { version = "1.0", features = ["derive"] }
31+
serde_json = "1.0.91"
32+
sha2 = { version = "0.10.6", features = ["oid"] }
33+
tempfile = "3.3.0"
2734

2835
[features]
2936
alloc = ["der/alloc"]
3037
arbitrary = ["std", "dep:arbitrary", "const-oid/arbitrary", "der/arbitrary", "spki/arbitrary"]
38+
builder = ["std", "sha-1/default"]
3139
pem = ["alloc", "der/pem"]
3240
std = ["der/std", "spki/std"]
3341

x509-cert/src/builder.rs

+350
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
//! X509 Certificate builder
2+
3+
use alloc::vec::Vec;
4+
use der::asn1::{BitString, OctetString};
5+
use sha1::{Digest, Sha1};
6+
use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned};
7+
8+
use crate::{
9+
certificate::{Certificate, TbsCertificate, Version},
10+
ext::{
11+
pkix::{
12+
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
13+
},
14+
AsExtension, Extension,
15+
},
16+
name::Name,
17+
serial_number::SerialNumber,
18+
time::Validity,
19+
};
20+
21+
type Result<T> = core::result::Result<T, der::Error>;
22+
23+
/// UniqueIds holds the optional attributes `issuerUniqueID` and `subjectUniqueID`
24+
/// to be filled in the TBSCertificate if version v2 or v3.
25+
///
26+
/// See X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1]
27+
pub struct UniqueIds {
28+
/// ```text
29+
/// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
30+
/// -- If present, version MUST be v2 or v3
31+
/// ```
32+
pub issuer_unique_id: Option<BitString>,
33+
/// ```text
34+
/// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
35+
/// -- If present, version MUST be v2 or v3
36+
/// ```
37+
pub subject_unique_id: Option<BitString>,
38+
}
39+
40+
impl UniqueIds {
41+
fn get_unique_ids(&self) -> (Option<BitString>, Option<BitString>) {
42+
(
43+
self.issuer_unique_id.clone(),
44+
self.subject_unique_id.clone(),
45+
)
46+
}
47+
}
48+
49+
/// The type of certificate to build
50+
#[derive(Clone, Debug, Eq, PartialEq)]
51+
pub enum Profile {
52+
/// Build a root CA certificate
53+
Root,
54+
/// Build an intermediate sub CA certificate
55+
SubCA {
56+
/// issuer Name,
57+
/// represents the name signing the certificate
58+
issuer: Name,
59+
/// pathLenConstraint INTEGER (0..MAX) OPTIONAL
60+
/// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
61+
path_len_constraint: Option<u8>,
62+
},
63+
/// Build an end certificate
64+
Leaf {
65+
/// issuer Name,
66+
/// represents the name signing the certificate
67+
issuer: Name,
68+
},
69+
}
70+
71+
impl Profile {
72+
fn get_issuer(&self, subject: &Name) -> Name {
73+
match self {
74+
Profile::Root => subject.clone(),
75+
Profile::SubCA { issuer, .. } => issuer.clone(),
76+
Profile::Leaf { issuer } => issuer.clone(),
77+
}
78+
}
79+
80+
fn build_extensions(
81+
&self,
82+
spk: &SubjectPublicKeyInfoOwned,
83+
issuer_spk: &SubjectPublicKeyInfoOwned,
84+
) -> Result<Vec<Extension>> {
85+
let mut extensions: Vec<Extension> = Vec::new();
86+
87+
extensions.push({
88+
let result = Sha1::digest(spk.subject_public_key.raw_bytes());
89+
SubjectKeyIdentifier(OctetString::new(result.to_vec())?).to_extension()?
90+
});
91+
92+
// Build Authority Key Identifier
93+
match self {
94+
Profile::Root => {}
95+
_ => {
96+
let mut hasher = Sha1::new();
97+
hasher.update(issuer_spk.subject_public_key.raw_bytes());
98+
let result = hasher.finalize();
99+
100+
extensions.push(
101+
AuthorityKeyIdentifier {
102+
key_identifier: Some(OctetString::new(result.to_vec())?),
103+
authority_cert_issuer: None,
104+
authority_cert_serial_number: None,
105+
}
106+
.to_extension()?,
107+
);
108+
}
109+
}
110+
111+
// Build Basic Contraints extensions
112+
extensions.push(match self {
113+
Profile::Root => BasicConstraints {
114+
ca: true,
115+
path_len_constraint: None,
116+
}
117+
.to_extension()?,
118+
Profile::SubCA {
119+
path_len_constraint,
120+
..
121+
} => BasicConstraints {
122+
ca: true,
123+
path_len_constraint: *path_len_constraint,
124+
}
125+
.to_extension()?,
126+
Profile::Leaf { .. } => BasicConstraints {
127+
ca: false,
128+
path_len_constraint: None,
129+
}
130+
.to_extension()?,
131+
});
132+
133+
// Build Key Usage extension
134+
match self {
135+
Profile::Root | Profile::SubCA { .. } => {
136+
extensions.push(
137+
KeyUsage(
138+
KeyUsages::DigitalSignature | KeyUsages::KeyCertSign | KeyUsages::CRLSign,
139+
)
140+
.to_extension()?,
141+
);
142+
}
143+
Profile::Leaf { .. } => {
144+
//todo!();
145+
}
146+
}
147+
148+
Ok(extensions)
149+
}
150+
}
151+
152+
/// The version of the Certificate to build.
153+
/// All newly built certificate should use `CertificateVersion::V3`
154+
pub enum CertificateVersion {
155+
/// Generate a X509 version 1
156+
V1,
157+
/// Generate a X509 version 2
158+
V2(UniqueIds),
159+
/// Generate a X509 version 3
160+
V3(UniqueIds),
161+
}
162+
163+
impl From<CertificateVersion> for Version {
164+
fn from(cv: CertificateVersion) -> Version {
165+
use CertificateVersion::*;
166+
match cv {
167+
V1 => Version::V1,
168+
V2(_) => Version::V2,
169+
V3(_) => Version::V3,
170+
}
171+
}
172+
}
173+
174+
/// X509 Certificate builder
175+
///
176+
/// ```
177+
/// use der::Decode;
178+
/// use x509_cert::spki::SubjectPublicKeyInfoOwned;
179+
/// use x509_cert::builder::{CertificateBuilder, CertificateVersion, Profile, UniqueIds};
180+
/// use x509_cert::name::Name;
181+
/// use x509_cert::serial_number::SerialNumber;
182+
/// use x509_cert::time::Validity;
183+
///
184+
/// # use std::time::Duration;
185+
/// # use x509_cert::constants;
186+
/// # use x509_cert::certificate::TbsCertificate;
187+
/// # use x509_cert::builder::Signer;
188+
/// # const RSA_2048_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-pub.der");
189+
///
190+
/// # struct RsaCertSigner;
191+
/// # impl Signer for RsaCertSigner {
192+
/// # type Alg = constants::RsaWithSha256;
193+
///
194+
/// # fn signature_algorithm(&self) -> Self::Alg {
195+
/// # constants::RsaWithSha256
196+
/// # }
197+
///
198+
/// # fn public_key(&self) -> SubjectPublicKeyInfoOwned {
199+
/// # SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key")
200+
/// # }
201+
///
202+
/// # fn sign(&self, input: &TbsCertificate) -> Vec<u8> {
203+
/// # todo!();
204+
/// # }
205+
/// # }
206+
///
207+
/// let uids = UniqueIds {
208+
/// issuer_unique_id: None,
209+
/// subject_unique_id: None,
210+
/// };
211+
///
212+
/// let serial_number = SerialNumber::from(42u32);
213+
/// let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
214+
/// let profile = Profile::Root;
215+
/// let subject =
216+
/// Name::encode_from_string("CN=World domination corporation,O=World domination Inc,C=US")
217+
/// .unwrap();
218+
/// let subject = Name::from_der(&subject).unwrap();
219+
/// let pub_key = SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key");
220+
///
221+
/// let mut builder = CertificateBuilder::new(
222+
/// profile,
223+
/// CertificateVersion::V3(uids),
224+
/// serial_number,
225+
/// validity,
226+
/// subject,
227+
/// pub_key,
228+
/// &RsaCertSigner,
229+
/// )
230+
/// .expect("Create certificate");
231+
/// ```
232+
pub struct CertificateBuilder<'s, S: Signer> {
233+
version: Version,
234+
serial_number: SerialNumber,
235+
signature_alg: AlgorithmIdentifierOwned,
236+
issuer: Name,
237+
validity: Validity,
238+
subject: Name,
239+
subject_public_key_info: SubjectPublicKeyInfoOwned,
240+
issuer_unique_id: Option<BitString>,
241+
subject_unique_id: Option<BitString>,
242+
243+
extensions: Vec<Extension>,
244+
245+
signer: &'s S,
246+
}
247+
248+
impl<'s, S: Signer> CertificateBuilder<'s, S> {
249+
/// Creates a new certificate builder
250+
pub fn new(
251+
profile: Profile,
252+
version: CertificateVersion,
253+
serial_number: SerialNumber,
254+
mut validity: Validity,
255+
subject: Name,
256+
subject_public_key_info: SubjectPublicKeyInfoOwned,
257+
signer: &'s S,
258+
) -> Result<Self> {
259+
let signer_pub = signer.public_key();
260+
261+
let signature_alg = signer.signature_algorithm().identifier();
262+
let issuer = profile.get_issuer(&subject);
263+
264+
validity.not_before.rfc5280_adjust_utc_time()?;
265+
validity.not_after.rfc5280_adjust_utc_time()?;
266+
267+
let (version, (issuer_unique_id, subject_unique_id), extensions) = match version {
268+
CertificateVersion::V1 => (Version::V1, (None, None), Vec::new()),
269+
CertificateVersion::V2(uids) => (Version::V2, uids.get_unique_ids(), Vec::new()),
270+
CertificateVersion::V3(uids) => (
271+
Version::V3,
272+
uids.get_unique_ids(),
273+
profile.build_extensions(&subject_public_key_info, &signer_pub)?,
274+
),
275+
};
276+
277+
Ok(Self {
278+
version,
279+
issuer_unique_id,
280+
subject_unique_id,
281+
serial_number,
282+
issuer,
283+
subject,
284+
validity,
285+
subject_public_key_info,
286+
signature_alg,
287+
extensions,
288+
signer,
289+
})
290+
}
291+
292+
/// Add an extension to this certificate
293+
pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
294+
self.extensions.push(extension.to_extension()?);
295+
Ok(())
296+
}
297+
298+
/// Run the certificate through the signer and build the end certificate.
299+
pub fn build(&mut self) -> Result<Certificate> {
300+
let extensions = if self.version == Version::V3 {
301+
Some(self.extensions.clone())
302+
} else {
303+
None
304+
};
305+
306+
let tbs_certificate = TbsCertificate {
307+
version: self.version,
308+
serial_number: self.serial_number.clone(),
309+
signature: self.signature_alg.clone(),
310+
issuer: self.issuer.clone(),
311+
validity: self.validity,
312+
subject: self.subject.clone(),
313+
subject_public_key_info: self.subject_public_key_info.clone(),
314+
issuer_unique_id: self.issuer_unique_id.clone(),
315+
subject_unique_id: self.subject_unique_id.clone(),
316+
extensions,
317+
};
318+
319+
let signature = BitString::from_bytes(&self.signer.sign(&tbs_certificate))?;
320+
321+
let cert = Certificate {
322+
tbs_certificate,
323+
signature_algorithm: self.signature_alg.clone(),
324+
signature,
325+
};
326+
327+
Ok(cert)
328+
}
329+
}
330+
331+
/// The certificate algorithm used to sign the certificate
332+
pub trait CertSignatureAlgorithm {
333+
/// Returns the `AlgorithmIdentifierOwned` used to sign the certificate
334+
fn identifier(&self) -> AlgorithmIdentifierOwned;
335+
}
336+
337+
/// Signer to be used to for signing the certificates
338+
pub trait Signer {
339+
/// The type of signature expected from this signer
340+
type Alg: CertSignatureAlgorithm;
341+
342+
/// The signature expected from this signer
343+
fn signature_algorithm(&self) -> Self::Alg;
344+
345+
/// The SPKI encoded public key used by this signer
346+
fn public_key(&self) -> SubjectPublicKeyInfoOwned;
347+
348+
/// The sign method should return the signature of the payload.
349+
fn sign(&self, input: &TbsCertificate) -> Vec<u8>;
350+
}

0 commit comments

Comments
 (0)