Skip to main content

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}