ssh_key/certificate.rs
1//! OpenSSH certificate support.
2
3mod builder;
4mod cert_type;
5mod field;
6mod options_map;
7mod unix_time;
8
9pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map::OptionsMap};
10
11use self::unix_time::UnixTime;
12use crate::{
13 public::{KeyData, SshFormat},
14 Algorithm, Error, Fingerprint, HashAlg, Result, Signature,
15};
16use alloc::{
17 borrow::ToOwned,
18 string::{String, ToString},
19 vec::Vec,
20};
21use core::str::FromStr;
22use encoding::{Base64Reader, CheckedSum, Decode, Encode, Reader, Writer};
23use signature::Verifier;
24
25#[cfg(feature = "serde")]
26use serde::{de, ser, Deserialize, Serialize};
27
28#[cfg(feature = "std")]
29use std::{fs, path::Path, time::SystemTime};
30
31/// OpenSSH certificate as specified in [PROTOCOL.certkeys].
32///
33/// OpenSSH supports X.509-like certificate authorities, but using a custom
34/// encoding format.
35///
36/// # ⚠️ Security Warning
37///
38/// Certificates must be validated before they can be trusted!
39///
40/// The [`Certificate`] type does not automatically perform validation checks
41/// and supports parsing certificates which may potentially be invalid.
42/// Just because a [`Certificate`] parses successfully does not mean that it
43/// can be trusted.
44///
45/// See "Certificate Validation" documentation below for more information on
46/// how to properly validate certificates.
47///
48/// # Certificate Validation
49///
50/// For a certificate to be trusted, the following properties MUST be
51/// validated:
52///
53/// - Certificate is signed by a trusted certificate authority (CA)
54/// - Signature over the certificate verifies successfully
55/// - Current time is within the certificate's validity window
56/// - Certificate authorizes the expected principal
57/// - All critical extensions to the certificate are recognized and validate
58/// successfully.
59///
60/// The [`Certificate::validate`] and [`Certificate::validate_at`] methods can
61/// be used to validate a certificate.
62///
63/// ## Example
64///
65/// The following example walks through how to implement the steps outlined
66/// above for validating a certificate:
67///
68#[cfg_attr(all(feature = "p256", feature = "std"), doc = " ```")]
69#[cfg_attr(not(all(feature = "p256", feature = "std")), doc = " ```ignore")]
70/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
71/// use ssh_key::{Certificate, Fingerprint};
72/// use std::str::FromStr;
73///
74/// // List of trusted certificate authority (CA) fingerprints
75/// let ca_fingerprints = [
76/// Fingerprint::from_str("SHA256:JQ6FV0rf7qqJHZqIj4zNH8eV0oB8KLKh9Pph3FTD98g")?
77/// ];
78///
79/// // Certificate to be validated
80/// let certificate = Certificate::from_str(
81/// "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE7x9ln6uZLLkfXM8iatrnAAuytVHeCznU8VlEgx7TvLAAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAAAAAAAAAAAABAAAAFGVkMjU1MTktd2l0aC1wMjU2LWNhAAAAAAAAAABiUZm7AAAAAPTaMrsAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+SqoojY6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAZAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASQAAACEA/0Cwxhkac5AeNYE958j8GgvmkIESDH1TE7QYIqxsFsIAAAAgTEw8WVjlz8AnvyaKGOUELMpyFFJagtD2JFAIAJvilrc= user@example.com"
82/// )?;
83///
84/// // Perform basic certificate validation, ensuring that the certificate is
85/// // signed by a trusted certificate authority (CA) and checking that the
86/// // current system clock time is within the certificate's validity window
87/// certificate.validate(&ca_fingerprints)?;
88///
89/// // Check that the certificate includes the expected principal name
90/// // (i.e. username or hostname)
91/// // if !certificate.principals().contains(expected_principal) { return Err(...) }
92///
93/// // Check that all of the critical extensions are recognized
94/// // if !certificate.critical_options.iter().all(|critical| ...) { return Err(...) }
95///
96/// // If we've made it this far, the certificate can be trusted
97/// Ok(())
98/// # }
99/// ```
100///
101/// # Certificate Builder (SSH CA support)
102///
103/// This crate implements all of the functionality needed for a pure Rust
104/// SSH certificate authority which can build and sign OpenSSH certificates.
105///
106/// See the [`Builder`] type's documentation for more information.
107///
108/// # `serde` support
109///
110/// When the `serde` feature of this crate is enabled, this type receives impls
111/// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`].
112///
113/// The serialization uses a binary encoding with binary formats like bincode
114/// and CBOR, and the OpenSSH string serialization when used with
115/// human-readable formats like JSON and TOML.
116///
117/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
118#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
119pub struct Certificate {
120 /// CA-provided random bitstring of arbitrary length
121 /// (but typically 16 or 32 bytes).
122 nonce: Vec<u8>,
123
124 /// Public key data.
125 public_key: KeyData,
126
127 /// Serial number.
128 serial: u64,
129
130 /// Certificate type.
131 cert_type: CertType,
132
133 /// Key ID.
134 key_id: String,
135
136 /// Valid principals.
137 valid_principals: Vec<String>,
138
139 /// Valid after.
140 valid_after: UnixTime,
141
142 /// Valid before.
143 valid_before: UnixTime,
144
145 /// Critical options.
146 critical_options: OptionsMap,
147
148 /// Extensions.
149 extensions: OptionsMap,
150
151 /// Reserved field.
152 reserved: Vec<u8>,
153
154 /// Signature key of signing CA.
155 signature_key: KeyData,
156
157 /// Signature over the certificate.
158 signature: Signature,
159
160 /// Comment on the certificate.
161 comment: String,
162}
163
164impl Certificate {
165 /// Parse an OpenSSH-formatted certificate.
166 ///
167 /// OpenSSH-formatted certificates look like the following
168 /// (i.e. similar to OpenSSH public keys with `-cert-v01@openssh.com`):
169 ///
170 /// ```text
171 /// ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlc...8REbCaAw== user@example.com
172 /// ```
173 pub fn from_openssh(certificate_str: &str) -> Result<Self> {
174 let encapsulation = SshFormat::decode(certificate_str.trim_end().as_bytes())?;
175 let mut reader = Base64Reader::new(encapsulation.base64_data)?;
176 let mut cert = Certificate::decode(&mut reader)?;
177
178 // Verify that the algorithm in the Base64-encoded data matches the text
179 if encapsulation.algorithm_id != cert.algorithm().to_certificate_type() {
180 return Err(Error::AlgorithmUnknown);
181 }
182
183 cert.comment = encapsulation.comment.to_owned();
184 Ok(reader.finish(cert)?)
185 }
186
187 /// Parse a raw binary OpenSSH certificate.
188 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
189 let reader = &mut bytes;
190 let cert = Certificate::decode(reader)?;
191 Ok(reader.finish(cert)?)
192 }
193
194 /// Encode OpenSSH certificate to a [`String`].
195 pub fn to_openssh(&self) -> Result<String> {
196 SshFormat::encode_string(
197 &self.algorithm().to_certificate_type(),
198 self,
199 self.comment(),
200 )
201 }
202
203 /// Serialize OpenSSH certificate as raw bytes.
204 pub fn to_bytes(&self) -> Result<Vec<u8>> {
205 let mut cert_bytes = Vec::new();
206 self.encode(&mut cert_bytes)?;
207 Ok(cert_bytes)
208 }
209
210 /// Read OpenSSH certificate from a file.
211 #[cfg(feature = "std")]
212 pub fn read_file(path: &Path) -> Result<Self> {
213 let input = fs::read_to_string(path)?;
214 Self::from_openssh(&input)
215 }
216
217 /// Write OpenSSH certificate to a file.
218 #[cfg(feature = "std")]
219 pub fn write_file(&self, path: &Path) -> Result<()> {
220 let encoded = self.to_openssh()?;
221 fs::write(path, encoded.as_bytes())?;
222 Ok(())
223 }
224
225 /// Get the public key algorithm for this certificate.
226 pub fn algorithm(&self) -> Algorithm {
227 self.public_key.algorithm()
228 }
229
230 /// Get the comment on this certificate.
231 pub fn comment(&self) -> &str {
232 self.comment.as_str()
233 }
234
235 /// Nonces are a CA-provided random bitstring of arbitrary length
236 /// (but typically 16 or 32 bytes).
237 ///
238 /// It's included to make attacks that depend on inducing collisions in the
239 /// signature hash infeasible.
240 pub fn nonce(&self) -> &[u8] {
241 &self.nonce
242 }
243
244 /// Get this certificate's public key data.
245 pub fn public_key(&self) -> &KeyData {
246 &self.public_key
247 }
248
249 /// Optional certificate serial number set by the CA to provide an
250 /// abbreviated way to refer to certificates from that CA.
251 ///
252 /// If a CA does not wish to number its certificates, it must set this
253 /// field to zero.
254 pub fn serial(&self) -> u64 {
255 self.serial
256 }
257
258 /// Specifies whether this certificate is for identification of a user or
259 /// a host.
260 pub fn cert_type(&self) -> CertType {
261 self.cert_type
262 }
263
264 /// Key IDs are a free-form text field that is filled in by the CA at the
265 /// time of signing.
266 ///
267 /// The intention is that the contents of this field are used to identify
268 /// the identity principal in log messages.
269 pub fn key_id(&self) -> &str {
270 &self.key_id
271 }
272
273 /// List of zero or more principals which this certificate is valid for.
274 ///
275 /// Principals are hostnames for host certificates and usernames for user
276 /// certificates.
277 ///
278 /// As a special case, a zero-length "valid principals" field means the
279 /// certificate is valid for any principal of the specified type.
280 pub fn valid_principals(&self) -> &[String] {
281 &self.valid_principals
282 }
283
284 /// Valid after (Unix time).
285 pub fn valid_after(&self) -> u64 {
286 self.valid_after.into()
287 }
288
289 /// Valid before (Unix time).
290 pub fn valid_before(&self) -> u64 {
291 self.valid_before.into()
292 }
293
294 /// Valid after (system time).
295 #[cfg(feature = "std")]
296 pub fn valid_after_time(&self) -> SystemTime {
297 self.valid_after.into()
298 }
299
300 /// Valid before (system time).
301 #[cfg(feature = "std")]
302 pub fn valid_before_time(&self) -> SystemTime {
303 self.valid_before.into()
304 }
305
306 /// The critical options section of the certificate specifies zero or more
307 /// options on the certificate's validity.
308 ///
309 /// Each named option may only appear once in a certificate.
310 ///
311 /// All options are "critical"; if an implementation does not recognize an
312 /// option, then the validating party should refuse to accept the
313 /// certificate.
314 pub fn critical_options(&self) -> &OptionsMap {
315 &self.critical_options
316 }
317
318 /// The extensions section of the certificate specifies zero or more
319 /// non-critical certificate extensions.
320 ///
321 /// If an implementation does not recognise an extension, then it should
322 /// ignore it.
323 pub fn extensions(&self) -> &OptionsMap {
324 &self.extensions
325 }
326
327 /// Signature key of signing CA.
328 pub fn signature_key(&self) -> &KeyData {
329 &self.signature_key
330 }
331
332 /// Signature computed over all preceding fields from the initial string up
333 /// to, and including the signature key.
334 pub fn signature(&self) -> &Signature {
335 &self.signature
336 }
337
338 /// Perform certificate validation using the system clock to check that
339 /// the current time is within the certificate's validity window.
340 ///
341 /// # ⚠️ Security Warning: Some Assembly Required
342 ///
343 /// See [`Certificate::validate_at`] documentation for important notes on
344 /// how to properly validate certificates!
345 #[cfg(feature = "std")]
346 pub fn validate<'a, I>(&self, ca_fingerprints: I) -> Result<()>
347 where
348 I: IntoIterator<Item = &'a Fingerprint>,
349 {
350 self.validate_at(UnixTime::now()?.into(), ca_fingerprints)
351 }
352
353 /// Perform certificate validation.
354 ///
355 /// Checks for the following:
356 ///
357 /// - Specified Unix timestamp is within the certificate's valid range
358 /// - Certificate's signature validates against the public key included in
359 /// the certificate
360 /// - Fingerprint of the public key included in the certificate matches one
361 /// of the trusted certificate authority (CA) fingerprints provided in
362 /// the `ca_fingerprints` parameter.
363 ///
364 /// NOTE: only SHA-256 fingerprints are supported at this time.
365 ///
366 /// # ⚠️ Security Warning: Some Assembly Required
367 ///
368 /// This method does not perform the full set of validation checks needed
369 /// to determine if a certificate is to be trusted.
370 ///
371 /// If this method succeeds, the following properties still need to be
372 /// checked to ensure the certificate is valid:
373 ///
374 /// - `valid_principals` is empty or contains the expected principal
375 /// - `critical_options` is empty or contains *only* options which are
376 /// recognized, and that the recognized options are all valid
377 ///
378 /// ## Returns
379 /// - `Ok` if the certificate validated successfully
380 /// - `Error::CertificateValidation` if the certificate failed to validate
381 pub fn validate_at<'a, I>(&self, unix_timestamp: u64, ca_fingerprints: I) -> Result<()>
382 where
383 I: IntoIterator<Item = &'a Fingerprint>,
384 {
385 self.verify_signature()?;
386
387 // TODO(tarcieri): support non SHA-256 public key fingerprints?
388 let cert_fingerprint = self.signature_key.fingerprint(HashAlg::Sha256);
389
390 if !ca_fingerprints.into_iter().any(|f| f == &cert_fingerprint) {
391 return Err(Error::CertificateValidation);
392 }
393
394 let unix_timestamp = UnixTime::new(unix_timestamp)?;
395
396 // From PROTOCOL.certkeys:
397 //
398 // "valid after" and "valid before" specify a validity period for the
399 // certificate. Each represents a time in seconds since 1970-01-01
400 // A certificate is considered valid if:
401 //
402 // valid after <= current time < valid before
403 if self.valid_after <= unix_timestamp && unix_timestamp < self.valid_before {
404 Ok(())
405 } else {
406 Err(Error::CertificateValidation)
407 }
408 }
409
410 /// Verify the signature on the certificate against the public key in the
411 /// certificate.
412 ///
413 /// # ⚠️ Security Warning
414 ///
415 /// DON'T USE THIS!
416 ///
417 /// This function alone does not provide any security guarantees whatsoever.
418 ///
419 /// It verifies the signature in the certificate matches the CA public key
420 /// in the certificate, but does not ensure the CA is trusted.
421 ///
422 /// It is public only for testing purposes, and deliberately hidden from
423 /// the documentation for that reason.
424 #[doc(hidden)]
425 pub fn verify_signature(&self) -> Result<()> {
426 let mut tbs_certificate = Vec::new();
427 self.encode_tbs(&mut tbs_certificate)?;
428 self.signature_key
429 .verify(&tbs_certificate, &self.signature)
430 .map_err(|_| Error::CertificateValidation)
431 }
432
433 /// Encode the portion of the certificate "to be signed" by the CA
434 /// (or to be verified against an existing CA signature)
435 fn encode_tbs(&self, writer: &mut impl Writer) -> encoding::Result<()> {
436 self.algorithm().to_certificate_type().encode(writer)?;
437 self.nonce.encode(writer)?;
438 self.public_key.encode_key_data(writer)?;
439 self.serial.encode(writer)?;
440 self.cert_type.encode(writer)?;
441 self.key_id.encode(writer)?;
442 self.valid_principals.encode(writer)?;
443 self.valid_after.encode(writer)?;
444 self.valid_before.encode(writer)?;
445 self.critical_options.encode(writer)?;
446 self.extensions.encode(writer)?;
447 self.reserved.encode(writer)?;
448 self.signature_key.encode_prefixed(writer)
449 }
450}
451
452impl Decode for Certificate {
453 type Error = Error;
454
455 fn decode(reader: &mut impl Reader) -> Result<Self> {
456 let algorithm = Algorithm::new_certificate(&String::decode(reader)?)?;
457
458 Ok(Self {
459 nonce: Vec::decode(reader)?,
460 public_key: KeyData::decode_as(reader, algorithm)?,
461 serial: u64::decode(reader)?,
462 cert_type: CertType::decode(reader)?,
463 key_id: String::decode(reader)?,
464 valid_principals: Vec::decode(reader)?,
465 valid_after: UnixTime::decode(reader)?,
466 valid_before: UnixTime::decode(reader)?,
467 critical_options: OptionsMap::decode(reader)?,
468 extensions: OptionsMap::decode(reader)?,
469 reserved: Vec::decode(reader)?,
470 signature_key: reader.read_prefixed(KeyData::decode)?,
471 signature: reader.read_prefixed(Signature::decode)?,
472 comment: String::new(),
473 })
474 }
475}
476
477impl Encode for Certificate {
478 fn encoded_len(&self) -> encoding::Result<usize> {
479 [
480 self.algorithm().to_certificate_type().encoded_len()?,
481 self.nonce.encoded_len()?,
482 self.public_key.encoded_key_data_len()?,
483 self.serial.encoded_len()?,
484 self.cert_type.encoded_len()?,
485 self.key_id.encoded_len()?,
486 self.valid_principals.encoded_len()?,
487 self.valid_after.encoded_len()?,
488 self.valid_before.encoded_len()?,
489 self.critical_options.encoded_len()?,
490 self.extensions.encoded_len()?,
491 self.reserved.encoded_len()?,
492 self.signature_key.encoded_len_prefixed()?,
493 self.signature.encoded_len_prefixed()?,
494 ]
495 .checked_sum()
496 }
497
498 fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
499 self.encode_tbs(writer)?;
500 self.signature.encode_prefixed(writer)
501 }
502}
503
504impl FromStr for Certificate {
505 type Err = Error;
506
507 fn from_str(s: &str) -> Result<Self> {
508 Self::from_openssh(s)
509 }
510}
511
512impl ToString for Certificate {
513 fn to_string(&self) -> String {
514 self.to_openssh().expect("SSH certificate encoding error")
515 }
516}
517
518#[cfg(feature = "serde")]
519impl<'de> Deserialize<'de> for Certificate {
520 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
521 where
522 D: de::Deserializer<'de>,
523 {
524 if deserializer.is_human_readable() {
525 let string = String::deserialize(deserializer)?;
526 Self::from_openssh(&string).map_err(de::Error::custom)
527 } else {
528 let bytes = Vec::<u8>::deserialize(deserializer)?;
529 Self::from_bytes(&bytes).map_err(de::Error::custom)
530 }
531 }
532}
533
534#[cfg(feature = "serde")]
535impl Serialize for Certificate {
536 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
537 where
538 S: ser::Serializer,
539 {
540 if serializer.is_human_readable() {
541 self.to_openssh()
542 .map_err(ser::Error::custom)?
543 .serialize(serializer)
544 } else {
545 self.to_bytes()
546 .map_err(ser::Error::custom)?
547 .serialize(serializer)
548 }
549 }
550}