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}