From f8760a5fbfbc00b0dc68f93d87e09736afbd2804 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 29 Mar 2024 13:17:52 -0600 Subject: [PATCH] ssh-key: use `u64` type for certificate timestamps Closes #174 Previously certificates only supported the `i64` range to allow for infallible conversions to/from `SystemTime`. Unfortunately OpenSSH defaults to using `u64::MAX` as the `valid_before` time in order to represent certificate that's valid "forever". The previous restriction meant that `ssh-key` was incapible of parsing such certificates. This commit switches to using a raw `u64` everywhere, and changing conversions to `SystemTime` to return an `Option` which is `None` if the `u64` value overflows an `i64`. --- Cargo.lock | 6 ++-- ssh-cipher/Cargo.toml | 6 ++-- ssh-encoding/Cargo.toml | 2 +- ssh-key/Cargo.toml | 6 ++-- ssh-key/src/authorized_keys.rs | 2 +- ssh-key/src/certificate.rs | 44 ++++++++++++++++++------------ ssh-key/src/certificate/builder.rs | 16 ++++------- ssh-key/src/known_hosts.rs | 2 +- 8 files changed, 43 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffc2227..68d62f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -762,7 +762,7 @@ dependencies = [ [[package]] name = "ssh-cipher" -version = "0.2.0" +version = "0.3.0-pre" dependencies = [ "aes", "aes-gcm", @@ -778,7 +778,7 @@ dependencies = [ [[package]] name = "ssh-encoding" -version = "0.2.0" +version = "0.3.0-pre" dependencies = [ "base64ct", "bytes", @@ -789,7 +789,7 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.6.5" +version = "0.7.0-pre" dependencies = [ "bcrypt-pbkdf", "dsa", diff --git a/ssh-cipher/Cargo.toml b/ssh-cipher/Cargo.toml index ac04406..b0c0b4c 100644 --- a/ssh-cipher/Cargo.toml +++ b/ssh-cipher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-cipher" -version = "0.2.0" +version = "0.3.0-pre" description = """ Pure Rust implementation of SSH symmetric encryption including support for the modern aes128-gcm@openssh.com/aes256-gcm@openssh.com and @@ -18,8 +18,8 @@ edition = "2021" rust-version = "1.72" [dependencies] -cipher = "0.5.0-pre.4" -encoding = { package = "ssh-encoding", version = "0.2", path = "../ssh-encoding" } +cipher = "=0.5.0-pre.4" +encoding = { package = "ssh-encoding", version = "=0.3.0-pre", path = "../ssh-encoding" } # optional dependencies aes = { version = "=0.9.0-pre", optional = true, default-features = false } diff --git a/ssh-encoding/Cargo.toml b/ssh-encoding/Cargo.toml index a59df85..8d71520 100644 --- a/ssh-encoding/Cargo.toml +++ b/ssh-encoding/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-encoding" -version = "0.2.0" +version = "0.3.0-pre" description = """ Pure Rust implementation of SSH data type decoders/encoders as described in RFC4251 diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index abda2c8..44ef8ff 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-key" -version = "0.6.5" +version = "0.7.0-pre" description = """ Pure Rust implementation of SSH key file format decoders/encoders as described in RFC4251/RFC4253 and OpenSSH key formats, as well as "sshsig" signatures and @@ -17,8 +17,8 @@ edition = "2021" rust-version = "1.73" [dependencies] -cipher = { package = "ssh-cipher", version = "0.2", path = "../ssh-cipher" } -encoding = { package = "ssh-encoding", version = "0.2", features = ["base64", "pem", "sha2"], path = "../ssh-encoding" } +cipher = { package = "ssh-cipher", version = "=0.3.0-pre", path = "../ssh-cipher" } +encoding = { package = "ssh-encoding", version = "=0.3.0-pre", features = ["base64", "pem", "sha2"], path = "../ssh-encoding" } sha2 = { version = "=0.11.0-pre.3", default-features = false } signature = { version = "=2.3.0-pre.3", default-features = false } subtle = { version = "2", default-features = false } diff --git a/ssh-key/src/authorized_keys.rs b/ssh-key/src/authorized_keys.rs index a485c7e..cdf0ed8 100644 --- a/ssh-key/src/authorized_keys.rs +++ b/ssh-key/src/authorized_keys.rs @@ -36,7 +36,7 @@ const COMMENT_DELIMITER: char = '#'; /// the key). pub struct AuthorizedKeys<'a> { /// Lines of the file being iterated over - lines: core::str::Lines<'a>, + lines: str::Lines<'a>, } impl<'a> AuthorizedKeys<'a> { diff --git a/ssh-key/src/certificate.rs b/ssh-key/src/certificate.rs index c241a88..2c24dcf 100644 --- a/ssh-key/src/certificate.rs +++ b/ssh-key/src/certificate.rs @@ -8,7 +8,6 @@ mod unix_time; pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map::OptionsMap}; -use self::unix_time::UnixTime; use crate::{ public::{KeyData, SshFormat}, Algorithm, Error, Fingerprint, HashAlg, Result, Signature, @@ -26,7 +25,10 @@ use signature::Verifier; use serde::{de, ser, Deserialize, Serialize}; #[cfg(feature = "std")] -use std::{fs, path::Path, time::SystemTime}; +use { + self::unix_time::UnixTime, + std::{fs, path::Path, time::SystemTime}, +}; /// OpenSSH certificate as specified in [PROTOCOL.certkeys]. /// @@ -137,10 +139,10 @@ pub struct Certificate { valid_principals: Vec, /// Valid after. - valid_after: UnixTime, + valid_after: u64, /// Valid before. - valid_before: UnixTime, + valid_before: u64, /// Critical options. critical_options: OptionsMap, @@ -281,26 +283,34 @@ impl Certificate { &self.valid_principals } - /// Valid after (Unix time). + /// Valid after (Unix time), i.e. certificate issuance time. pub fn valid_after(&self) -> u64 { - self.valid_after.into() + self.valid_after } - /// Valid before (Unix time). + /// Valid before (Unix time), i.e. certificate expiration time. pub fn valid_before(&self) -> u64 { - self.valid_before.into() + self.valid_before } - /// Valid after (system time). + /// Valid after (system time), i.e. certificate issuance time. + /// + /// # Returns + /// - `Some` if the `u64` value is a valid `SystemTime` + /// - `None` if it is not (i.e. overflows `i64`) #[cfg(feature = "std")] - pub fn valid_after_time(&self) -> SystemTime { - self.valid_after.into() + pub fn valid_after_time(&self) -> Option { + UnixTime::try_from(self.valid_after).ok().map(Into::into) } - /// Valid before (system time). + /// Valid before (system time), i.e. certificate expiration time. + /// + /// # Returns + /// - `Some` if the `u64` value is a valid `SystemTime` + /// - `None` if it is not (i.e. overflows `i64`, effectively never-expiring) #[cfg(feature = "std")] - pub fn valid_before_time(&self) -> SystemTime { - self.valid_before.into() + pub fn valid_before_time(&self) -> Option { + UnixTime::try_from(self.valid_before).ok().map(Into::into) } /// The critical options section of the certificate specifies zero or more @@ -391,8 +401,6 @@ impl Certificate { return Err(Error::CertificateValidation); } - let unix_timestamp = UnixTime::new(unix_timestamp)?; - // From PROTOCOL.certkeys: // // "valid after" and "valid before" specify a validity period for the @@ -462,8 +470,8 @@ impl Decode for Certificate { cert_type: CertType::decode(reader)?, key_id: String::decode(reader)?, valid_principals: Vec::decode(reader)?, - valid_after: UnixTime::decode(reader)?, - valid_before: UnixTime::decode(reader)?, + valid_after: u64::decode(reader)?, + valid_before: u64::decode(reader)?, critical_options: OptionsMap::decode(reader)?, extensions: OptionsMap::decode(reader)?, reserved: Vec::decode(reader)?, diff --git a/ssh-key/src/certificate/builder.rs b/ssh-key/src/certificate/builder.rs index 3d00b9c..e67fba9 100644 --- a/ssh-key/src/certificate/builder.rs +++ b/ssh-key/src/certificate/builder.rs @@ -1,6 +1,6 @@ //! OpenSSH certificate builder. -use super::{unix_time::UnixTime, CertType, Certificate, Field, OptionsMap}; +use super::{CertType, Certificate, Field, OptionsMap}; use crate::{public, Result, Signature, SigningKey}; use alloc::{string::String, vec::Vec}; @@ -8,7 +8,7 @@ use alloc::{string::String, vec::Vec}; use rand_core::CryptoRngCore; #[cfg(feature = "std")] -use std::time::SystemTime; +use {super::UnixTime, std::time::SystemTime}; #[cfg(doc)] use crate::PrivateKey; @@ -84,8 +84,8 @@ pub struct Builder { cert_type: Option, key_id: Option, valid_principals: Option>, - valid_after: UnixTime, - valid_before: UnixTime, + valid_after: u64, + valid_before: u64, critical_options: OptionsMap, extensions: OptionsMap, comment: Option, @@ -105,12 +105,6 @@ impl Builder { valid_after: u64, valid_before: u64, ) -> Result { - let valid_after = - UnixTime::new(valid_after).map_err(|_| Field::ValidAfter.invalid_error())?; - - let valid_before = - UnixTime::new(valid_before).map_err(|_| Field::ValidBefore.invalid_error())?; - if valid_before < valid_after { return Err(Field::ValidBefore.invalid_error()); } @@ -304,7 +298,7 @@ impl Builder { #[cfg(debug_assertions)] cert.validate_at( - cert.valid_after.into(), + cert.valid_after, &[cert.signature_key.fingerprint(Default::default())], )?; diff --git a/ssh-key/src/known_hosts.rs b/ssh-key/src/known_hosts.rs index e5da0f4..7ceb316 100644 --- a/ssh-key/src/known_hosts.rs +++ b/ssh-key/src/known_hosts.rs @@ -46,7 +46,7 @@ const MAGIC_HASH_PREFIX: &str = "|1|"; /// the key). pub struct KnownHosts<'a> { /// Lines of the file being iterated over - lines: core::str::Lines<'a>, + lines: str::Lines<'a>, } impl<'a> KnownHosts<'a> {