diff --git a/Cargo.toml b/Cargo.toml index e6c1b1c..a8eb12e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ path = "tests/keys/main.rs" [dependencies] aes = "0.8" aes-gcm = "^0.10.0-pre" +base64 = "^0.13" bcrypt-pbkdf = "0.9" bytes = "1.1" cbc = "0.1" diff --git a/examples/client.rs b/examples/client.rs index 7e589b5..17c1164 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -179,7 +179,8 @@ async fn verify_pubkey( accept_tx: makiko::AcceptPubkeySender, ) -> Result<()> { log::info!("verifying server pubkey: {}", pubkey); - let prompt = format!("ssh: server pubkey: {}\nssh: do you want to connect?", pubkey); + let prompt = format!("ssh: server pubkey fingerprint {}\nssh: do you want to connect?", + pubkey.fingerprint()); if ask_yes_no(&prompt).await? { accept_tx.accept(); } else { diff --git a/src/lib.rs b/src/lib.rs index 567c97f..1022907 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,12 +29,12 @@ pub use self::mac::MacAlgo; pub use self::pubkey::{PubkeyAlgo, Pubkey, Privkey}; pub use bytes; +pub use ecdsa; +pub use ecdsa::elliptic_curve; pub use ed25519_dalek; -pub use rsa; pub use p256; pub use p384; -pub use ecdsa; -pub use ecdsa::elliptic_curve; +pub use rsa; pub mod cipher; mod client; diff --git a/src/pubkey/mod.rs b/src/pubkey/mod.rs index cb32bbd..8bfafbc 100644 --- a/src/pubkey/mod.rs +++ b/src/pubkey/mod.rs @@ -91,13 +91,36 @@ impl Pubkey { } } - pub(crate) fn decode(blob: Bytes) -> Result { + /// Decode a public key from SSH wire encoding. + /// + /// This is the encoding initially defined by RFC 4253. For keys other than RSA, the encoding + /// is defined in the RFC that introduces the key type. + pub fn decode(blob: Bytes) -> Result { decode_pubkey(blob) } - pub(crate) fn encode(&self) -> Bytes { + /// Encode a public key into SSH encoding. + /// + /// This is the encoding initially defined by RFC 4253. For keys other than RSA, the encoding + /// is defined in the RFC that introduces the key type. + /// + /// You can use this method to calculate a digest of the public key. + pub fn encode(&self) -> Bytes { encode_pubkey(self) } + + /// Compute a fingerprint of the public key. + /// + /// The fingerprint is in the SHA-256 digest of the public key encoded with base64 (not padded + /// with `=` characters) and prefixed with `SHA256:` (e.g. + /// `"SHA256:eaBPG/rqx+IPa0Lc9KHypkG3UxjmUwerwq9CZ/xpPWM"`). If you need another format, use + /// [`encode()`][Self::encode()] to encode the key into bytes and apply a digest of your + /// choice. + pub fn fingerprint(&self) -> String { + use sha2::Digest; + let digest = sha2::Sha256::digest(self.encode()); + format!("SHA256:{}", base64::encode_config(digest, base64::STANDARD_NO_PAD)) + } } impl fmt::Display for Pubkey { diff --git a/tests/keys/main.rs b/tests/keys/main.rs index 244cb61..12b9ad6 100644 --- a/tests/keys/main.rs +++ b/tests/keys/main.rs @@ -74,7 +74,7 @@ fn check_decode_privkey(expected_privkey: makiko::Privkey, pem_data: &str, passw } #[test] -#[should_panic] // this is not implemented +#[should_panic] // this functionality is not implemented fn test_decode_encrypted_rsa_aes128_gcm() { // the `cryptography` library in Python does not support keys encrypted using aes128-gcm, so // the keys.rs file does not contain `encrypted_rsa_aes128_gcm()` @@ -85,3 +85,26 @@ fn test_decode_encrypted_rsa_aes128_gcm() { assert_eq!(decoded.pubkey, decoded.privkey.pubkey()); } +fn check_fingerprint(privkey: makiko::Privkey, expected: &str) { + let fingerprint = privkey.pubkey().fingerprint(); + assert_eq!(fingerprint, expected); +} + +#[test] fn test_fingerprint_alice_ed25519() { + check_fingerprint(keys::alice_ed25519(), "SHA256:sBkOpFO1h8D6+8mvKFvAgaHSFLjrG3LMeXDhST/qXwY"); +} +#[test] fn test_fingerprint_ruth_rsa_1024() { + check_fingerprint(keys::ruth_rsa_1024(), "SHA256:JKKfprhKd9n4BaqMcwQmdrtaMxxvaYAi3LEwfsl/j10"); +} +#[test] fn test_fingerprint_ruth_rsa_2048() { + check_fingerprint(keys::ruth_rsa_2048(), "SHA256:f7yXzeoej4cteCs7EipdN2+GPWRLgtleYTpDDQzNybk"); +} +#[test] fn test_fingerprint_ruth_rsa_4096() { + check_fingerprint(keys::ruth_rsa_4096(), "SHA256:eaBPG/rqx+IPa0Lc9KHypkG3UxjmUwerwq9CZ/xpPWM"); +} +#[test] fn test_fingerprint_eda_ecdsa_p256() { + check_fingerprint(keys::eda_ecdsa_p256(), "SHA256:c8EWc8omrSNzIK2ipOLWju6F9Do4ypK+mf3RRbgOCXw"); +} +#[test] fn test_fingerprint_eda_ecdsa_p384() { + check_fingerprint(keys::eda_ecdsa_p384(), "SHA256:8vBuizZHVX0885H8gCJQTzpf73/S9y3vT3VAHtuBikY"); +}