ssh_key/certificate/
builder.rs

1//! OpenSSH certificate builder.
2
3use super::{unix_time::UnixTime, CertType, Certificate, Field, OptionsMap, SigningKey};
4use crate::{public, Result, Signature};
5use alloc::{string::String, vec::Vec};
6
7#[cfg(feature = "rand_core")]
8use rand_core::{CryptoRng, RngCore};
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    ) -> Self {
108        // TODO(tarcieri): return a `Result` instead of using `expect`
109        // Breaking change; needs to be done in the next release
110        let valid_after = UnixTime::new(valid_after).expect("valid_after time overflow");
111        let valid_before = UnixTime::new(valid_before).expect("valid_before time overflow");
112
113        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
129    /// using [`SystemTime`] values.
130    #[cfg(feature = "std")]
131    #[cfg_attr(docsrs, doc(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        // TODO(tarcieri): move this check into `Builder::new`
145        if valid_before < valid_after {
146            return Err(Field::ValidBefore.invalid_error());
147        }
148
149        Ok(Self::new(
150            nonce,
151            public_key,
152            valid_before.into(),
153            valid_after.into(),
154        ))
155    }
156
157    /// Create a new certificate builder, generating a random nonce using the
158    /// provided random number generator.
159    #[cfg(feature = "rand_core")]
160    #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
161    pub fn new_with_random_nonce(
162        mut rng: impl CryptoRng + RngCore,
163        public_key: impl Into<public::KeyData>,
164        valid_after: u64,
165        valid_before: u64,
166    ) -> Self {
167        let mut nonce = vec![0u8; Self::RECOMMENDED_NONCE_SIZE];
168        rng.fill_bytes(&mut nonce);
169        Self::new(nonce, public_key, valid_after, valid_before)
170    }
171
172    /// Set certificate serial number.
173    ///
174    /// Default: `0`.
175    pub fn serial(&mut self, serial: u64) -> Result<&mut Self> {
176        if self.serial.is_some() {
177            return Err(Field::Serial.invalid_error());
178        }
179
180        self.serial = Some(serial);
181        Ok(self)
182    }
183
184    /// Set certificate type: user or host.
185    ///
186    /// Default: [`CertType::User`].
187    pub fn cert_type(&mut self, cert_type: CertType) -> Result<&mut Self> {
188        if self.cert_type.is_some() {
189            return Err(Field::Type.invalid_error());
190        }
191
192        self.cert_type = Some(cert_type);
193        Ok(self)
194    }
195
196    /// Set key ID: label to identify this particular certificate.
197    ///
198    /// Default `""`
199    pub fn key_id(&mut self, key_id: impl Into<String>) -> Result<&mut Self> {
200        if self.key_id.is_some() {
201            return Err(Field::KeyId.invalid_error());
202        }
203
204        self.key_id = Some(key_id.into());
205        Ok(self)
206    }
207
208    /// Add a principal (i.e. username or hostname) to `valid_principals`.
209    pub fn valid_principal(&mut self, principal: impl Into<String>) -> Result<&mut Self> {
210        match &mut self.valid_principals {
211            Some(principals) => principals.push(principal.into()),
212            None => self.valid_principals = Some(vec![principal.into()]),
213        }
214
215        Ok(self)
216    }
217
218    /// Mark this certificate as being valid for all principals.
219    ///
220    /// # ⚠️ Security Warning
221    ///
222    /// Use this method with care! It generates "golden ticket" certificates
223    /// which can e.g. authenticate as any user on a system, or impersonate
224    /// any host.
225    pub fn all_principals_valid(&mut self) -> Result<&mut Self> {
226        self.valid_principals = Some(Vec::new());
227        Ok(self)
228    }
229
230    /// Add a critical option to this certificate.
231    ///
232    /// Critical options must be recognized or the certificate must be rejected.
233    pub fn critical_option(
234        &mut self,
235        name: impl Into<String>,
236        data: impl Into<String>,
237    ) -> Result<&mut Self> {
238        let name = name.into();
239        let data = data.into();
240
241        if self.critical_options.contains_key(&name) {
242            return Err(Field::CriticalOptions.invalid_error());
243        }
244
245        self.critical_options.insert(name, data);
246        Ok(self)
247    }
248
249    /// Add an extension to this certificate.
250    ///
251    /// Extensions can be unrecognized without impacting the certificate.
252    pub fn extension(
253        &mut self,
254        name: impl Into<String>,
255        data: impl Into<String>,
256    ) -> Result<&mut Self> {
257        let name = name.into();
258        let data = data.into();
259
260        if self.extensions.contains_key(&name) {
261            return Err(Field::Extensions.invalid_error());
262        }
263
264        self.extensions.insert(name, data);
265        Ok(self)
266    }
267
268    /// Add a comment to this certificate.
269    ///
270    /// Default `""`
271    pub fn comment(&mut self, comment: impl Into<String>) -> Result<&mut Self> {
272        if self.comment.is_some() {
273            return Err(Field::Comment.invalid_error());
274        }
275
276        self.comment = Some(comment.into());
277        Ok(self)
278    }
279
280    /// Sign the certificate using the provided signer type.
281    ///
282    /// The [`PrivateKey`] type can be used as a signer.
283    pub fn sign<S: SigningKey>(self, signing_key: &S) -> Result<Certificate> {
284        // Empty valid principals result in a "golden ticket", so this check
285        // ensures that was explicitly configured via `all_principals_valid`.
286        let valid_principals = match self.valid_principals {
287            Some(principals) => principals,
288            None => return Err(Field::ValidPrincipals.invalid_error()),
289        };
290
291        let mut cert = Certificate {
292            nonce: self.nonce,
293            public_key: self.public_key,
294            serial: self.serial.unwrap_or_default(),
295            cert_type: self.cert_type.unwrap_or_default(),
296            key_id: self.key_id.unwrap_or_default(),
297            valid_principals,
298            valid_after: self.valid_after,
299            valid_before: self.valid_before,
300            critical_options: self.critical_options,
301            extensions: self.extensions,
302            reserved: Vec::new(),
303            comment: self.comment.unwrap_or_default(),
304            signature_key: signing_key.public_key(),
305            signature: Signature::placeholder(),
306        };
307
308        let mut tbs_cert = Vec::new();
309        cert.encode_tbs(&mut tbs_cert)?;
310        cert.signature = signing_key.try_sign(&tbs_cert)?;
311
312        #[cfg(all(debug_assertions, feature = "fingerprint"))]
313        cert.validate_at(
314            cert.valid_after.into(),
315            &[cert.signature_key.fingerprint(Default::default())],
316        )?;
317
318        Ok(cert)
319    }
320}