1use crate::pki_types::{CertificateDer, PrivateKeyDer};
14use rama_core::error::{BoxError, BoxErrorExt as _};
15use rama_net::address::{Domain, Host};
16use serde::{Deserialize, Serialize};
17use std::{net::IpAddr, time::Duration};
18
19#[cfg(feature = "boring")]
20#[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
21pub mod boring;
22
23#[cfg(any(feature = "aws-lc", feature = "ring"))]
24pub mod rcgen;
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub enum CertificateIdentity {
29 Dns(Domain),
30 Ip(IpAddr),
31}
32
33impl CertificateIdentity {
34 pub fn try_from_host(host: &Host) -> Result<Self, BoxError> {
36 if let Ok(ip) = host.try_as_ip() {
37 return Ok(Self::Ip(ip));
38 }
39
40 let domain = host.try_as_domain()?;
41 Ok(domain
42 .as_str()
43 .parse()
44 .map_or_else(|_| Self::Dns(domain.into_owned()), Self::Ip))
45 }
46}
47
48impl From<Domain> for CertificateIdentity {
49 fn from(domain: Domain) -> Self {
50 domain
51 .as_str()
52 .parse()
53 .map_or_else(|_| Self::Dns(domain), Self::Ip)
54 }
55}
56
57impl From<IpAddr> for CertificateIdentity {
58 fn from(ip: IpAddr) -> Self {
59 Self::Ip(ip)
60 }
61}
62
63impl TryFrom<&Host> for CertificateIdentity {
64 type Error = BoxError;
65
66 fn try_from(host: &Host) -> Result<Self, Self::Error> {
67 Self::try_from_host(host)
68 }
69}
70
71impl TryFrom<Host> for CertificateIdentity {
72 type Error = BoxError;
73
74 fn try_from(host: Host) -> Result<Self, Self::Error> {
75 Self::try_from_host(&host)
76 }
77}
78
79#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
81pub struct CertificateSubject {
82 pub organisation_name: Option<String>,
83 pub common_name: Option<String>,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88pub struct CertificateValidity {
89 pub lifetime: Duration,
91 pub not_before_skew: Duration,
93}
94
95impl CertificateValidity {
96 #[must_use]
97 pub const fn new(lifetime: Duration, not_before_skew: Duration) -> Self {
98 Self {
99 lifetime,
100 not_before_skew,
101 }
102 }
103}
104
105impl Default for CertificateValidity {
106 fn default() -> Self {
107 Self::new(Duration::from_hours(90 * 24), Duration::from_mins(1))
108 }
109}
110
111#[cfg(any(feature = "boring", feature = "aws-lc", feature = "ring"))]
112pub(super) fn validate_certificate_lifetime(lifetime: Duration) -> Result<(), BoxError> {
113 if lifetime < Duration::from_secs(1) {
114 return Err(BoxError::from_static_str(
115 "certificate lifetime must be at least one second",
116 ));
117 }
118 Ok(())
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
129pub enum CertificateKeyKind {
130 Rsa2048,
132 Rsa4096,
134 #[default]
136 EcP256,
137 EcP384,
139 EcP521,
141 Ed25519,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct SelfSignedCaConfig {
148 pub subject: CertificateSubject,
149 pub validity: CertificateValidity,
150 pub key_kind: CertificateKeyKind,
151}
152
153impl Default for SelfSignedCaConfig {
154 fn default() -> Self {
155 Self {
156 subject: CertificateSubject::default(),
157 validity: CertificateValidity::new(
158 Duration::from_hours(365 * 20 * 24),
159 Duration::from_mins(1),
160 ),
161 key_kind: CertificateKeyKind::default(),
162 }
163 }
164}
165
166#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
168pub struct LeafCertConfig {
169 pub subject: CertificateSubject,
170 pub validity: CertificateValidity,
171 pub key_kind: CertificateKeyKind,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct LeafCertRequest {
177 pub config: LeafCertConfig,
178 pub identities: Vec<CertificateIdentity>,
179}
180
181impl Default for LeafCertRequest {
182 fn default() -> Self {
183 Self {
184 config: LeafCertConfig::default(),
185 identities: vec![CertificateIdentity::Dns(Domain::from_static("localhost"))],
186 }
187 }
188}
189
190impl LeafCertRequest {
191 #[must_use]
192 pub fn new(identity: impl Into<CertificateIdentity>) -> Self {
193 Self {
194 config: LeafCertConfig::default(),
195 identities: vec![identity.into()],
196 }
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub enum GeneratedServerAuthConfig {
203 SelfSignedLeaf(LeafCertRequest),
205 GeneratedCa {
207 ca: SelfSignedCaConfig,
208 leaf: LeafCertRequest,
209 },
210}
211
212impl Default for GeneratedServerAuthConfig {
213 fn default() -> Self {
214 Self::GeneratedCa {
215 ca: SelfSignedCaConfig::default(),
216 leaf: LeafCertRequest::default(),
217 }
218 }
219}
220
221impl GeneratedServerAuthConfig {
222 #[must_use]
223 pub fn generated_ca_for(identity: impl Into<CertificateIdentity>) -> Self {
224 Self::GeneratedCa {
225 ca: SelfSignedCaConfig::default(),
226 leaf: LeafCertRequest::new(identity),
227 }
228 }
229}
230
231#[derive(Debug)]
233pub struct CertificateAuthorityData {
234 certificate_chain: Vec<CertificateDer<'static>>,
236 private_key: PrivateKeyDer<'static>,
237}
238
239impl CertificateAuthorityData {
240 pub fn try_new(
241 certificate_chain: Vec<CertificateDer<'static>>,
242 private_key: PrivateKeyDer<'static>,
243 ) -> Result<Self, BoxError> {
244 if certificate_chain.is_empty() {
245 return Err(BoxError::from_static_str(
246 "certificate authority chain cannot be empty",
247 ));
248 }
249 validate_certificate_authority_key(&certificate_chain[0], &private_key)?;
250 for (index, certificate) in certificate_chain.iter().enumerate() {
251 use rama_core::error::ErrorContext as _;
252 let (_, parsed) = x509_parser::parse_x509_certificate(certificate.as_ref())
253 .context("parse certificate authority chain")?;
254 let is_ca = parsed
255 .basic_constraints()
256 .context("parse CA basic constraints")?
257 .is_some_and(|extension| extension.value.ca);
258 if !is_ca {
259 return Err(BoxError::from_static_str(
260 "certificate authority chain certificate must have CA basic constraints",
261 ));
262 }
263 let key_usage = parsed.key_usage().context("parse CA key usage")?;
264 if key_usage.is_some_and(|extension| !extension.value.key_cert_sign()) {
265 return Err(BoxError::from_static_str(
266 "certificate authority chain certificate must permit certificate signing",
267 ));
268 }
269 if let Some(parent) = certificate_chain.get(index + 1) {
270 let (_, parent) = x509_parser::parse_x509_certificate(parent.as_ref())
271 .context("parse parent certificate authority")?;
272 if parsed.issuer() != parent.subject() {
273 return Err(BoxError::from_static_str(
274 "certificate authority chain is not ordered issuer-first",
275 ));
276 }
277 }
278 }
279 validate_certificate_authority_chain(&certificate_chain)?;
280 Ok(Self {
281 certificate_chain,
282 private_key,
283 })
284 }
285
286 pub fn generate(config: SelfSignedCaConfig) -> Result<Self, BoxError> {
287 generate_certificate_authority(config)
288 }
289
290 #[must_use]
291 pub fn certificate_chain(&self) -> &[CertificateDer<'static>] {
292 &self.certificate_chain
293 }
294
295 #[must_use]
296 pub fn private_key(&self) -> &PrivateKeyDer<'static> {
297 &self.private_key
298 }
299
300 #[must_use]
301 pub fn into_parts(self) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
302 (self.certificate_chain, self.private_key)
303 }
304
305 pub fn issue_leaf(
306 &self,
307 request: LeafCertRequest,
308 ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
309 issue_certificate_authority_leaf(self, request)
310 }
311}
312
313#[cfg(feature = "boring")]
314fn validate_certificate_authority_key(
315 certificate: &CertificateDer<'_>,
316 private_key: &PrivateKeyDer<'_>,
317) -> Result<(), BoxError> {
318 boring::validate_certificate_authority_key(certificate, private_key)
319}
320
321#[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
322fn validate_certificate_authority_key(
323 certificate: &CertificateDer<'_>,
324 private_key: &PrivateKeyDer<'_>,
325) -> Result<(), BoxError> {
326 rcgen::validate_certificate_authority_key(certificate, private_key)
327}
328
329#[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
330fn validate_certificate_authority_key(
331 _certificate: &CertificateDer<'_>,
332 _private_key: &PrivateKeyDer<'_>,
333) -> Result<(), BoxError> {
334 Err(BoxError::from_static_str(
335 "enable a rama-crypto cert provider to validate a certificate authority key",
336 ))
337}
338
339#[cfg(feature = "boring")]
340fn validate_certificate_authority_chain(
341 certificate_chain: &[CertificateDer<'_>],
342) -> Result<(), BoxError> {
343 boring::validate_certificate_authority_chain(certificate_chain)
344}
345
346#[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
347fn validate_certificate_authority_chain(
348 certificate_chain: &[CertificateDer<'_>],
349) -> Result<(), BoxError> {
350 rcgen::validate_certificate_authority_chain(certificate_chain)
351}
352
353#[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
354fn validate_certificate_authority_chain(
355 certificate_chain: &[CertificateDer<'_>],
356) -> Result<(), BoxError> {
357 if certificate_chain.len() > 1 {
358 return Err(BoxError::from_static_str(
359 "enable a rama-crypto cert provider to validate a certificate authority chain",
360 ));
361 }
362 Ok(())
363}
364
365impl Clone for CertificateAuthorityData {
366 fn clone(&self) -> Self {
367 Self {
368 certificate_chain: self.certificate_chain.clone(),
369 private_key: self.private_key.clone_key(),
370 }
371 }
372}
373
374pub fn spki_sha256(certificate: &CertificateDer<'_>) -> Result<[u8; 32], BoxError> {
379 use rama_core::error::ErrorContext as _;
380 use sha2::Digest as _;
381 use x509_parser::prelude::FromDer as _;
382
383 let (_, cert) = x509_parser::certificate::X509Certificate::from_der(certificate.as_ref())
384 .context("parse x509 certificate for spki digest")?;
385 Ok(sha2::Sha256::digest(cert.public_key().raw).into())
386}
387
388#[cfg(feature = "boring")]
389pub fn generate_server_auth(
390 config: GeneratedServerAuthConfig,
391) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
392 boring::generate_server_auth(config)
393}
394
395#[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
396pub fn generate_server_auth(
397 config: GeneratedServerAuthConfig,
398) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
399 rcgen::generate_server_auth(config)
400}
401
402#[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
403pub fn generate_server_auth(
404 _config: GeneratedServerAuthConfig,
405) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
406 use rama_core::error::BoxErrorExt;
407
408 Err(BoxError::from_static_str(
409 "enable one of the rama-crypto cert providers (boring, aws-lc, ring) to generate server auth",
410 ))
411}
412
413#[cfg(feature = "boring")]
414pub fn generate_certificate_authority(
415 config: SelfSignedCaConfig,
416) -> Result<CertificateAuthorityData, BoxError> {
417 boring::generate_certificate_authority(config)
418}
419
420#[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
421pub fn generate_certificate_authority(
422 config: SelfSignedCaConfig,
423) -> Result<CertificateAuthorityData, BoxError> {
424 rcgen::generate_certificate_authority(config)
425}
426
427#[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
428pub fn generate_certificate_authority(
429 _config: SelfSignedCaConfig,
430) -> Result<CertificateAuthorityData, BoxError> {
431 Err(BoxError::from_static_str(
432 "enable one of the rama-crypto cert providers to generate a certificate authority",
433 ))
434}
435
436#[cfg(feature = "boring")]
437pub fn issue_certificate_authority_leaf(
438 ca: &CertificateAuthorityData,
439 request: LeafCertRequest,
440) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
441 boring::issue_certificate_authority_leaf(ca, request)
442}
443
444#[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
445pub fn issue_certificate_authority_leaf(
446 ca: &CertificateAuthorityData,
447 request: LeafCertRequest,
448) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
449 rcgen::issue_certificate_authority_leaf(ca, request)
450}
451
452#[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
453pub fn issue_certificate_authority_leaf(
454 _ca: &CertificateAuthorityData,
455 _request: LeafCertRequest,
456) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), BoxError> {
457 Err(BoxError::from_static_str(
458 "enable one of the rama-crypto cert providers to issue a leaf certificate",
459 ))
460}
461
462#[cfg(test)]
463mod spki_tests {
464 use super::*;
465 use base64::{Engine as _, prelude::BASE64_STANDARD};
466
467 const EXAMPLE_COM_CRT_B64: &str = "MIIE3DCCAsSgAwIBAgIJAN4TBpLFs4VhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTI0MTIwOTIwMDUxN1oXDTM0MTIwNzIwMDUxN1owFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC14A6yHqrF+5+VPljtBd9vgjTQxBCqQ7Af/7cNlFtZjOKmXz0bOCfZRjaxNNjjveztFH+VhRpH/JyM7Qd7R0FX84IyH4Z9a58jgKW/l/YM1Q4Y50WGpM9Sk5p9Q8xTWIoZPrjvh6zV4PKef87LxxqoO9QXv34d5g7dsQLbSwJ93SeggH0E5e1VvP1DW0kvu1BF6rsmF5eTyK/VNg/el9mGyMbcyhBKTpTyVT2FQYRFuZtHXHRnAocCdv887c/TsYVDffTwv7peVoOotO0twKn0SMdtybiNJyDEdcgw2bFbQu7oV/95cBurpxePzED31E64QI8emTvZ62L/c5QvP0OY3x2CSb5ctd6z7wWTJ8wkl7N8+y7Xgn1aAAfki4rWk5qfWAO3BNZo/TGyiWeoNttJ+NddfwI3+h6phK7X56vRhYSqwSnxWyQYlTJAnFQb7TMEP/k9ov2S9MzLTURLLeNiiXjvkOxi+12HzhlTNgk3X49y9f8PLxkNw37TghunAl4OvA+LdslSayFMmZbx9fm+6ZGkjcsDYnf1Mff+aoCkkUcAFg5DQFDkmvu08mJL2D+9I9OK/Yvn/qXWjhVdWLJ/k5hrQmLIs1KbQtlGvvYeC7kHY3yBK+3wt/Cnx9qwhOPJcufuEChiMcVcseGAZJhUT7gQM22v9jb9QZfhihGMpQIDAQABoy0wKzApBgNVHREEIjAgggtleGFtcGxlLmNvbYILZXhhbXBsZS5jb22HBAoAAAEwDQYJKoZIhvcNAQELBQADggIBAJBG9KcH0FG7xn2u4SA4nlwaP/v2ZWZlOwjVjHEQJF7AGaEZFVofzLoRncVnQs14Xr3SGstIBG/P30LC4zHO4Lhz0M+g/lbXhrDjTJLNX7ZNv2ZJj+6XBysJK2IuZX14YCtxhwFCuPBK1cxPDkP4nZm4u5tozLHPtZEHc4kGVQflurkTVmfhJMi5ndAOevXVgfAHRbHfh6x1kNZWDpybiPeeBvZOjRoxecsD7LA54knsSFCQe6zQRlfBUUD+RDI/ggDi3XnKdDHEkLZCH3/db4CcneyzzVkaNcvpOS6ZT6akDLmR8qAglTrADdsnNVzyWzNbBhXQEFoygY3F2rVQndTLoEFGMx7U2d3Fz8sVN/F2SzBYxtrwgj5rQC8tOhHZPVgQLXu6NRRZHEQgypDtGP0H4SUNcGb1Lw27E43KSIT9CpY8Z3SG34G4bYGfpdMN3wtoXG7BtrdmInNWiT+ygh+iJCSaSsAWtaPRnx/9uGLwUNVjzVxJhxGKBbf1hJ5g1x3zMeL73wrsiY6RBa6tWx9SHbRoq8htbkQAnP0tMOavGiTApFquBYDe2gYbuq5jh4yTbNyuxR4WW6m6Bvj7YhUREXQnTDonUwHzw2P29T95z52aPb5PaZYHgg4S26zRV+/Dc8E3oLkjgCyaDuQO4uUpmtT8ssTolIFNr2QUzD12";
468
469 #[test]
470 fn spki_sha256_matches_openssl_pin() {
471 let der = BASE64_STANDARD.decode(EXAMPLE_COM_CRT_B64).unwrap();
472 let digest = spki_sha256(&CertificateDer::from(der)).unwrap();
473 assert_eq!(
474 BASE64_STANDARD.encode(digest),
475 "xg6kqyS+uaJikboVvZPxNOYXMD3XPakJAakHSfGau/M="
476 );
477 }
478
479 #[test]
480 fn spki_sha256_rejects_invalid_der() {
481 spki_sha256(&CertificateDer::from(vec![1, 2, 3])).unwrap_err();
482 }
483
484 #[cfg(not(any(feature = "boring", feature = "aws-lc", feature = "ring")))]
485 #[test]
486 fn certificate_authority_key_validation_requires_a_provider() {
487 use crate::pki_types::PrivatePkcs8KeyDer;
488
489 let error = CertificateAuthorityData::try_new(
490 vec![CertificateDer::from(vec![1])],
491 PrivatePkcs8KeyDer::from(vec![1]).into(),
492 )
493 .expect_err("missing provider must fail before accepting CA data");
494 assert_eq!(
495 error.to_string(),
496 "enable a rama-crypto cert provider to validate a certificate authority key"
497 );
498 }
499}
500
501#[cfg(all(test, any(feature = "boring", feature = "aws-lc", feature = "ring")))]
502mod tests {
503 use super::*;
504 use x509_parser::prelude::*;
505
506 #[expect(clippy::expect_used)]
507 fn parse_certificate<'a>(der: &'a CertificateDer<'_>) -> X509Certificate<'a> {
508 X509Certificate::from_der(der.as_ref())
509 .expect("parse certificate DER")
510 .1
511 }
512
513 #[test]
514 fn genuine_self_signed_leaf_has_no_ca_certificate() {
515 let (chain, _key) = generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(
516 LeafCertRequest::new(Domain::from_static("self-signed.rama.test")),
517 ))
518 .expect("generate self-signed leaf");
519 assert_eq!(chain.len(), 1);
520 let leaf = parse_certificate(&chain[0]);
521 assert_eq!(leaf.subject(), leaf.issuer());
522 assert!(leaf.issuer().iter().next().is_some());
523 assert_eq!(leaf.subject().iter_common_name().count(), 0);
524 assert_eq!(
525 leaf.subject()
526 .iter_organization()
527 .next()
528 .and_then(|entry| entry.as_str().ok()),
529 Some("Anonymous")
530 );
531 assert!(
532 !leaf
533 .basic_constraints()
534 .expect("basic constraints")
535 .is_some_and(|extension| extension.value.ca)
536 );
537 }
538
539 #[test]
540 fn ca_issued_leaf_has_end_entity_and_key_identifier_extensions() {
541 let (chain, _key) = generate_server_auth(GeneratedServerAuthConfig::default())
542 .expect("generate CA-issued leaf");
543 let leaf = parse_certificate(&chain[0]);
544 let basic_constraints = leaf
545 .basic_constraints()
546 .expect("parse basic constraints")
547 .expect("basic constraints present");
548 assert!(!basic_constraints.value.ca);
549 assert!(leaf.extensions().iter().any(|extension| {
550 matches!(
551 extension.parsed_extension(),
552 ParsedExtension::SubjectKeyIdentifier(_)
553 )
554 }));
555 assert!(leaf.extensions().iter().any(|extension| {
556 matches!(
557 extension.parsed_extension(),
558 ParsedExtension::AuthorityKeyIdentifier(authority)
559 if authority.key_identifier.is_some()
560 )
561 }));
562 }
563
564 #[test]
565 fn self_signed_leaf_rejects_subsecond_lifetime() {
566 let error =
567 generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(LeafCertRequest {
568 config: LeafCertConfig {
569 validity: CertificateValidity::new(Duration::from_millis(999), Duration::ZERO),
570 ..Default::default()
571 },
572 ..Default::default()
573 }))
574 .expect_err("subsecond leaf lifetime must fail");
575 assert!(
576 error
577 .to_string()
578 .contains("certificate lifetime must be at least one second")
579 );
580 }
581
582 #[test]
583 fn self_signed_leaf_accepts_one_second_lifetime() {
584 generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(LeafCertRequest {
585 config: LeafCertConfig {
586 validity: CertificateValidity::new(Duration::from_secs(1), Duration::ZERO),
587 ..Default::default()
588 },
589 ..Default::default()
590 }))
591 .expect("one-second leaf lifetime must be accepted");
592 }
593
594 #[test]
595 fn certificate_authority_rejects_subsecond_lifetime() {
596 let error = CertificateAuthorityData::generate(SelfSignedCaConfig {
597 validity: CertificateValidity::new(Duration::from_millis(999), Duration::ZERO),
598 ..Default::default()
599 })
600 .expect_err("subsecond CA lifetime must fail");
601 assert!(
602 error
603 .to_string()
604 .contains("certificate lifetime must be at least one second")
605 );
606 }
607
608 #[test]
609 fn certificate_authority_rejects_subsecond_leaf_lifetime() {
610 let ca =
611 CertificateAuthorityData::generate(SelfSignedCaConfig::default()).expect("generate CA");
612 let error = ca
613 .issue_leaf(LeafCertRequest {
614 config: LeafCertConfig {
615 validity: CertificateValidity::new(Duration::from_millis(999), Duration::ZERO),
616 ..Default::default()
617 },
618 ..Default::default()
619 })
620 .expect_err("subsecond issued leaf lifetime must fail");
621 assert!(
622 error
623 .to_string()
624 .contains("certificate lifetime must be at least one second")
625 );
626 }
627
628 #[test]
629 fn certificate_authority_rejects_out_of_range_lifetime() {
630 let result = CertificateAuthorityData::generate(SelfSignedCaConfig {
631 validity: CertificateValidity::new(
632 Duration::from_hours(8_000 * 365 * 24),
633 Duration::ZERO,
634 ),
635 ..Default::default()
636 });
637 assert!(result.is_err(), "out-of-range CA lifetime must fail");
638 }
639
640 #[test]
641 fn certificate_authority_clamps_out_of_range_skew_to_epoch() {
642 for skew in [Duration::from_hours(20_000 * 365 * 24), Duration::MAX] {
643 let ca = CertificateAuthorityData::generate(SelfSignedCaConfig {
644 validity: CertificateValidity::new(Duration::from_hours(24), skew),
645 ..Default::default()
646 })
647 .expect("out-of-range skew must clamp to the Unix epoch");
648 let issuer = parse_certificate(&ca.certificate_chain[0]);
649 assert_eq!(issuer.validity().not_before.timestamp(), 0);
650 }
651 }
652
653 #[test]
654 fn generated_ca_for_sets_only_the_requested_identity() {
655 let identity = CertificateIdentity::Ip(std::net::Ipv6Addr::LOCALHOST.into());
656 let config = GeneratedServerAuthConfig::generated_ca_for(identity.clone());
657 assert!(matches!(
658 config,
659 GeneratedServerAuthConfig::GeneratedCa {
660 leaf: LeafCertRequest { identities, .. },
661 ..
662 } if identities == [identity]
663 ));
664 }
665
666 #[test]
667 fn explicit_common_names_do_not_add_anonymous_organisations() {
668 let (chain, _key) = generate_server_auth(GeneratedServerAuthConfig::GeneratedCa {
669 ca: SelfSignedCaConfig {
670 subject: CertificateSubject {
671 common_name: Some("Named CA".to_owned()),
672 ..Default::default()
673 },
674 ..Default::default()
675 },
676 leaf: LeafCertRequest {
677 config: LeafCertConfig {
678 subject: CertificateSubject {
679 common_name: Some("Named leaf".to_owned()),
680 ..Default::default()
681 },
682 ..Default::default()
683 },
684 ..Default::default()
685 },
686 })
687 .expect("generate named chain");
688
689 for certificate in &chain {
690 let certificate = parse_certificate(certificate);
691 let subject = certificate.subject();
692 assert_eq!(subject.iter_organization().count(), 0);
693 }
694 }
695
696 #[test]
697 fn self_signed_leaf_common_name_does_not_add_anonymous_organisation() {
698 let (chain, _key) =
699 generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(LeafCertRequest {
700 config: LeafCertConfig {
701 subject: CertificateSubject {
702 common_name: Some("Named self-signed leaf".to_owned()),
703 ..Default::default()
704 },
705 ..Default::default()
706 },
707 ..Default::default()
708 }))
709 .expect("generate named self-signed leaf");
710 let leaf = parse_certificate(&chain[0]);
711 assert_eq!(leaf.subject().iter_organization().count(), 0);
712 }
713
714 #[test]
715 fn issued_leaf_validity_is_clamped_to_issuing_ca() {
716 let ca = CertificateAuthorityData::generate(SelfSignedCaConfig {
717 validity: CertificateValidity::new(Duration::from_hours(2), Duration::from_mins(1)),
718 ..Default::default()
719 })
720 .expect("generate short-lived CA");
721 let (chain, _key) = ca
722 .issue_leaf(LeafCertRequest {
723 config: LeafCertConfig {
724 validity: CertificateValidity::new(
725 Duration::from_hours(4),
726 Duration::from_mins(2),
727 ),
728 ..Default::default()
729 },
730 ..Default::default()
731 })
732 .expect("issue validity-clamped leaf");
733 let leaf = parse_certificate(&chain[0]);
734 let issuer = parse_certificate(&chain[1]);
735 assert_eq!(leaf.validity().not_before, issuer.validity().not_before);
736 assert_eq!(leaf.validity().not_after, issuer.validity().not_after);
737 }
738
739 #[cfg(feature = "boring")]
740 #[test]
741 fn boring_rsa_leaf_permits_key_encipherment() {
742 let (chain, _key) =
743 generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(LeafCertRequest {
744 config: LeafCertConfig {
745 key_kind: CertificateKeyKind::Rsa2048,
746 ..Default::default()
747 },
748 ..Default::default()
749 }))
750 .expect("generate RSA leaf");
751 let leaf = parse_certificate(&chain[0]);
752 let usage = leaf
753 .key_usage()
754 .expect("parse key usage")
755 .expect("key usage present");
756 assert!(usage.value.digital_signature());
757 assert!(usage.value.key_encipherment());
758 }
759
760 #[test]
761 fn existing_ca_issues_configured_ip_leaf_in_one_call() {
762 let mut ca_validity = SelfSignedCaConfig::default().validity;
763 ca_validity.not_before_skew = Duration::from_mins(2);
764 let ca = CertificateAuthorityData::generate(SelfSignedCaConfig {
765 subject: CertificateSubject {
766 organisation_name: Some("Rama Issuer".to_owned()),
767 common_name: Some("Rama test CA".to_owned()),
768 },
769 validity: ca_validity,
770 ..Default::default()
771 })
772 .expect("generate CA");
773 let lifetime = Duration::from_hours(365 * 24);
774 let (chain, _key) = ca
775 .issue_leaf(LeafCertRequest {
776 config: LeafCertConfig {
777 subject: CertificateSubject {
778 common_name: Some("descriptive leaf label".to_owned()),
779 ..Default::default()
780 },
781 validity: CertificateValidity::new(lifetime, Duration::from_mins(2)),
782 key_kind: CertificateKeyKind::EcP384,
783 },
784 identities: vec![CertificateIdentity::Ip(
785 std::net::Ipv4Addr::LOCALHOST.into(),
786 )],
787 })
788 .expect("issue leaf");
789
790 assert_eq!(chain.len(), ca.certificate_chain.len() + 1);
791 let leaf = parse_certificate(&chain[0]);
792 let issuer = parse_certificate(&chain[1]);
793 assert_eq!(leaf.issuer(), issuer.subject());
794 assert_eq!(
795 leaf.validity().not_after.timestamp() - leaf.validity().not_before.timestamp(),
796 i64::try_from(lifetime.as_secs()).unwrap()
797 );
798 assert_eq!(
799 leaf.subject()
800 .iter_organization()
801 .next()
802 .and_then(|entry| entry.as_str().ok()),
803 Some("Rama Issuer")
804 );
805 let san = leaf
806 .subject_alternative_name()
807 .expect("parse SAN")
808 .expect("SAN present");
809 assert!(san.value.general_names.iter().any(|name| {
810 matches!(name, GeneralName::IPAddress(bytes) if *bytes == [127, 0, 0, 1])
811 }));
812 }
813
814 #[test]
815 fn certificate_authority_rejects_mismatched_private_key() {
816 let first = CertificateAuthorityData::generate(SelfSignedCaConfig::default())
817 .expect("generate first CA");
818 let second = CertificateAuthorityData::generate(SelfSignedCaConfig::default())
819 .expect("generate second CA");
820 CertificateAuthorityData::try_new(first.certificate_chain, second.private_key)
821 .expect_err("mismatched key must fail");
822 }
823
824 #[test]
825 fn certificate_authority_rejects_same_name_unrelated_parent() {
826 let config = SelfSignedCaConfig {
827 subject: CertificateSubject {
828 organisation_name: Some("Same Name Issuer".to_owned()),
829 common_name: None,
830 },
831 ..Default::default()
832 };
833 let child = CertificateAuthorityData::generate(config.clone()).expect("generate child CA");
834 let unrelated_parent =
835 CertificateAuthorityData::generate(config).expect("generate unrelated parent CA");
836 let (mut chain, key) = child.into_parts();
837 chain.push(unrelated_parent.certificate_chain()[0].clone());
838
839 CertificateAuthorityData::try_new(chain, key)
840 .expect_err("same-name parent with the wrong key must fail");
841 }
842
843 #[test]
844 fn certificate_authority_rejects_non_ca_parent() {
845 let subject = CertificateSubject {
846 organisation_name: Some("Same Name Issuer".to_owned()),
847 common_name: None,
848 };
849 let child = CertificateAuthorityData::generate(SelfSignedCaConfig {
850 subject: subject.clone(),
851 ..Default::default()
852 })
853 .expect("generate child CA");
854 let (parent_chain, _parent_key) =
855 generate_server_auth(GeneratedServerAuthConfig::SelfSignedLeaf(LeafCertRequest {
856 config: LeafCertConfig {
857 subject,
858 ..Default::default()
859 },
860 ..Default::default()
861 }))
862 .expect("generate non-CA parent");
863 let (mut chain, key) = child.into_parts();
864 chain.push(parent_chain[0].clone());
865
866 CertificateAuthorityData::try_new(chain, key).expect_err("non-CA parent must fail");
867 }
868
869 #[cfg(feature = "boring")]
870 #[test]
871 fn certificate_authority_accepts_valid_intermediate_chain() {
872 use crate::pki_types::PrivatePkcs8KeyDer;
873
874 let (root_cert, root_key) =
875 boring::generate_certificate_authority_x509(&SelfSignedCaConfig {
876 subject: CertificateSubject {
877 common_name: Some("Rama Root CA".to_owned()),
878 ..Default::default()
879 },
880 ..Default::default()
881 })
882 .expect("generate root CA");
883 let (intermediate_template, _) =
884 boring::generate_certificate_authority_x509(&SelfSignedCaConfig {
885 subject: CertificateSubject {
886 common_name: Some("Rama Intermediate CA".to_owned()),
887 ..Default::default()
888 },
889 ..Default::default()
890 })
891 .expect("generate intermediate CA template");
892 let (intermediate_cert, intermediate_key) = boring::self_signed_server_auth_mirror_cert(
893 intermediate_template.as_ref(),
894 &root_cert,
895 &root_key,
896 )
897 .expect("sign intermediate CA");
898
899 let chain = vec![
900 CertificateDer::from(
901 intermediate_cert
902 .to_der()
903 .expect("serialize intermediate CA"),
904 ),
905 CertificateDer::from(root_cert.to_der().expect("serialize root CA")),
906 ];
907 let key = PrivatePkcs8KeyDer::from(
908 intermediate_key
909 .private_key_to_der_pkcs8()
910 .expect("serialize intermediate CA key"),
911 )
912 .into();
913 let ca = CertificateAuthorityData::try_new(chain, key).expect("accept valid CA chain");
914 assert_eq!(ca.certificate_chain().len(), 2);
915 }
916
917 #[cfg(all(not(feature = "boring"), any(feature = "aws-lc", feature = "ring")))]
918 #[test]
919 fn rcgen_certificate_authority_accepts_valid_intermediate_chain() {
920 use crate::pki_types::PrivatePkcs8KeyDer;
921
922 fn ca_params(common_name: &str) -> ::rcgen::CertificateParams {
923 let mut params = ::rcgen::CertificateParams::new(Vec::new()).expect("CA parameters");
924 params.distinguished_name = ::rcgen::DistinguishedName::new();
925 params
926 .distinguished_name
927 .push(::rcgen::DnType::CommonName, common_name);
928 params.is_ca = ::rcgen::IsCa::Ca(::rcgen::BasicConstraints::Unconstrained);
929 params.key_usages = vec![
930 ::rcgen::KeyUsagePurpose::KeyCertSign,
931 ::rcgen::KeyUsagePurpose::CrlSign,
932 ];
933 params
934 }
935
936 let root_params = ca_params("Rama rcgen Root CA");
937 let root_key = ::rcgen::KeyPair::generate().expect("generate root key");
938 let root_cert = root_params
939 .self_signed(&root_key)
940 .expect("generate root certificate");
941 let root_issuer = ::rcgen::Issuer::new(root_params, root_key);
942
943 let intermediate_params = ca_params("Rama rcgen Intermediate CA");
944 let intermediate_key = ::rcgen::KeyPair::generate().expect("generate intermediate key");
945 let intermediate_cert = intermediate_params
946 .signed_by(&intermediate_key, &root_issuer)
947 .expect("generate intermediate certificate");
948 let intermediate_key_der = intermediate_key.serialize_der();
949
950 let ca = CertificateAuthorityData::try_new(
951 vec![intermediate_cert.into(), root_cert.into()],
952 PrivatePkcs8KeyDer::from(intermediate_key_der).into(),
953 )
954 .expect("accept valid rcgen CA chain");
955 assert_eq!(ca.certificate_chain().len(), 2);
956 }
957
958 #[test]
959 fn generated_leaf_san_covers_dns_names_and_ip_addresses() {
960 let leaf = LeafCertRequest {
961 config: LeafCertConfig {
962 subject: CertificateSubject {
963 common_name: Some("display label, not an identity".to_owned()),
964 ..Default::default()
965 },
966 ..Default::default()
967 },
968 identities: vec![
969 CertificateIdentity::Dns(Domain::from_static("primary.rama.test")),
970 CertificateIdentity::Dns(Domain::from_static("alt-one.rama.test")),
971 CertificateIdentity::Dns(Domain::from_static("alt-two.rama.test")),
972 CertificateIdentity::Ip(std::net::Ipv4Addr::LOCALHOST.into()),
973 CertificateIdentity::Ip(std::net::Ipv4Addr::new(127, 0, 0, 2).into()),
974 CertificateIdentity::Ip(std::net::Ipv6Addr::LOCALHOST.into()),
975 ],
976 };
977 let (chain, _key) = generate_server_auth(GeneratedServerAuthConfig::GeneratedCa {
978 ca: SelfSignedCaConfig::default(),
979 leaf,
980 })
981 .expect("generate server auth");
982
983 let (_, cert) =
984 X509Certificate::from_der(chain[0].as_ref()).expect("parse leaf certificate DER");
985 let common_names = cert
986 .subject()
987 .iter_common_name()
988 .filter_map(|entry| entry.as_str().ok())
989 .collect::<Vec<_>>();
990 assert_eq!(common_names, ["display label, not an identity"]);
991 let mut dns = Vec::new();
992 let mut ips = Vec::new();
993 for ext in cert.extensions() {
994 if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() {
995 for gn in &san.general_names {
996 match gn {
997 GeneralName::DNSName(name) => dns.push((*name).to_owned()),
998 GeneralName::IPAddress(ip) => ips.push(ip.to_vec()),
999 _ => {}
1000 }
1001 }
1002 }
1003 }
1004
1005 for expected in [
1006 "primary.rama.test",
1007 "alt-one.rama.test",
1008 "alt-two.rama.test",
1009 ] {
1010 assert!(
1011 dns.iter().any(|n| n == expected),
1012 "leaf SAN must contain {expected}; got {dns:?}"
1013 );
1014 }
1015 assert!(
1016 !dns.iter()
1017 .any(|name| name == "display label, not an identity"),
1018 "commonName must not be copied into SAN"
1019 );
1020 assert!(
1021 ips.contains(&vec![127, 0, 0, 1]),
1022 "missing IPv4 SAN: {ips:?}"
1023 );
1024 assert!(
1025 ips.contains(&vec![127, 0, 0, 2]),
1026 "numeric domain must be an IPv4 SAN: {ips:?}"
1027 );
1028 assert!(
1029 ips.contains(&std::net::Ipv6Addr::LOCALHOST.octets().to_vec()),
1030 "missing IPv6 SAN: {ips:?}"
1031 );
1032 }
1033}