sipx_testkit/certs.rs
1//! A certificate authority the tests control.
2//!
3//! Every certificate in the TLS and WSS tests is generated at run time from this, so nothing
4//! depends on a public CA, on a clock beyond the test's control, or on anyone else's expiry
5//! date. It lives here rather than in each test file because a fixture copied three times is
6//! three fixtures that can quietly stop meaning the same thing.
7
8// Panicking is the right failure here and the only useful one. These are fixtures: a test
9// whose certificate could not be generated has not found a bug, it has failed to start, and
10// threading a `Result` out of every fixture call would put error handling into every test for
11// a case that means the test harness is broken.
12#![allow(clippy::expect_used)]
13
14use rcgen::{
15 BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, Issuer, KeyPair, SanType,
16};
17
18/// A private CA, and the certificates it issues.
19#[derive(Debug)]
20pub struct Ca {
21 pem: String,
22 issuer: Issuer<'static, KeyPair>,
23}
24
25impl Ca {
26 /// A fresh authority, trusted by nobody until a test says so.
27 #[must_use]
28 pub fn new() -> Self {
29 Self::named("sipx test CA")
30 }
31
32 /// A fresh authority with a distinct subject name.
33 ///
34 /// Useful when a test needs two unrelated issuers: giving both authorities the same
35 /// distinguished name can make a verifier try the trusted authority's key and report a bad
36 /// signature instead of the unknown issuer the fixture meant to exercise.
37 #[must_use]
38 pub fn named(common_name: &str) -> Self {
39 let key = KeyPair::generate().expect("a key");
40 let mut params = CertificateParams::new(Vec::new()).expect("params");
41 params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
42 let mut name = DistinguishedName::new();
43 name.push(DnType::CommonName, common_name);
44 params.distinguished_name = name;
45
46 let certificate = params.clone().self_signed(&key).expect("a CA certificate");
47 Self {
48 pem: certificate.pem(),
49 issuer: Issuer::new(params, key),
50 }
51 }
52
53 /// The authority's own certificate, to be added as a trust anchor.
54 #[must_use]
55 pub fn pem(&self) -> String {
56 self.pem.clone()
57 }
58
59 /// Issue a certificate carrying these subject alternative names and this common name.
60 ///
61 /// The two are separate arguments on purpose: a certificate whose SAN and CN disagree is
62 /// the case RFC 6125 ยง6.4.4 exists for, and it cannot be constructed if they are one field.
63 #[must_use]
64 pub fn issue(&self, sans: &[SanType], common_name: &str) -> (String, String) {
65 let key = KeyPair::generate().expect("a key");
66 let mut params = CertificateParams::new(Vec::new()).expect("params");
67 params.subject_alt_names = sans.to_vec();
68 let mut name = DistinguishedName::new();
69 name.push(DnType::CommonName, common_name);
70 params.distinguished_name = name;
71
72 let signed = params
73 .signed_by(&key, &self.issuer)
74 .expect("a leaf certificate");
75 (signed.pem(), key.serialize_pem())
76 }
77
78 /// Issue a certificate for one DNS name, which is the common case.
79 #[must_use]
80 pub fn issue_for(&self, host: &str) -> (String, String) {
81 self.issue(&[dns(host)], host)
82 }
83}
84
85impl Default for Ca {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91/// A `dNSName` subject alternative name.
92#[must_use]
93pub fn dns(name: &str) -> SanType {
94 SanType::DnsName(name.try_into().expect("a DNS name"))
95}