diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index fd0622ac..1c9e6750 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -20,6 +20,7 @@ rust-version = "1.60" [dependencies] base64ct = "1.4" pem-rfc7468 = "0.6" +sha2 = { version = "0.10.6", default-features = false } zeroize = { version = "1", default-features = false } # optional dependencies @@ -36,7 +37,6 @@ rsa = { version = "0.7", optional = true } sec1 = { version = "0.3", optional = true, default-features = false, features = ["point"] } serde = { version = "1", optional = true } sha1 = { version = "0.10", optional = true, default-features = false } -sha2 = { version = "0.10.6", optional = true, default-features = false, features = ["oid"] } signature = { version = "1.6.4", optional = true, default-features = false } subtle = { version = "2", optional = true, default-features = false } @@ -47,7 +47,7 @@ tempfile = "3" zeroize_derive = "1.3" # hack to make minimal-versions lint happy (pulled in by `ed25519-dalek`) [features] -default = ["ecdsa", "fingerprint", "std", "rand_core"] +default = ["ecdsa", "rand_core", "std"] alloc = ["base64ct/alloc", "signature", "zeroize/alloc"] std = [ "alloc", @@ -65,9 +65,8 @@ dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "signature/rand-preview"] ecdsa = ["dep:sec1"] ed25519 = ["dep:ed25519-dalek", "rand_core"] encryption = [ "alloc", "dep:aes", "dep:bcrypt-pbkdf", "dep:ctr", "rand_core"] -fingerprint = ["dep:sha2"] getrandom = ["rand_core/getrandom"] -rsa = ["dep:bigint", "dep:rsa"] +rsa = ["dep:bigint", "dep:rsa", "sha2/oid"] [package.metadata.docs.rs] all-features = true diff --git a/ssh-key/src/certificate.rs b/ssh-key/src/certificate.rs index ca24ca0a..ccf742f4 100644 --- a/ssh-key/src/certificate.rs +++ b/ssh-key/src/certificate.rs @@ -20,7 +20,7 @@ use crate::{ public::{Encapsulation, KeyData}, reader::{Base64Reader, Reader}, writer::{base64_len, Writer}, - Algorithm, Error, Result, Signature, + Algorithm, Error, Fingerprint, HashAlg, Result, Signature, }; use alloc::{ borrow::ToOwned, @@ -28,12 +28,7 @@ use alloc::{ vec::Vec, }; use core::str::FromStr; - -#[cfg(feature = "fingerprint")] -use { - crate::{Fingerprint, HashAlg}, - signature::Verifier, -}; +use signature::Verifier; #[cfg(feature = "serde")] use serde::{de, ser, Deserialize, Serialize}; @@ -372,8 +367,8 @@ impl Certificate { /// /// See [`Certificate::validate_at`] documentation for important notes on /// how to properly validate certificates! - #[cfg(all(feature = "fingerprint", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "fingerprint", feature = "std"))))] + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn validate<'a, I>(&self, ca_fingerprints: I) -> Result<()> where I: IntoIterator, @@ -409,8 +404,6 @@ impl Certificate { /// ## Returns /// - `Ok` if the certificate validated successfully /// - `Error::CertificateValidation` if the certificate failed to validate - #[cfg(feature = "fingerprint")] - #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] pub fn validate_at<'a, I>(&self, unix_timestamp: u64, ca_fingerprints: I) -> Result<()> where I: IntoIterator, @@ -454,7 +447,6 @@ impl Certificate { /// /// It is public only for testing purposes, and deliberately hidden from /// the documentation for that reason. - #[cfg(feature = "fingerprint")] #[doc(hidden)] pub fn verify_signature(&self) -> Result<()> { let mut tbs_certificate = Vec::new(); diff --git a/ssh-key/src/certificate/builder.rs b/ssh-key/src/certificate/builder.rs index 5774abb3..07a1c3fa 100644 --- a/ssh-key/src/certificate/builder.rs +++ b/ssh-key/src/certificate/builder.rs @@ -309,7 +309,7 @@ impl Builder { cert.encode_tbs(&mut tbs_cert)?; cert.signature = signing_key.try_sign(&tbs_cert)?; - #[cfg(all(debug_assertions, feature = "fingerprint"))] + #[cfg(debug_assertions)] cert.validate_at( cert.valid_after.into(), &[cert.signature_key.fingerprint(Default::default())], diff --git a/ssh-key/src/certificate/unix_time.rs b/ssh-key/src/certificate/unix_time.rs index f1fc882c..f2190c49 100644 --- a/ssh-key/src/certificate/unix_time.rs +++ b/ssh-key/src/certificate/unix_time.rs @@ -58,7 +58,7 @@ impl UnixTime { } /// Get the current time as a Unix timestamp. - #[cfg(all(feature = "std", feature = "fingerprint"))] + #[cfg(all(feature = "std"))] pub fn now() -> Result { SystemTime::now().try_into() } diff --git a/ssh-key/src/fingerprint.rs b/ssh-key/src/fingerprint.rs index b90efa67..9aa20454 100644 --- a/ssh-key/src/fingerprint.rs +++ b/ssh-key/src/fingerprint.rs @@ -37,7 +37,6 @@ use { /// /// When the `serde` feature of this crate is enabled, this type receives impls /// of [`Deserialize`][`serde::Deserialize`] and [`Serialize`][`serde::Serialize`]. -#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum Fingerprint { diff --git a/ssh-key/src/lib.rs b/ssh-key/src/lib.rs index 66685860..dae2440b 100644 --- a/ssh-key/src/lib.rs +++ b/ssh-key/src/lib.rs @@ -154,12 +154,11 @@ mod cipher; mod decode; mod encode; mod error; +mod fingerprint; mod kdf; mod reader; mod writer; -#[cfg(feature = "fingerprint")] -mod fingerprint; #[cfg(feature = "alloc")] mod mpint; #[cfg(feature = "alloc")] @@ -170,12 +169,14 @@ pub use crate::{ authorized_keys::AuthorizedKeys, cipher::Cipher, error::{Error, Result}, + fingerprint::Fingerprint, kdf::Kdf, private::PrivateKey, public::PublicKey, }; pub use base64ct::LineEnding; pub use pem_rfc7468 as pem; +pub use sha2; #[cfg(feature = "alloc")] pub use crate::{ @@ -185,8 +186,5 @@ pub use crate::{ #[cfg(feature = "ecdsa")] pub use sec1; -#[cfg(feature = "fingerprint")] -pub use crate::fingerprint::Fingerprint; - #[cfg(feature = "rand_core")] pub use rand_core; diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs index 52273ee0..45a1d89f 100644 --- a/ssh-key/src/private.rs +++ b/ssh-key/src/private.rs @@ -139,7 +139,7 @@ use crate::{ public, reader::Reader, writer::Writer, - Algorithm, Cipher, Error, Kdf, PublicKey, Result, + Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, }; use core::str; @@ -149,9 +149,6 @@ use { zeroize::Zeroizing, }; -#[cfg(feature = "fingerprint")] -use crate::{Fingerprint, HashAlg}; - #[cfg(feature = "rand_core")] use rand_core::{CryptoRng, RngCore}; @@ -412,8 +409,6 @@ impl PrivateKey { /// Compute key fingerprint. /// /// Use [`Default::default()`] to use the default hash function (SHA-256). - #[cfg(feature = "fingerprint")] - #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint { self.public_key.fingerprint(hash_alg) } diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index e8cd1841..fcc5b0c4 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -27,7 +27,7 @@ use crate::{ decode::Decode, encode::Encode, reader::{Base64Reader, Reader}, - Algorithm, Error, Result, + Algorithm, Error, Fingerprint, HashAlg, Result, }; use core::str::FromStr; @@ -41,9 +41,6 @@ use { }, }; -#[cfg(feature = "fingerprint")] -use crate::{Fingerprint, HashAlg}; - #[cfg(all(feature = "alloc", feature = "serde"))] use serde::{de, ser, Deserialize, Serialize}; @@ -211,8 +208,6 @@ impl PublicKey { /// Compute key fingerprint. /// /// Use [`Default::default()`] to use the default hash function (SHA-256). - #[cfg(feature = "fingerprint")] - #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint { self.key_data.fingerprint(hash_alg) } diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs index fd813adf..09cae902 100644 --- a/ssh-key/src/public/key_data.rs +++ b/ssh-key/src/public/key_data.rs @@ -3,7 +3,7 @@ use super::{Ed25519PublicKey, SkEd25519}; use crate::{ checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Algorithm, - Error, Result, + Error, Fingerprint, HashAlg, Result, }; #[cfg(feature = "alloc")] @@ -12,9 +12,6 @@ use super::{DsaPublicKey, RsaPublicKey}; #[cfg(feature = "ecdsa")] use super::{EcdsaPublicKey, SkEcdsaSha2NistP256}; -#[cfg(feature = "fingerprint")] -use crate::{Fingerprint, HashAlg}; - /// Public key data. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[non_exhaustive] @@ -99,8 +96,6 @@ impl KeyData { /// Compute key fingerprint. /// /// Use [`Default::default()`] to use the default hash function (SHA-256). - #[cfg(feature = "fingerprint")] - #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint { Fingerprint::new(hash_alg, self) } diff --git a/ssh-key/src/writer.rs b/ssh-key/src/writer.rs index 351331a5..cc26ea4b 100644 --- a/ssh-key/src/writer.rs +++ b/ssh-key/src/writer.rs @@ -2,13 +2,11 @@ use crate::Result; use pem_rfc7468 as pem; +use sha2::{Digest, Sha256, Sha512}; #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "fingerprint")] -use sha2::{Digest, Sha256, Sha512}; - /// Get the estimated length of data when encoded as Base64. /// /// This is an upper bound where the actual length might be slightly shorter. @@ -50,7 +48,6 @@ impl Writer for Vec { } } -#[cfg(feature = "fingerprint")] impl Writer for Sha256 { fn write(&mut self, bytes: &[u8]) -> Result<()> { self.update(bytes); @@ -58,7 +55,6 @@ impl Writer for Sha256 { } } -#[cfg(feature = "fingerprint")] impl Writer for Sha512 { fn write(&mut self, bytes: &[u8]) -> Result<()> { self.update(bytes); diff --git a/ssh-key/tests/certificate.rs b/ssh-key/tests/certificate.rs index 8bd737dc..2a72225c 100644 --- a/ssh-key/tests/certificate.rs +++ b/ssh-key/tests/certificate.rs @@ -23,16 +23,16 @@ const ECDSA_P256_CERT_EXAMPLE: &str = include_str!("examples/id_ecdsa_p256-cert. const ED25519_CERT_EXAMPLE: &str = include_str!("examples/id_ed25519-cert.pub"); /// Ed25519 OpenSSH Certificate with deliberately invalid signature -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] const ED25519_CERT_BADSIG_EXAMPLE: &str = include_str!("examples/id_ed25519-cert-badsig.pub"); /// Ed25519 OpenSSH Certificate with P-256 certificate authority -#[cfg(all(feature = "p256", feature = "fingerprint"))] +#[cfg(feature = "p256")] const ED25519_CERT_WITH_P256_CA_EXAMPLE: &str = include_str!("examples/id_ed25519-cert-with-p256-ca.pub"); /// Ed25519 OpenSSH Certificate with RSA certificate authority -#[cfg(all(feature = "p256", feature = "fingerprint"))] +#[cfg(feature = "p256")] const ED25519_CERT_WITH_RSA_CA_EXAMPLE: &str = include_str!("examples/id_ed25519-cert-with-rsa-ca.pub"); @@ -47,19 +47,19 @@ const SK_ECDSA_P256_CERT_EXAMPLE: &str = include_str!("examples/id_sk_ecdsa_p256 const SK_ED25519_CERT_EXAMPLE: &str = include_str!("examples/id_sk_ed25519-cert.pub"); /// Example certificate authority fingerprint (matches `id_ed25519.pub` example) -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] const CA_FINGERPRINT: &str = "SHA256:UCUiLr7Pjs9wFFJMDByLgc3NrtdU344OgUM45wZPcIQ"; /// Valid certificate timestamp. -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] const VALID_TIMESTAMP: u64 = 1750000000; /// Timestamp which is before the validity window. -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] const PAST_TIMESTAMP: u64 = 1500000000; /// Expired certificate timestamp. -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] const EXPIRED_TIMESTAMP: u64 = 2500000000; #[test] @@ -236,7 +236,7 @@ fn encode_rsa_4096_openssh() { ); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn verify_ed25519_certificate_signature() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -244,14 +244,14 @@ fn verify_ed25519_certificate_signature() { assert!(cert.verify_signature().is_ok()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn reject_ed25519_certificate_with_invalid_signature() { let cert = Certificate::from_str(ED25519_CERT_BADSIG_EXAMPLE).unwrap(); assert!(cert.verify_signature().is_err()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn validate_certificate() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -259,7 +259,7 @@ fn validate_certificate() { assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_ok()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint", feature = "std"))] +#[cfg(all(feature = "ed25519", feature = "std"))] #[test] fn validate_certificate_against_system_clock() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -267,7 +267,7 @@ fn validate_certificate_against_system_clock() { assert!(cert.validate(&[ca]).is_ok()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn reject_certificate_with_invalid_signature() { let cert = Certificate::from_str(ED25519_CERT_BADSIG_EXAMPLE).unwrap(); @@ -275,7 +275,7 @@ fn reject_certificate_with_invalid_signature() { assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_err()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn reject_certificate_with_untrusted_ca() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -287,7 +287,7 @@ fn reject_certificate_with_untrusted_ca() { assert!(cert.validate_at(VALID_TIMESTAMP, &[ca]).is_err()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn reject_expired_certificate() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -295,7 +295,7 @@ fn reject_expired_certificate() { assert!(cert.validate_at(EXPIRED_TIMESTAMP, &[ca]).is_err()); } -#[cfg(all(feature = "ed25519", feature = "fingerprint"))] +#[cfg(feature = "ed25519")] #[test] fn reject_certificate_with_future_valid_after() { let cert = Certificate::from_str(ED25519_CERT_EXAMPLE).unwrap(); @@ -303,7 +303,7 @@ fn reject_certificate_with_future_valid_after() { assert!(cert.validate_at(PAST_TIMESTAMP, &[ca]).is_err()) } -#[cfg(all(feature = "p256", feature = "fingerprint"))] +#[cfg(feature = "p256")] #[test] fn verify_p256_certificate_signature() { let cert = Certificate::from_str(ED25519_CERT_WITH_P256_CA_EXAMPLE).unwrap(); @@ -316,7 +316,7 @@ fn verify_p256_certificate_signature() { assert!(cert.verify_signature().is_ok()); } -#[cfg(all(feature = "rsa", feature = "fingerprint"))] +#[cfg(feature = "rsa")] #[test] fn verify_rsa_certificate_signature() { let cert = Certificate::from_str(ED25519_CERT_WITH_RSA_CA_EXAMPLE).unwrap(); diff --git a/ssh-key/tests/certificate_builder.rs b/ssh-key/tests/certificate_builder.rs index 169bd4c3..def313b9 100644 --- a/ssh-key/tests/certificate_builder.rs +++ b/ssh-key/tests/certificate_builder.rs @@ -1,10 +1,6 @@ //! Certificate builder tests. -#![cfg(all( - feature = "alloc", - feature = "fingerprint", - any(feature = "ed25519", feature = "p256") -))] +#![cfg(all(feature = "alloc", any(feature = "ed25519", feature = "p256")))] use hex_literal::hex; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; diff --git a/ssh-key/tests/public_key.rs b/ssh-key/tests/public_key.rs index c76e6c55..0f78f82c 100644 --- a/ssh-key/tests/public_key.rs +++ b/ssh-key/tests/public_key.rs @@ -77,8 +77,6 @@ fn decode_dsa_openssh() { ); assert_eq!("user@example.com", key.comment()); - - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:Nh0Me49Zh9fDw/VYUfq43IJmI1T+XrjiYONPND8GzaM", &key.fingerprint(Default::default()).to_string(), @@ -109,7 +107,6 @@ fn decode_ecdsa_p256_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:JQ6FV0rf7qqJHZqIj4zNH8eV0oB8KLKh9Pph3FTD98g", &key.fingerprint(Default::default()).to_string(), @@ -141,7 +138,6 @@ fn decode_ecdsa_p384_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:nkGE8oV7pHvOiPKHtQRs67WUPiVLRxbNu//gV/k4Vjw", &key.fingerprint(Default::default()).to_string(), @@ -174,7 +170,6 @@ fn decode_ecdsa_p521_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:l3AUUMK6Q2BbuiqvMx2fs97f8LUYq7sWCAx7q5m3S6M", &key.fingerprint(Default::default()).to_string(), @@ -194,7 +189,6 @@ fn decode_ed25519_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:UCUiLr7Pjs9wFFJMDByLgc3NrtdU344OgUM45wZPcIQ", &key.fingerprint(Default::default()).to_string(), @@ -223,10 +217,7 @@ fn decode_rsa_3072_openssh() { ), rsa_key.n.as_bytes(), ); - assert_eq!("user@example.com", key.comment()); - - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:Fmxts/GcV77PakFnf1Ueki5mpU4ZjUQWGRjZGAo3n/I", &key.fingerprint(Default::default()).to_string(), @@ -258,10 +249,7 @@ fn decode_rsa_4096_openssh() { ), rsa_key.n.as_bytes(), ); - assert_eq!("user@example.com", key.comment()); - - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:FKAyeywtQNZLl1YTzIzCV/ThadBlnWMaD7jHQYDseEY", &key.fingerprint(Default::default()).to_string(), @@ -288,7 +276,6 @@ fn decode_sk_ecdsa_p256_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:UINe2WXFh3SiqwLxsBv34fBO2ei+g7uOeJJXVEK95iE", &key.fingerprint(Default::default()).to_string(), @@ -311,7 +298,6 @@ fn decode_sk_ed25519_openssh() { #[cfg(feature = "alloc")] assert_eq!("user@example.com", key.comment()); - #[cfg(feature = "fingerprint")] assert_eq!( "SHA256:6WZVJ44bqhAWLVP4Ns0TDkoSQSsZo/h2K+mEvOaNFbw", &key.fingerprint(Default::default()).to_string(),