Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }

Expand All @@ -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",
Expand All @@ -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
Expand Down
16 changes: 4 additions & 12 deletions ssh-key/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,15 @@ 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,
string::{String, ToString},
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};
Expand Down Expand Up @@ -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<Item = &'a Fingerprint>,
Expand Down Expand Up @@ -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<Item = &'a Fingerprint>,
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion ssh-key/src/certificate/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())],
Expand Down
2 changes: 1 addition & 1 deletion ssh-key/src/certificate/unix_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
SystemTime::now().try_into()
}
Expand Down
1 change: 0 additions & 1 deletion ssh-key/src/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 3 additions & 5 deletions ssh-key/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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::{
Expand All @@ -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;
7 changes: 1 addition & 6 deletions ssh-key/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -149,9 +149,6 @@ use {
zeroize::Zeroizing,
};

#[cfg(feature = "fingerprint")]
use crate::{Fingerprint, HashAlg};

#[cfg(feature = "rand_core")]
use rand_core::{CryptoRng, RngCore};

Expand Down Expand Up @@ -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)
}
Expand Down
7 changes: 1 addition & 6 deletions ssh-key/src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -41,9 +41,6 @@ use {
},
};

#[cfg(feature = "fingerprint")]
use crate::{Fingerprint, HashAlg};

#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};

Expand Down Expand Up @@ -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)
}
Expand Down
7 changes: 1 addition & 6 deletions ssh-key/src/public/key_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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]
Expand Down Expand Up @@ -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)
}
Expand Down
6 changes: 1 addition & 5 deletions ssh-key/src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -50,15 +48,13 @@ impl Writer for Vec<u8> {
}
}

#[cfg(feature = "fingerprint")]
impl Writer for Sha256 {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.update(bytes);
Ok(())
}
}

#[cfg(feature = "fingerprint")]
impl Writer for Sha512 {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.update(bytes);
Expand Down
34 changes: 17 additions & 17 deletions ssh-key/tests/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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]
Expand Down Expand Up @@ -236,46 +236,46 @@ 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();
assert_eq!(Algorithm::Ed25519, cert.signature_key().algorithm());
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();
let ca = CA_FINGERPRINT.parse().unwrap();
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();
let ca = CA_FINGERPRINT.parse().unwrap();
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();
let ca = CA_FINGERPRINT.parse().unwrap();
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();
Expand All @@ -287,23 +287,23 @@ 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();
let ca = CA_FINGERPRINT.parse().unwrap();
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();
let ca = CA_FINGERPRINT.parse().unwrap();
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();
Expand All @@ -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();
Expand Down
Loading