Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/ssh-key.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@ jobs:
- run: cargo test --release
#- run: cargo test --release --features getrandom # TODO(tarcieri): fix `getrandom` feature
- run: cargo test --release --features std
- run: cargo test --no-default-features --features ecdsa,rand_core,std # debug build without `sha2`
- run: cargo test --all-features # debug build
- run: cargo test --release --all-features
13 changes: 7 additions & 6 deletions ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ rust-version = "1.85"
cipher = { package = "ssh-cipher", version = "0.3", features = ["encoding", "zeroize"] }
ctutils = { version = "0.4", default-features = false }
encoding = { package = "ssh-encoding", version = "0.3", features = ["base64", "digest", "pem", "ctutils", "zeroize"] }
sha2 = { version = "0.11", default-features = false }
signature = { version = "3", default-features = false }
zeroize = { version = "1.9", default-features = false }

Expand All @@ -40,21 +39,22 @@ rsa = { version = "0.10.0-rc.18", optional = true, default-features = false, fea
sec1 = { version = "0.8", optional = true, default-features = false, features = ["point"] }
serde = { version = "1.0.16", optional = true }
sha1 = { version = "0.11", optional = true, default-features = false, features = ["oid"] }
sha2 = { version = "0.11", optional = true, default-features = false }

[dev-dependencies]
hex-literal = "1"
chacha20 = { version = "0.10", features = ["rng"] }
getrandom = { version = "0.4", features = ["sys_rng"] }

[features]
default = ["ecdsa", "rand_core", "std"]
default = ["ecdsa", "rand_core", "sha2", "std"]
alloc = ["encoding/alloc", "signature/alloc", "zeroize/alloc", ]
std = ["alloc"]

crypto = ["ed25519", "p256", "p384", "p521", "rsa"] # NOTE: `dsa` is obsolete/weak
dsa = ["dep:dsa", "dep:sha1", "alloc", "encoding/bigint", "signature/rand_core"]
ecdsa = ["dep:sec1"]
ed25519 = ["dep:ed25519-dalek", "rand_core"]
ed25519 = ["dep:ed25519-dalek", "rand_core", "sha2"]
encryption = [
"dep:bcrypt-pbkdf",
"alloc",
Expand All @@ -63,13 +63,14 @@ encryption = [
"rand_core"
]
getrandom = ["cipher/getrandom", "rand_core"]
p256 = ["dep:p256", "ecdsa"]
p256 = ["dep:p256", "ecdsa", "sha2"]
p384 = ["dep:p384", "ecdsa"]
p521 = ["dep:p521", "ecdsa"]
ppk = ["dep:hex", "alloc", "cipher/aes", "dep:hmac", "dep:argon2", "dep:sha1"]
ppk = ["dep:hex", "alloc", "cipher/aes", "dep:hmac", "dep:argon2", "dep:sha1", "sha2"]
rand_core = ["dep:rand_core", "cipher/rand_core"]
rsa = ["dep:rsa", "alloc", "encoding/bigint", "rand_core"]
rsa = ["dep:rsa", "alloc", "encoding/bigint", "rand_core", "sha2"]
sha1 = ["dep:sha1"]
sha2 = ["dep:sha2"]
tdes = ["cipher/tdes", "encryption"]

[lints]
Expand Down
8 changes: 8 additions & 0 deletions ssh-key/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ When the `ed25519`, `p256`, and/or `rsa` features of this crate are enabled,
provides key generation and certificate signing/verification support for that
respective SSH key algorithm.

Key fingerprints, certificate validation, and `sshsig` signing/verification of
messages compute SHA-2 hashes using the `sha2` crate, and are only available
when the `sha2` feature is enabled. It's on by default and implied by the
`ed25519`, `p256`, `ppk`, and `rsa` features. With it disabled, `sshsig`
signatures can still be created and verified from a digest computed by another
SHA-2 implementation, using the `*_prehash` methods, or the `*_digest` methods
with a type that implements `AssociatedHashAlg`.

## Features

- [x] Constant-time Base64 decoder/encoder using `base64ct`/`pem-rfc7468` crates
Expand Down
15 changes: 11 additions & 4 deletions ssh-key/src/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ mod name;

use crate::{Error, Result};
use core::{fmt, str};
use encoding::{Label, LabelError};
use sha2::{Digest, Sha256, Sha512};
use encoding::{Label, LabelError, digest::Digest};

#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, string::String, vec::Vec};
use alloc::{borrow::ToOwned, string::String};

#[cfg(all(feature = "alloc", feature = "sha2"))]
use alloc::vec::Vec;

#[cfg(feature = "sha2")]
use sha2::{Sha256, Sha512};

#[cfg(feature = "alloc")]
pub use name::AlgorithmName;
Expand Down Expand Up @@ -457,7 +462,7 @@ impl HashAlg {
}

/// Compute a digest of the given message using this hash function.
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", feature = "sha2"))]
#[must_use]
pub fn digest(self, msg: &[u8]) -> Vec<u8> {
match self {
Expand Down Expand Up @@ -499,10 +504,12 @@ pub trait AssociatedHashAlg: Digest {
const HASH_ALG: HashAlg;
}

#[cfg(feature = "sha2")]
impl AssociatedHashAlg for Sha256 {
const HASH_ALG: HashAlg = HashAlg::Sha256;
}

#[cfg(feature = "sha2")]
impl AssociatedHashAlg for Sha512 {
const HASH_ALG: HashAlg = HashAlg::Sha512;
}
Expand Down
8 changes: 6 additions & 2 deletions ssh-key/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod unix_time;
pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map::OptionsMap};

use crate::{
Algorithm, Comment, Error, Fingerprint, HashAlg, Result, Signature,
Algorithm, Comment, Error, Result, Signature,
public::{KeyData, SshFormat},
};
use alloc::{
Expand All @@ -23,6 +23,9 @@ use signature::Verifier;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize, de, ser};

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

#[cfg(feature = "std")]
use {
self::unix_time::UnixTime,
Expand Down Expand Up @@ -396,7 +399,7 @@ impl Certificate {
///
/// # Errors
/// Returns [`Error::CertificateValidation`] if the certificate failed to validate
#[cfg(feature = "std")]
#[cfg(all(feature = "sha2", feature = "std"))]
pub fn validate<'a, I>(&self, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
Expand Down Expand Up @@ -431,6 +434,7 @@ impl Certificate {
///
/// # Errors
/// Returns [`Error::CertificateValidation`] if the certificate failed to validate
#[cfg(feature = "sha2")]
pub fn validate_at<'a, I>(&self, unix_timestamp: u64, ca_fingerprints: I) -> Result<()>
where
I: IntoIterator<Item = &'a Fingerprint>,
Expand Down
5 changes: 4 additions & 1 deletion ssh-key/src/certificate/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,15 @@ impl Builder {
cert.encode_tbs(&mut tbs_cert)?;
cert.signature = signing_key.try_sign(&tbs_cert)?;

#[cfg(debug_assertions)]
#[cfg(all(debug_assertions, feature = "sha2"))]
cert.validate_at(
cert.valid_after,
&[cert.signature_key.fingerprint(Default::default())],
)?;

#[cfg(all(debug_assertions, not(feature = "sha2")))]
cert.verify_signature()?;

Ok(cert)
}
}
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 @@ -73,7 +73,7 @@ impl UnixTime {
}

/// Get the current time as a Unix timestamp.
#[cfg(feature = "std")]
#[cfg(all(feature = "sha2", feature = "std"))]
pub(super) fn now() -> Result<Self> {
SystemTime::now().try_into()
}
Expand Down
7 changes: 6 additions & 1 deletion ssh-key/src/dot_ssh.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! `~/.ssh` support.

use crate::{Fingerprint, PrivateKey, PublicKey, Result};
use crate::{PrivateKey, PublicKey, Result};
use core::fmt::{self, Debug};
use std::{
env,
fs::{self, ReadDir},
path::{Path, PathBuf},
};

#[cfg(feature = "sha2")]
use crate::Fingerprint;

#[cfg(doc)]
use crate::Error;

Expand Down Expand Up @@ -62,6 +65,7 @@ impl DotSsh {
}

/// Find a private key whose public key has the given key fingerprint.
#[cfg(feature = "sha2")]
#[must_use]
pub fn private_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PrivateKey> {
self.private_keys()
Expand All @@ -80,6 +84,7 @@ impl DotSsh {
}

/// Find a public key with the given key fingerprint.
#[cfg(feature = "sha2")]
#[must_use]
pub fn public_key_with_fingerprint(&self, fingerprint: Fingerprint) -> Option<PublicKey> {
self.public_keys()
Expand Down
16 changes: 10 additions & 6 deletions ssh-key/src/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@
mod randomart;

use self::randomart::Randomart;
use crate::{Error, HashAlg, Result, public};
use crate::{Error, HashAlg, Result};
use core::{
fmt::{self, Display},
str::{self, FromStr},
};
use encoding::{
DigestWriter, Encode,
base64::{Base64Unpadded, Encoding},
};
use sha2::{Digest, Sha256, Sha512};
use encoding::base64::{Base64Unpadded, Encoding};

#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{Deserialize, Serialize, de, ser};
#[cfg(feature = "sha2")]
use {
crate::public,
encoding::{DigestWriter, Encode},
sha2::{Digest, Sha256, Sha512},
};

/// Fingerprint encoding error message.
#[cfg(feature = "sha2")]
const FINGERPRINT_ERR_MSG: &str = "fingerprint encoding error";

/// SSH public key fingerprints.
Expand Down Expand Up @@ -58,6 +61,7 @@ impl Fingerprint {

/// Create a fingerprint of the given public key data using the provided
/// hash algorithm.
#[cfg(feature = "sha2")]
#[must_use]
#[allow(clippy::missing_panics_doc, reason = "should not panic")]
pub fn new(algorithm: HashAlg, public_key: &public::KeyData) -> Self {
Expand Down
3 changes: 2 additions & 1 deletion ssh-key/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ pub use crate::{
};
pub use cipher::{self, Cipher};
pub use encoding::{self, pem::LineEnding};
pub use sha2;

#[cfg(feature = "std")]
pub use crate::dot_ssh::DotSsh;
Expand All @@ -167,6 +166,8 @@ pub use cipher::cipher::common::getrandom;
pub use rand_core;
#[cfg(feature = "ecdsa")]
pub use sec1;
#[cfg(feature = "sha2")]
pub use sha2;
#[cfg(feature = "alloc")]
pub use {
crate::{
Expand Down
13 changes: 10 additions & 3 deletions ssh-key/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ pub use self::{
#[cfg(feature = "alloc")]
pub use crate::{
Comment, SshSig,
encoding::digest::Digest,
private::{
dsa::{DsaKeypair, DsaPrivateKey},
opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
rsa::{RsaKeypair, RsaPrivateKey},
sk::SkEd25519,
},
sha2::Digest,
};

#[cfg(feature = "ecdsa")]
Expand All @@ -129,7 +129,7 @@ pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub use self::sk::SkEcdsaSha2NistP256;

use crate::{Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, public};
use crate::{Algorithm, Cipher, Error, Kdf, PublicKey, Result, public};
use cipher::Tag;
use core::str;
use ctutils::{Choice, CtEq};
Expand All @@ -151,6 +151,12 @@ use rand_core::TryCryptoRng;
#[cfg(feature = "rand_core")]
use rand_core::CryptoRng;

#[cfg(feature = "sha2")]
use crate::Fingerprint;

#[cfg(any(feature = "alloc", feature = "sha2"))]
use crate::HashAlg;

#[cfg(feature = "std")]
use std::{fs::File, path::Path};

Expand Down Expand Up @@ -354,7 +360,7 @@ impl PrivateKey {
///
/// # Errors
/// Propagates errors from [`SshSig::sign`].
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", feature = "sha2"))]
pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
SshSig::sign(self, namespace, hash_alg, msg)
}
Expand Down Expand Up @@ -569,6 +575,7 @@ impl PrivateKey {
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
#[cfg(feature = "sha2")]
#[must_use]
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.public_key.fingerprint(hash_alg)
Expand Down
11 changes: 7 additions & 4 deletions ssh-key/src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};

pub(crate) use self::ssh_format::SshFormat;

use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use crate::{Algorithm, Error, Result};
use core::str::{self, FromStr};
use encoding::{Base64Reader, Decode, Reader};

Expand All @@ -41,13 +41,15 @@ use {
string::{String, ToString},
vec::Vec,
},
encoding::Encode,
sha2::Digest,
encoding::{Encode, digest::Digest},
};

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

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

#[cfg(feature = "std")]
use std::{fs::File, path::Path};

Expand Down Expand Up @@ -251,7 +253,7 @@ impl PublicKey {
/// - Returns [`Error::PublicKey`] if this key does not match the one in the signature.
/// - Returns [`Error::Namespace`] if the provided `namespace` does not match the signature.
/// - Returns [`Error::Signature`] in the event signature verification failed.
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", feature = "sha2"))]
pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
self.verify_prehash(
namespace,
Expand Down Expand Up @@ -378,6 +380,7 @@ impl PublicKey {
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
#[cfg(feature = "sha2")]
#[must_use]
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.key_data.fingerprint(hash_alg)
Expand Down
6 changes: 5 additions & 1 deletion ssh-key/src/public/key_data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Public key data.

use super::{Ed25519PublicKey, SkEd25519};
use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use crate::{Algorithm, Error, Result};
use encoding::{CheckedSum, Decode, Encode, Reader, Writer};

#[cfg(feature = "alloc")]
Expand All @@ -14,6 +14,9 @@ use {
#[cfg(feature = "ecdsa")]
use super::{EcdsaPublicKey, SkEcdsaSha2NistP256};

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

/// Public key data.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
Expand Down Expand Up @@ -112,6 +115,7 @@ impl KeyData {
/// Compute key fingerprint.
///
/// Use [`Default::default()`] to use the default hash function (SHA-256).
#[cfg(feature = "sha2")]
#[must_use]
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
Fingerprint::new(hash_alg, self)
Expand Down
Loading
Loading