rustls_cert_gen/
cert.rs

1use std::{fmt, fs::File, io, path::Path};
2
3use bpaf::Bpaf;
4use rcgen::{
5	BasicConstraints, Certificate, CertificateParams, DistinguishedName, DnType,
6	DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, Issuer, KeyPair, KeyUsagePurpose,
7	SanType,
8};
9
10#[cfg(feature = "aws_lc_rs")]
11use aws_lc_rs as ring_like;
12#[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))]
13use ring as ring_like;
14
15#[derive(Debug, Clone)]
16/// PEM serialized Certificate and PEM serialized corresponding private key
17pub struct PemCertifiedKey {
18	pub cert_pem: String,
19	pub private_key_pem: String,
20}
21
22impl PemCertifiedKey {
23	pub fn write(&self, dir: &Path, name: &str) -> Result<(), io::Error> {
24		use std::io::Write;
25		std::fs::create_dir_all(dir)?;
26
27		let key_path = dir.join(format!("{name}.key.pem"));
28		let mut key_out = File::create(key_path)?;
29		write!(key_out, "{}", &self.private_key_pem)?;
30
31		let cert_path = dir.join(format!("{name}.pem"));
32		let mut cert_out = File::create(cert_path)?;
33		write!(cert_out, "{}", &self.cert_pem)?;
34
35		Ok(())
36	}
37}
38
39/// Builder to configure TLS [CertificateParams] to be finalized
40/// into either a [Ca] or an [EndEntity].
41#[derive(Clone, Debug, Default)]
42pub struct CertificateBuilder {
43	params: CertificateParams,
44	alg: KeyPairAlgorithm,
45}
46
47impl CertificateBuilder {
48	/// Initialize `CertificateParams` with defaults
49	/// # Example
50	/// ```
51	/// # use rustls_cert_gen::CertificateBuilder;
52	/// let cert = CertificateBuilder::new();
53	/// ```
54	pub fn new() -> Self {
55		let mut params = CertificateParams::default();
56		// override default Common Name
57		params.distinguished_name = DistinguishedName::new();
58		Self {
59			params,
60			alg: KeyPairAlgorithm::EcdsaP256,
61		}
62	}
63	/// Set signature algorithm (instead of default).
64	pub fn signature_algorithm(mut self, alg: KeyPairAlgorithm) -> anyhow::Result<Self> {
65		self.alg = alg;
66		Ok(self)
67	}
68	/// Set options for Ca Certificates
69	/// # Example
70	/// ```
71	/// # use rustls_cert_gen::CertificateBuilder;
72	/// let cert = CertificateBuilder::new().certificate_authority();
73	/// ```
74	pub fn certificate_authority(self) -> CaBuilder {
75		CaBuilder::new(self.params, self.alg)
76	}
77	/// Set options for `EndEntity` Certificates
78	pub fn end_entity(self) -> EndEntityBuilder {
79		EndEntityBuilder::new(self.params, self.alg)
80	}
81}
82
83/// [CertificateParams] from which an [Ca] [Certificate] can be built
84#[derive(Clone, Debug)]
85pub struct CaBuilder {
86	params: CertificateParams,
87	alg: KeyPairAlgorithm,
88}
89
90impl CaBuilder {
91	/// Initialize `CaBuilder`
92	pub fn new(mut params: CertificateParams, alg: KeyPairAlgorithm) -> Self {
93		params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
94		params.key_usages.push(KeyUsagePurpose::DigitalSignature);
95		params.key_usages.push(KeyUsagePurpose::KeyCertSign);
96		params.key_usages.push(KeyUsagePurpose::CrlSign);
97		Self { params, alg }
98	}
99	/// Add CountryName to `distinguished_name`. Multiple calls will
100	/// replace previous value.
101	pub fn country_name(mut self, country: &str) -> Result<Self, rcgen::Error> {
102		self.params
103			.distinguished_name
104			.push(DnType::CountryName, PrintableString(country.try_into()?));
105		Ok(self)
106	}
107	/// Add OrganizationName to `distinguished_name`. Multiple calls will
108	/// replace previous value.
109	pub fn organization_name(mut self, name: &str) -> Self {
110		self.params
111			.distinguished_name
112			.push(DnType::OrganizationName, name);
113		self
114	}
115	/// build `Ca` Certificate.
116	pub fn build(self) -> Result<Ca, rcgen::Error> {
117		let key_pair = self.alg.to_key_pair()?;
118		let cert = self.params.self_signed(&key_pair)?;
119		Ok(Ca {
120			cert,
121			issuer: Issuer::new(self.params, key_pair),
122		})
123	}
124}
125
126/// End-entity [Certificate]
127pub struct Ca {
128	cert: Certificate,
129	issuer: Issuer<'static, KeyPair>,
130}
131
132impl Ca {
133	/// Self-sign and serialize
134	pub fn serialize_pem(&self) -> PemCertifiedKey {
135		PemCertifiedKey {
136			cert_pem: self.cert.pem(),
137			private_key_pem: self.issuer.key().serialize_pem(),
138		}
139	}
140	/// Return `&Certificate`
141	#[allow(dead_code)]
142	pub fn cert(&self) -> &Certificate {
143		&self.cert
144	}
145}
146
147impl fmt::Debug for Ca {
148	/// Formats the `Ca` information without revealing the key pair.
149	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150		// The key pair is omitted from the debug output as it contains secret information.
151		let Ca { cert, issuer } = self;
152
153		f.debug_struct("Ca")
154			.field("cert", cert)
155			.field("issuer", issuer)
156			.finish()
157	}
158}
159
160/// End-entity [Certificate]
161pub struct EndEntity {
162	cert: Certificate,
163	key_pair: KeyPair,
164}
165
166impl EndEntity {
167	/// Sign with `signer` and serialize.
168	pub fn serialize_pem(&self) -> PemCertifiedKey {
169		PemCertifiedKey {
170			cert_pem: self.cert.pem(),
171			private_key_pem: self.key_pair.serialize_pem(),
172		}
173	}
174}
175
176impl fmt::Debug for EndEntity {
177	/// Formats the `EndEntity` information without revealing the key pair.
178	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179		// The key pair is omitted from the debug output as it contains secret information.
180		let EndEntity { cert, key_pair } = self;
181
182		f.debug_struct("EndEntity")
183			.field("cert", cert)
184			.field("key_pair", key_pair)
185			.finish()
186	}
187}
188
189/// [CertificateParams] from which an [EndEntity] [Certificate] can be built
190#[derive(Clone, Debug)]
191pub struct EndEntityBuilder {
192	params: CertificateParams,
193	alg: KeyPairAlgorithm,
194}
195
196impl EndEntityBuilder {
197	/// Initialize `EndEntityBuilder`
198	pub fn new(mut params: CertificateParams, alg: KeyPairAlgorithm) -> Self {
199		params.is_ca = IsCa::NoCa;
200		params.use_authority_key_identifier_extension = true;
201		params.key_usages.push(KeyUsagePurpose::DigitalSignature);
202		Self { params, alg }
203	}
204	/// Add CommonName to `distinguished_name`. Multiple calls will
205	/// replace previous value.
206	pub fn common_name(mut self, name: &str) -> Self {
207		self.params
208			.distinguished_name
209			.push(DnType::CommonName, name);
210		self
211	}
212	/// `SanTypes` that will be recorded as
213	/// `subject_alt_names`. Multiple calls will append to previous
214	/// values.
215	pub fn subject_alternative_names(mut self, sans: Vec<SanType>) -> Self {
216		self.params.subject_alt_names.extend(sans);
217		self
218	}
219	/// Add ClientAuth to `extended_key_usages` if it is not already present.
220	pub fn client_auth(&mut self) -> &Self {
221		let usage = ExtendedKeyUsagePurpose::ClientAuth;
222		if !self.params.extended_key_usages.iter().any(|e| e == &usage) {
223			self.params.extended_key_usages.push(usage);
224		}
225		self
226	}
227	/// Add ServerAuth to `extended_key_usages` if it is not already present.
228	pub fn server_auth(&mut self) -> &Self {
229		let usage = ExtendedKeyUsagePurpose::ServerAuth;
230		if !self.params.extended_key_usages.iter().any(|e| e == &usage) {
231			self.params.extended_key_usages.push(usage);
232		}
233		self
234	}
235	/// build `EndEntity` Certificate.
236	pub fn build(self, issuer: &Ca) -> Result<EndEntity, rcgen::Error> {
237		let key_pair = self.alg.to_key_pair()?;
238		let cert = self.params.signed_by(&key_pair, &issuer.issuer)?;
239		Ok(EndEntity { cert, key_pair })
240	}
241}
242
243/// Supported Keypair Algorithms
244#[derive(Clone, Copy, Debug, Default, Bpaf, PartialEq)]
245pub enum KeyPairAlgorithm {
246	Rsa,
247	Ed25519,
248	#[default]
249	EcdsaP256,
250	EcdsaP384,
251	#[cfg(feature = "aws_lc_rs")]
252	EcdsaP521,
253}
254
255impl KeyPairAlgorithm {
256	/// Return an `rcgen::KeyPair` for the given varient
257	fn to_key_pair(self) -> Result<rcgen::KeyPair, rcgen::Error> {
258		match self {
259			KeyPairAlgorithm::Rsa => rcgen::KeyPair::generate_for(&rcgen::PKCS_RSA_SHA256),
260			KeyPairAlgorithm::Ed25519 => {
261				use ring_like::signature::Ed25519KeyPair;
262
263				let rng = ring_like::rand::SystemRandom::new();
264				let alg = &rcgen::PKCS_ED25519;
265				let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng)
266					.map_err(|_| rcgen::Error::RingUnspecified)?;
267
268				rcgen::KeyPair::from_pkcs8_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg)
269			},
270			KeyPairAlgorithm::EcdsaP256 => {
271				use ring_like::signature::EcdsaKeyPair;
272				use ring_like::signature::ECDSA_P256_SHA256_ASN1_SIGNING;
273
274				let rng = ring_like::rand::SystemRandom::new();
275				let alg = &rcgen::PKCS_ECDSA_P256_SHA256;
276				let pkcs8_bytes =
277					EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng)
278						.map_err(|_| rcgen::Error::RingUnspecified)?;
279				rcgen::KeyPair::from_pkcs8_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg)
280			},
281			KeyPairAlgorithm::EcdsaP384 => {
282				use ring_like::signature::EcdsaKeyPair;
283				use ring_like::signature::ECDSA_P384_SHA384_ASN1_SIGNING;
284
285				let rng = ring_like::rand::SystemRandom::new();
286				let alg = &rcgen::PKCS_ECDSA_P384_SHA384;
287				let pkcs8_bytes =
288					EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_ASN1_SIGNING, &rng)
289						.map_err(|_| rcgen::Error::RingUnspecified)?;
290
291				rcgen::KeyPair::from_pkcs8_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg)
292			},
293			#[cfg(feature = "aws_lc_rs")]
294			KeyPairAlgorithm::EcdsaP521 => {
295				use ring_like::signature::EcdsaKeyPair;
296				use ring_like::signature::ECDSA_P521_SHA512_ASN1_SIGNING;
297
298				let rng = ring_like::rand::SystemRandom::new();
299				let alg = &rcgen::PKCS_ECDSA_P521_SHA512;
300				let pkcs8_bytes =
301					EcdsaKeyPair::generate_pkcs8(&ECDSA_P521_SHA512_ASN1_SIGNING, &rng)
302						.map_err(|_| rcgen::Error::RingUnspecified)?;
303
304				rcgen::KeyPair::from_pkcs8_der_and_sign_algo(&pkcs8_bytes.as_ref().into(), alg)
305			},
306		}
307	}
308}
309
310impl fmt::Display for KeyPairAlgorithm {
311	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312		match self {
313			KeyPairAlgorithm::Rsa => write!(f, "rsa"),
314			KeyPairAlgorithm::Ed25519 => write!(f, "ed25519"),
315			KeyPairAlgorithm::EcdsaP256 => write!(f, "ecdsa-p256"),
316			KeyPairAlgorithm::EcdsaP384 => write!(f, "ecdsa-p384"),
317			#[cfg(feature = "aws_lc_rs")]
318			KeyPairAlgorithm::EcdsaP521 => write!(f, "ecdsa-p521"),
319		}
320	}
321}
322
323#[cfg(test)]
324mod tests {
325	use x509_parser::prelude::{FromDer, X509Certificate};
326
327	use super::*;
328
329	#[test]
330	fn test_write_files() -> anyhow::Result<()> {
331		use assert_fs::prelude::*;
332		let temp = assert_fs::TempDir::new()?;
333		let dir = temp.path();
334		let entity_cert = temp.child("cert.pem");
335		let entity_key = temp.child("cert.key.pem");
336
337		let pck = PemCertifiedKey {
338			cert_pem: "x".into(),
339			private_key_pem: "y".into(),
340		};
341
342		pck.write(dir, "cert")?;
343
344		// assert contents of created files
345		entity_cert.assert("x");
346		entity_key.assert("y");
347
348		Ok(())
349	}
350	#[test]
351	fn init_ca() {
352		let cert = CertificateBuilder::new().certificate_authority();
353		assert_eq!(cert.params.is_ca, IsCa::Ca(BasicConstraints::Unconstrained))
354	}
355	#[test]
356	fn with_sig_algo_default() -> anyhow::Result<()> {
357		let end_entity = CertificateBuilder::new().end_entity();
358
359		assert_eq!(end_entity.alg, KeyPairAlgorithm::EcdsaP256);
360		Ok(())
361	}
362	#[test]
363	fn serialize_end_entity_default_sig() -> anyhow::Result<()> {
364		let ca = CertificateBuilder::new().certificate_authority().build()?;
365		let end_entity = CertificateBuilder::new()
366			.end_entity()
367			.build(&ca)?
368			.serialize_pem();
369
370		let der = pem::parse(end_entity.cert_pem)?;
371		let (_, cert) = X509Certificate::from_der(der.contents())?;
372
373		let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?;
374		let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?;
375
376		assert!(!cert.is_ca());
377		check_signature(&cert, &issuer);
378
379		Ok(())
380	}
381	#[test]
382	fn serialize_end_entity_ecdsa_p384_sha384_sig() -> anyhow::Result<()> {
383		let ca = CertificateBuilder::new().certificate_authority().build()?;
384		let end_entity = CertificateBuilder::new()
385			.signature_algorithm(KeyPairAlgorithm::EcdsaP384)?
386			.end_entity()
387			.build(&ca)?
388			.serialize_pem();
389
390		let der = pem::parse(end_entity.cert_pem)?;
391		let (_, cert) = X509Certificate::from_der(der.contents())?;
392
393		let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?;
394		let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?;
395
396		check_signature(&cert, &issuer);
397		Ok(())
398	}
399
400	#[test]
401	#[cfg(feature = "aws_lc_rs")]
402	fn serialize_end_entity_ecdsa_p521_sha512_sig() -> anyhow::Result<()> {
403		let ca = CertificateBuilder::new().certificate_authority().build()?;
404		let end_entity = CertificateBuilder::new()
405			.signature_algorithm(KeyPairAlgorithm::EcdsaP521)?
406			.end_entity()
407			.build(&ca)?
408			.serialize_pem();
409
410		let der = pem::parse(end_entity.cert_pem)?;
411		let (_, cert) = X509Certificate::from_der(der.contents())?;
412
413		let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?;
414		let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?;
415
416		check_signature(&cert, &issuer);
417		Ok(())
418	}
419
420	#[test]
421	fn serialize_end_entity_ed25519_sig() -> anyhow::Result<()> {
422		let ca = CertificateBuilder::new().certificate_authority().build()?;
423		let end_entity = CertificateBuilder::new()
424			.signature_algorithm(KeyPairAlgorithm::Ed25519)?
425			.end_entity()
426			.build(&ca)?
427			.serialize_pem();
428
429		let der = pem::parse(end_entity.cert_pem)?;
430		let (_, cert) = X509Certificate::from_der(der.contents())?;
431
432		let issuer_der = pem::parse(ca.serialize_pem().cert_pem)?;
433		let (_, issuer) = X509Certificate::from_der(issuer_der.contents())?;
434
435		check_signature(&cert, &issuer);
436		Ok(())
437	}
438
439	fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) {
440		let verified = cert.verify_signature(Some(issuer.public_key())).is_ok();
441		assert!(verified);
442	}
443
444	#[test]
445	fn init_end_endity() {
446		let params = CertificateParams::default();
447		let cert = EndEntityBuilder::new(params, KeyPairAlgorithm::default());
448		assert_eq!(cert.params.is_ca, IsCa::NoCa)
449	}
450	#[test]
451	fn client_auth_end_entity() {
452		let _ca = CertificateBuilder::new()
453			.certificate_authority()
454			.build()
455			.unwrap();
456		let params = CertificateParams::default();
457		let mut cert = EndEntityBuilder::new(params, KeyPairAlgorithm::default());
458		assert_eq!(cert.params.is_ca, IsCa::NoCa);
459		assert_eq!(
460			cert.client_auth().params.extended_key_usages,
461			vec![ExtendedKeyUsagePurpose::ClientAuth]
462		);
463	}
464	#[test]
465	fn server_auth_end_entity() {
466		let _ca = CertificateBuilder::new()
467			.certificate_authority()
468			.build()
469			.unwrap();
470		let params = CertificateParams::default();
471		let mut cert = EndEntityBuilder::new(params, KeyPairAlgorithm::default());
472		assert_eq!(cert.params.is_ca, IsCa::NoCa);
473		assert_eq!(
474			cert.server_auth().params.extended_key_usages,
475			vec![ExtendedKeyUsagePurpose::ServerAuth]
476		);
477	}
478	#[test]
479	fn sans_end_entity() {
480		let _ca = CertificateBuilder::new()
481			.certificate_authority()
482			.build()
483			.unwrap();
484		let name = "unexpected.oomyoo.xyz";
485		let names = vec![SanType::DnsName(name.try_into().unwrap())];
486		let params = CertificateParams::default();
487		let cert = EndEntityBuilder::new(params, KeyPairAlgorithm::default())
488			.subject_alternative_names(names);
489		assert_eq!(
490			cert.params.subject_alt_names,
491			vec![rcgen::SanType::DnsName(name.try_into().unwrap())]
492		);
493	}
494	#[test]
495	fn sans_end_entity_empty() {
496		let _ca = CertificateBuilder::new()
497			.certificate_authority()
498			.build()
499			.unwrap();
500		let names = vec![];
501		let params = CertificateParams::default();
502		let cert = EndEntityBuilder::new(params, KeyPairAlgorithm::default())
503			.subject_alternative_names(names);
504		assert_eq!(cert.params.subject_alt_names, vec![]);
505	}
506
507	#[test]
508	fn key_pair_algorithm_to_keypair() -> anyhow::Result<()> {
509		let keypair = KeyPairAlgorithm::Ed25519.to_key_pair()?;
510		assert_eq!(format!("{:?}", keypair.algorithm()), "PKCS_ED25519");
511		let keypair = KeyPairAlgorithm::EcdsaP256.to_key_pair()?;
512		assert_eq!(
513			format!("{:?}", keypair.algorithm()),
514			"PKCS_ECDSA_P256_SHA256"
515		);
516		let keypair = KeyPairAlgorithm::EcdsaP384.to_key_pair()?;
517		assert_eq!(
518			format!("{:?}", keypair.algorithm()),
519			"PKCS_ECDSA_P384_SHA384"
520		);
521
522		#[cfg(feature = "aws_lc_rs")]
523		{
524			let keypair = KeyPairAlgorithm::EcdsaP521.to_key_pair()?;
525			assert_eq!(
526				format!("{:?}", keypair.algorithm()),
527				"PKCS_ECDSA_P521_SHA512"
528			);
529		}
530		Ok(())
531	}
532}