ssh_key/certificate/
builder.rs

1//! OpenSSH certificate builder.
2
3use super::{unix_time::UnixTime, CertType, Certificate, Field, OptionsMap};
4use crate::{public, Result, Signature, SigningKey};
5use alloc::{string::String, vec::Vec};
6
7#[cfg(feature = "rand_core")]
8use rand_core::CryptoRngCore;
9
10#[cfg(feature = "std")]
11use std::time::SystemTime;
12
13#[cfg(doc)]
14use crate::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(
38    all(feature = "ed25519", feature = "getrandom", feature = "std"),
39    doc = " ```"
40)]
41#[cfg_attr(
42    not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
43    doc = " ```ignore"
44)]
45/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
46/// use ssh_key::{Algorithm, PrivateKey, certificate, rand_core::OsRng};
47/// use std::time::{SystemTime, UNIX_EPOCH};
48///
49/// // Generate the certificate authority's private key
50/// let ca_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
51///
52/// // Generate a "subject" key to be signed by the certificate authority.
53/// // Normally a user or host would do this locally and give the certificate
54/// // authority the public key.
55/// let subject_private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
56/// let subject_public_key = subject_private_key.public_key();
57///
58/// // Create certificate validity window
59/// let valid_after = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
60/// let valid_before = valid_after + (365 * 86400); // e.g. 1 year
61///
62/// // Initialize certificate builder
63/// let mut cert_builder = certificate::Builder::new_with_random_nonce(
64///     &mut OsRng,
65///     subject_public_key,
66///     valid_after,
67///     valid_before,
68/// )?;
69/// cert_builder.serial(42)?; // Optional: serial number chosen by the CA
70/// cert_builder.key_id("nobody-cert-02")?; // Optional: CA-specific key identifier
71/// cert_builder.cert_type(certificate::CertType::User)?; // User or host certificate
72/// cert_builder.valid_principal("nobody")?; // Unix username or hostname
73/// cert_builder.comment("nobody@example.com")?; // Comment (typically an email address)
74///
75/// // Sign and return the `Certificate` for `subject_public_key`
76/// let cert = cert_builder.sign(&ca_key)?;
77/// # Ok(())
78/// # }
79/// ```
80pub struct Builder {
81    public_key: public::KeyData,
82    nonce: Vec<u8>,
83    serial: Option<u64>,
84    cert_type: Option<CertType>,
85    key_id: Option<String>,
86    valid_principals: Option<Vec<String>>,
87    valid_after: UnixTime,
88    valid_before: UnixTime,
89    critical_options: OptionsMap,
90    extensions: OptionsMap,
91    comment: Option<String>,
92}
93
94impl Builder {
95    /// Recommended size for a nonce.
96    pub const RECOMMENDED_NONCE_SIZE: usize = 16;
97
98    /// Create a new certificate builder for the given subject's public key.
99    ///
100    /// Also requires a nonce (random value typically 16 or 32 bytes long) and
101    /// the validity window of the certificate as Unix seconds.
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        let valid_after =
109            UnixTime::new(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
110
111        let valid_before =
112            UnixTime::new(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
113
114        if valid_before < valid_after {
115            return Err(Field::ValidBefore.invalid_error());
116        }
117
118        Ok(Self {
119            nonce: nonce.into(),
120            public_key: public_key.into(),
121            serial: None,
122            cert_type: None,
123            key_id: None,
124            valid_principals: None,
125            valid_after,
126            valid_before,
127            critical_options: OptionsMap::new(),
128            extensions: OptionsMap::new(),
129            comment: None,
130        })
131    }
132
133    /// Create a new certificate builder with the validity window specified
134    /// using [`SystemTime`] values.
135    #[cfg(feature = "std")]
136    pub fn new_with_validity_times(
137        nonce: impl Into<Vec<u8>>,
138        public_key: impl Into<public::KeyData>,
139        valid_after: SystemTime,
140        valid_before: SystemTime,
141    ) -> Result<Self> {
142        let valid_after =
143            UnixTime::try_from(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?;
144
145        let valid_before =
146            UnixTime::try_from(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?;
147
148        Self::new(nonce, public_key, valid_after.into(), valid_before.into())
149    }
150
151    /// Create a new certificate builder, generating a random nonce using the
152    /// provided random number generator.
153    #[cfg(feature = "rand_core")]
154    pub fn new_with_random_nonce(
155        rng: &mut impl CryptoRngCore,
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    pub fn serial(&mut self, serial: u64) -> Result<&mut Self> {
169        if self.serial.is_some() {
170            return Err(Field::Serial.invalid_error());
171        }
172
173        self.serial = Some(serial);
174        Ok(self)
175    }
176
177    /// Set certificate type: user or host.
178    ///
179    /// Default: [`CertType::User`].
180    pub fn cert_type(&mut self, cert_type: CertType) -> Result<&mut Self> {
181        if self.cert_type.is_some() {
182            return Err(Field::Type.invalid_error());
183        }
184
185        self.cert_type = Some(cert_type);
186        Ok(self)
187    }
188
189    /// Set key ID: label to identify this particular certificate.
190    ///
191    /// Default `""`
192    pub fn key_id(&mut self, key_id: impl Into<String>) -> Result<&mut Self> {
193        if self.key_id.is_some() {
194            return Err(Field::KeyId.invalid_error());
195        }
196
197        self.key_id = Some(key_id.into());
198        Ok(self)
199    }
200
201    /// Add a principal (i.e. username or hostname) to `valid_principals`.
202    pub fn valid_principal(&mut self, principal: impl Into<String>) -> Result<&mut Self> {
203        match &mut self.valid_principals {
204            Some(principals) => principals.push(principal.into()),
205            None => self.valid_principals = Some(vec![principal.into()]),
206        }
207
208        Ok(self)
209    }
210
211    /// Mark this certificate as being valid for all principals.
212    ///
213    /// # ⚠️ Security Warning
214    ///
215    /// Use this method with care! It generates "golden ticket" certificates
216    /// which can e.g. authenticate as any user on a system, or impersonate
217    /// any host.
218    pub fn all_principals_valid(&mut self) -> Result<&mut Self> {
219        self.valid_principals = Some(Vec::new());
220        Ok(self)
221    }
222
223    /// Add a critical option to this certificate.
224    ///
225    /// Critical options must be recognized or the certificate must be rejected.
226    pub fn critical_option(
227        &mut self,
228        name: impl Into<String>,
229        data: impl Into<String>,
230    ) -> Result<&mut Self> {
231        let name = name.into();
232        let data = data.into();
233
234        if self.critical_options.contains_key(&name) {
235            return Err(Field::CriticalOptions.invalid_error());
236        }
237
238        self.critical_options.insert(name, data);
239        Ok(self)
240    }
241
242    /// Add an extension to this certificate.
243    ///
244    /// Extensions can be unrecognized without impacting the certificate.
245    pub fn extension(
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.extensions.contains_key(&name) {
254            return Err(Field::Extensions.invalid_error());
255        }
256
257        self.extensions.insert(name, data);
258        Ok(self)
259    }
260
261    /// Add a comment to this certificate.
262    ///
263    /// Default `""`
264    pub fn comment(&mut self, comment: impl Into<String>) -> Result<&mut Self> {
265        if self.comment.is_some() {
266            return Err(Field::Comment.invalid_error());
267        }
268
269        self.comment = Some(comment.into());
270        Ok(self)
271    }
272
273    /// Sign the certificate using the provided signer type.
274    ///
275    /// The [`PrivateKey`] type can be used as a signer.
276    pub fn sign<S: SigningKey>(self, signing_key: &S) -> Result<Certificate> {
277        // Empty valid principals result in a "golden ticket", so this check
278        // ensures that was explicitly configured via `all_principals_valid`.
279        let valid_principals = match self.valid_principals {
280            Some(principals) => principals,
281            None => return Err(Field::ValidPrincipals.invalid_error()),
282        };
283
284        let mut cert = Certificate {
285            nonce: self.nonce,
286            public_key: self.public_key,
287            serial: self.serial.unwrap_or_default(),
288            cert_type: self.cert_type.unwrap_or_default(),
289            key_id: self.key_id.unwrap_or_default(),
290            valid_principals,
291            valid_after: self.valid_after,
292            valid_before: self.valid_before,
293            critical_options: self.critical_options,
294            extensions: self.extensions,
295            reserved: Vec::new(),
296            comment: self.comment.unwrap_or_default(),
297            signature_key: signing_key.public_key(),
298            signature: Signature::placeholder(),
299        };
300
301        let mut tbs_cert = Vec::new();
302        cert.encode_tbs(&mut tbs_cert)?;
303        cert.signature = signing_key.try_sign(&tbs_cert)?;
304
305        #[cfg(debug_assertions)]
306        cert.validate_at(
307            cert.valid_after.into(),
308            &[cert.signature_key.fingerprint(Default::default())],
309        )?;
310
311        Ok(cert)
312    }
313}