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}