Skip to main content

ssh_key/certificate/
builder.rs

1//! OpenSSH certificate builder.
2
3use super::{CertType, Certificate, Field, OptionsMap};
4use crate::{Comment, Result, Signature, SigningKey, public};
5use alloc::{string::String, vec::Vec};
6use core::fmt::{self, Debug, Formatter};
7
8#[cfg(feature = "rand_core")]
9use rand_core::CryptoRng;
10#[cfg(feature = "std")]
11use {super::UnixTime, std::time::SystemTime};
12
13#[cfg(doc)]
14use crate::{Error, PrivateKey};
15
16/// OpenSSH certificate builder.
17///
18/// This type provides the core functionality of an OpenSSH certificate
19/// authority.
20///
21/// It can build and sign OpenSSH certificates.
22///
23/// ## Principals
24///
25/// Certificates are valid for a specific set of principal names:
26///
27/// - Usernames for [`CertType::User`].
28/// - Hostnames for [`CertType::Host`].
29///
30/// When building a certificate, you will either need to specify principals
31/// by calling [`Builder::valid_principal`] one or more times, or explicitly
32/// marking a certificate as valid for all principals (i.e. "golden ticket")
33/// using the [`Builder::all_principals_valid`] method.
34///
35/// ## Example
36///
37#[cfg_attr(all(feature = "ed25519", feature = "getrandom"), doc = " ```")]
38#[cfg_attr(
39    not(all(feature = "ed25519", feature = "getrandom")),
40    doc = " ```ignore"
41)]
42/// # fn main() -> Result<(), ssh_key::Error> {
43/// use ssh_key::{Algorithm, PrivateKey, certificate, rand_core::{TryRngCore, OsRng}};
44/// use std::time::{SystemTime, UNIX_EPOCH};
45///
46/// // Generate the certificate authority's private key
47/// let ca_key = PrivateKey::random(&mut OsRng.unwrap_err(), Algorithm::Ed25519)?;
48///
49/// // Generate a "subject" key to be signed by the certificate authority.
50/// // Normally a user or host would do this locally and give the certificate
51/// // authority the public key.
52/// let subject_private_key = PrivateKey::random(&mut OsRng.unwrap_err(), Algorithm::Ed25519)?;
53/// let subject_public_key = subject_private_key.public_key();
54///
55/// // Create certificate validity window
56/// let valid_after = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
57/// let valid_before = valid_after + (365 * 86400); // e.g. 1 year
58///
59/// // Initialize certificate builder
60/// let mut cert_builder = certificate::Builder::new_with_random_nonce(
61///     &mut OsRng.unwrap_err(),
62///     subject_public_key,
63///     valid_after,
64///     valid_before,
65/// )?;
66/// cert_builder.serial(42)?; // Optional: serial number chosen by the CA
67/// cert_builder.key_id("nobody-cert-02")?; // Optional: CA-specific key identifier
68/// cert_builder.cert_type(certificate::CertType::User)?; // User or host certificate
69/// cert_builder.valid_principal("nobody")?; // Unix username or hostname
70/// cert_builder.comment("nobody@example.com")?; // Comment (typically an email address)
71///
72/// // Sign and return the `Certificate` for `subject_public_key`
73/// let cert = cert_builder.sign(&ca_key)?;
74/// # Ok(())
75/// # }
76/// ```
77pub struct Builder {
78    public_key: public::KeyData,
79    nonce: Vec<u8>,
80    serial: Option<u64>,
81    cert_type: Option<CertType>,
82    key_id: Option<String>,
83    valid_principals: Option<Vec<String>>,
84    valid_after: u64,
85    valid_before: u64,
86    critical_options: OptionsMap,
87    extensions: OptionsMap,
88    comment: Option<Comment>,
89}
90
91impl Builder {
92    /// Recommended size for a nonce.
93    pub const RECOMMENDED_NONCE_SIZE: usize = 16;
94
95    /// Create a new certificate builder for the given subject's public key.
96    ///
97    /// Also requires a nonce (random value typically 16 or 32 bytes long) and the validity window
98    /// of the certificate as Unix seconds.
99    ///
100    /// # Errors
101    /// Returns [`Error::CertificateFieldInvalid`] if `valid_before < valid_after`.
102    pub fn new(
103        nonce: impl Into<Vec<u8>>,
104        public_key: impl Into<public::KeyData>,
105        valid_after: u64,
106        valid_before: u64,
107    ) -> Result<Self> {
108        if valid_before < valid_after {
109            return Err(Field::ValidBefore.invalid_error());
110        }
111
112        Ok(Self {
113            nonce: nonce.into(),
114            public_key: public_key.into(),
115            serial: None,
116            cert_type: None,
117            key_id: None,
118            valid_principals: None,
119            valid_after,
120            valid_before,
121            critical_options: OptionsMap::new(),
122            extensions: OptionsMap::new(),
123            comment: None,
124        })
125    }
126
127    /// Create a new certificate builder with the validity window specified using [`SystemTime`]s.
128    ///
129    /// # Errors
130    /// Returns [`Error::CertificateFieldInvalid`] if `valid_before < valid_after`.
131    #[cfg(feature = "std")]
132    pub fn new_with_validity_times(
133        nonce: impl Into<Vec<u8>>,
134        public_key: impl Into<public::KeyData>,
135        valid_after: SystemTime,
136        valid_before: SystemTime,
137    ) -> Result<Self> {
138        let valid_after =
139            UnixTime::try_from(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
140
141        let valid_before =
142            UnixTime::try_from(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
143
144        Self::new(nonce, public_key, valid_after.into(), valid_before.into())
145    }
146
147    /// Create a new certificate builder, generating a random nonce using the provided random number
148    /// generator.
149    ///
150    /// # Errors
151    /// Returns [`Error::CertificateFieldInvalid`] if `valid_before < valid_after`.
152    #[cfg(feature = "rand_core")]
153    pub fn new_with_random_nonce(
154        rng: &mut impl CryptoRng,
155        public_key: impl Into<public::KeyData>,
156        valid_after: u64,
157        valid_before: u64,
158    ) -> Result<Self> {
159        let mut nonce = vec![0u8; Self::RECOMMENDED_NONCE_SIZE];
160        rng.fill_bytes(&mut nonce);
161        Self::new(nonce, public_key, valid_after, valid_before)
162    }
163
164    /// Set certificate serial number.
165    ///
166    /// Default: `0`.
167    ///
168    /// # Errors
169    /// Returns [`Error::CertificateFieldInvalid`] if called multiple times.
170    pub fn serial(&mut self, serial: u64) -> Result<&mut Self> {
171        if self.serial.is_some() {
172            return Err(Field::Serial.invalid_error());
173        }
174
175        self.serial = Some(serial);
176        Ok(self)
177    }
178
179    /// Set certificate type: user or host.
180    ///
181    /// Default: [`CertType::User`].
182    ///
183    /// # Errors
184    /// Returns [`Error::CertificateFieldInvalid`] if called multiple times.
185    pub fn cert_type(&mut self, cert_type: CertType) -> Result<&mut Self> {
186        if self.cert_type.is_some() {
187            return Err(Field::Type.invalid_error());
188        }
189
190        self.cert_type = Some(cert_type);
191        Ok(self)
192    }
193
194    /// Set key ID: label to identify this particular certificate.
195    ///
196    /// Default `""`
197    ///
198    /// # Errors
199    /// Returns [`Error::CertificateFieldInvalid`] if called multiple times.
200    pub fn key_id(&mut self, key_id: impl Into<String>) -> Result<&mut Self> {
201        if self.key_id.is_some() {
202            return Err(Field::KeyId.invalid_error());
203        }
204
205        self.key_id = Some(key_id.into());
206        Ok(self)
207    }
208
209    /// Add a principal (i.e. username or hostname) to `valid_principals`.
210    ///
211    /// # Errors
212    /// Currently does not return errors.
213    pub fn valid_principal(&mut self, principal: impl Into<String>) -> Result<&mut Self> {
214        match &mut self.valid_principals {
215            Some(principals) => principals.push(principal.into()),
216            None => self.valid_principals = Some(vec![principal.into()]),
217        }
218
219        Ok(self)
220    }
221
222    /// Mark this certificate as being valid for all principals.
223    ///
224    /// <div class="warning">
225    /// <b>Security Warning</b>
226    ///
227    /// Use this method with care! It generates "golden ticket" certificates which can e.g.
228    /// authenticate as any user on a system, or impersonate any host.
229    /// </div>
230    ///
231    /// # Errors
232    /// Currently does not return errors.
233    pub fn all_principals_valid(&mut self) -> Result<&mut Self> {
234        self.valid_principals = Some(Vec::new());
235        Ok(self)
236    }
237
238    /// Add a critical option to this certificate.
239    ///
240    /// Critical options must be recognized or the certificate must be rejected.
241    ///
242    /// # Errors
243    /// Returns [`Error::CertificateFieldInvalid`] if `name` is already configured as a critical
244    /// option.
245    pub fn critical_option(
246        &mut self,
247        name: impl Into<String>,
248        data: impl Into<String>,
249    ) -> Result<&mut Self> {
250        let name = name.into();
251        let data = data.into();
252
253        if self.critical_options.contains_key(&name) {
254            return Err(Field::CriticalOptions.invalid_error());
255        }
256
257        self.critical_options.insert(name, data);
258        Ok(self)
259    }
260
261    /// Add an extension to this certificate.
262    ///
263    /// Extensions can be unrecognized without impacting the certificate.
264    ///
265    /// # Errors
266    /// Returns [`Error::CertificateFieldInvalid`] if `name` is already configured as an extension.
267    pub fn extension(
268        &mut self,
269        name: impl Into<String>,
270        data: impl Into<String>,
271    ) -> Result<&mut Self> {
272        let name = name.into();
273        let data = data.into();
274
275        if self.extensions.contains_key(&name) {
276            return Err(Field::Extensions.invalid_error());
277        }
278
279        self.extensions.insert(name, data);
280        Ok(self)
281    }
282
283    /// Add a comment to this certificate.
284    ///
285    /// Default `""`
286    ///
287    /// # Errors
288    /// Returns [`Error::CertificateFieldInvalid`] if called multiple times.
289    pub fn comment(&mut self, comment: impl Into<Comment>) -> Result<&mut Self> {
290        if self.comment.is_some() {
291            return Err(Field::Comment.invalid_error());
292        }
293
294        self.comment = Some(comment.into());
295        Ok(self)
296    }
297
298    /// Sign the certificate using the provided signer type.
299    ///
300    /// The [`PrivateKey`] type can be used as a signer.
301    ///
302    /// # Errors
303    /// - Returns [`Error::CertificateFieldInvalid`] if any fields are invalid.
304    /// - Returns [`Error::Signature`] if signature generation failed.
305    pub fn sign<S: SigningKey>(self, signing_key: &S) -> Result<Certificate> {
306        // Empty valid principals result in a "golden ticket", so this check
307        // ensures that was explicitly configured via `all_principals_valid`.
308        let valid_principals = match self.valid_principals {
309            Some(principals) => principals,
310            None => return Err(Field::ValidPrincipals.invalid_error()),
311        };
312
313        let mut cert = Certificate {
314            nonce: self.nonce,
315            public_key: self.public_key,
316            serial: self.serial.unwrap_or_default(),
317            cert_type: self.cert_type.unwrap_or_default(),
318            key_id: self.key_id.unwrap_or_default(),
319            valid_principals,
320            valid_after: self.valid_after,
321            valid_before: self.valid_before,
322            critical_options: self.critical_options,
323            extensions: self.extensions,
324            reserved: Vec::new(),
325            comment: self.comment.unwrap_or_default(),
326            signature_key: signing_key.public_key(),
327            signature: Signature::placeholder(),
328        };
329
330        let mut tbs_cert = Vec::new();
331        cert.encode_tbs(&mut tbs_cert)?;
332        cert.signature = signing_key.try_sign(&tbs_cert)?;
333
334        #[cfg(debug_assertions)]
335        cert.validate_at(
336            cert.valid_after,
337            &[cert.signature_key.fingerprint(Default::default())],
338        )?;
339
340        Ok(cert)
341    }
342}
343
344impl Debug for Builder {
345    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
346        f.debug_struct("Builder").finish_non_exhaustive()
347    }
348}