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)]
16pub 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#[derive(Clone, Debug, Default)]
42pub struct CertificateBuilder {
43 params: CertificateParams,
44 alg: KeyPairAlgorithm,
45}
46
47impl CertificateBuilder {
48 pub fn new() -> Self {
55 let mut params = CertificateParams::default();
56 params.distinguished_name = DistinguishedName::new();
58 Self {
59 params,
60 alg: KeyPairAlgorithm::EcdsaP256,
61 }
62 }
63 pub fn signature_algorithm(mut self, alg: KeyPairAlgorithm) -> anyhow::Result<Self> {
65 self.alg = alg;
66 Ok(self)
67 }
68 pub fn certificate_authority(self) -> CaBuilder {
75 CaBuilder::new(self.params, self.alg)
76 }
77 pub fn end_entity(self) -> EndEntityBuilder {
79 EndEntityBuilder::new(self.params, self.alg)
80 }
81}
82
83#[derive(Clone, Debug)]
85pub struct CaBuilder {
86 params: CertificateParams,
87 alg: KeyPairAlgorithm,
88}
89
90impl CaBuilder {
91 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 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 pub fn organization_name(mut self, name: &str) -> Self {
110 self.params
111 .distinguished_name
112 .push(DnType::OrganizationName, name);
113 self
114 }
115 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
126pub struct Ca {
128 cert: Certificate,
129 issuer: Issuer<'static, KeyPair>,
130}
131
132impl Ca {
133 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 #[allow(dead_code)]
142 pub fn cert(&self) -> &Certificate {
143 &self.cert
144 }
145}
146
147impl fmt::Debug for Ca {
148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150 let Ca { cert, issuer } = self;
152
153 f.debug_struct("Ca")
154 .field("cert", cert)
155 .field("issuer", issuer)
156 .finish()
157 }
158}
159
160pub struct EndEntity {
162 cert: Certificate,
163 key_pair: KeyPair,
164}
165
166impl EndEntity {
167 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 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179 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#[derive(Clone, Debug)]
191pub struct EndEntityBuilder {
192 params: CertificateParams,
193 alg: KeyPairAlgorithm,
194}
195
196impl EndEntityBuilder {
197 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 pub fn common_name(mut self, name: &str) -> Self {
207 self.params
208 .distinguished_name
209 .push(DnType::CommonName, name);
210 self
211 }
212 pub fn subject_alternative_names(mut self, sans: Vec<SanType>) -> Self {
216 self.params.subject_alt_names.extend(sans);
217 self
218 }
219 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 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 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#[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 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 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}