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}