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