xwt_cert_gen/
lib.rs

1//! Certificate generation facilities.
2//!
3//! See [`Params::self_signed`] for a usage example.
4
5pub use x509_cert;
6
7/// The params for certificate generation.
8#[allow(missing_docs)]
9#[derive(Debug)]
10pub struct Params<'a> {
11    pub common_name: &'a str,
12    pub subject_alt_names: &'a [&'a str],
13    pub valid_days_before: u32,
14    pub valid_days_after: u32,
15}
16
17impl<'a> Params<'a> {
18    /// Create new certificate generation params with the given common name
19    /// and subject alt names and the standard WebTransport self-signed
20    /// certificate two-week validity window.
21    pub fn new(common_name: &'a str, subject_alt_names: &'a [&'a str]) -> Self {
22        Self {
23            common_name,
24            subject_alt_names,
25            valid_days_before: 0,
26            valid_days_after: 14,
27        }
28    }
29}
30
31/// A duration of a single day.
32const DAY: std::time::Duration = std::time::Duration::from_secs(60 * 60 * 24);
33
34#[allow(missing_docs)]
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37    #[error("bad common name value: {0}")]
38    BadCommonName(der::Error),
39    #[error("bad valid days before value")]
40    BadValidDaysBefore,
41    #[error("bad valid days after value")]
42    BadValidDaysAfter,
43    #[error("bad subject alt name value")]
44    BadSubjectAltName,
45    #[error("bad key")]
46    BadKey,
47    #[error("unable to create a certificate builder")]
48    Builder(x509_cert::builder::Error),
49    #[error("unable to add a subject alt name extension")]
50    SubjectAltNameExtension(x509_cert::builder::Error),
51    #[error("unable to build the certificate")]
52    Build(x509_cert::builder::Error),
53}
54
55impl Params<'_> {
56    /// Generate a new self-signed certificate from this params and
57    /// a given key.
58    ///
59    /// A self signed certificate is a certificate containing the public
60    /// component of a key and signed by the same very key.
61    ///
62    /// You can use any key that works with [`signature`] and
63    /// [`x509_cert::spki`] crates.
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// let params = xwt_cert_gen::Params {
69    ///   common_name: "My certificate",
70    ///   subject_alt_names: &["localhost", "127.0.0.1", "::1"],
71    ///   valid_days_before: 0,
72    ///   valid_days_after: 14,
73    /// };
74    ///
75    /// // Using `p256` crate to create a NIST P-256 secret key.
76    /// let key = p256::SecretKey::random(&mut rand::thread_rng());
77    ///
78    /// // Prepare ECDSA/P-256 signing key.
79    /// let key = p256::ecdsa::SigningKey::from(key);
80    ///
81    /// // Create a new self-signed certificate - a certificate containing
82    /// // the public component of this key and signed by this same very key.
83    /// let cert = params
84    ///   .self_signed::<_, p256::ecdsa::DerSignature>(&key)
85    ///   .unwrap();
86    /// ```
87    pub fn self_signed<Key, Signature>(&self, key: &Key) -> Result<x509_cert::Certificate, Error>
88    where
89        Key: x509_cert::spki::DynSignatureAlgorithmIdentifier,
90        Key: signature::Keypair,
91        Key::VerifyingKey: x509_cert::spki::EncodePublicKey,
92        Key: signature::Signer<Signature>,
93        Signature: x509_cert::spki::SignatureBitStringEncoding,
94    {
95        use x509_cert::builder::Builder;
96
97        let now = std::time::SystemTime::now();
98
99        let subject: x509_cert::name::Name = format!("CN={}", self.common_name)
100            .parse()
101            .map_err(Error::BadCommonName)?;
102
103        let profile = x509_cert::builder::Profile::Leaf {
104            issuer: subject.clone(),
105            enable_key_agreement: true,
106            enable_key_encipherment: true,
107        };
108        let serial_number = x509_cert::serial_number::SerialNumber::from(1u8);
109        let validity = x509_cert::time::Validity {
110            not_before: (now - (self.valid_days_before * DAY))
111                .try_into()
112                .map_err(|_| Error::BadValidDaysBefore)?,
113            not_after: (now + (self.valid_days_after * DAY))
114                .try_into()
115                .map_err(|_| Error::BadValidDaysAfter)?,
116        };
117
118        let subject_public_key_info =
119            x509_cert::spki::SubjectPublicKeyInfoOwned::from_key(key.verifying_key())
120                .map_err(|_| Error::BadKey)?;
121
122        let mut builder = x509_cert::builder::CertificateBuilder::new(
123            profile,
124            serial_number,
125            validity,
126            subject,
127            subject_public_key_info,
128            key,
129        )
130        .map_err(Error::Builder)?;
131
132        let parse_subject_alt_name =
133            |s: &str| -> Result<x509_cert::ext::pkix::name::GeneralName, Error> {
134                Ok(match s.parse::<core::net::IpAddr>() {
135                    Ok(ip) => x509_cert::ext::pkix::name::GeneralName::from(ip),
136                    Err(_) => {
137                        let val =
138                            der::asn1::Ia5String::new(s).map_err(|_| Error::BadSubjectAltName)?;
139                        x509_cert::ext::pkix::name::GeneralName::DnsName(val)
140                    }
141                })
142            };
143
144        let subject_alt_names = self
145            .subject_alt_names
146            .iter()
147            .copied()
148            .map(parse_subject_alt_name)
149            .collect::<Result<_, _>>()?;
150
151        builder
152            .add_extension(&x509_cert::ext::pkix::SubjectAltName(subject_alt_names))
153            .map_err(Error::SubjectAltNameExtension)?;
154
155        let cert = builder.build().map_err(Error::Builder)?;
156        Ok(cert)
157    }
158}