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