ssh_key/
certificate.rs

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