From acbcc7e62b26e83c5e78c26eb145ee64a19d965a Mon Sep 17 00:00:00 2001 From: Noa Resare Date: Fri, 11 Aug 2023 19:39:37 +0100 Subject: [PATCH] ssh-key: have PublicKey and its parts implment Hash (#145) This change derives the Hash trait for PublicKey and it's constitutent parts. Since Mpint implements its own PartialEq, an explicit Hash implmenetation delegating to the backing byte vector is added. A test verifying that a HashSet containing a public key can indeed be created and queried is also added. --- ssh-key/src/mpint.rs | 7 +++++++ ssh-key/src/public.rs | 2 +- ssh-key/src/public/dsa.rs | 2 +- ssh-key/src/public/ecdsa.rs | 2 +- ssh-key/src/public/key_data.rs | 2 +- ssh-key/src/public/opaque.rs | 4 ++-- ssh-key/src/public/rsa.rs | 2 +- ssh-key/src/public/sk.rs | 4 ++-- ssh-key/tests/public_key.rs | 8 ++++++++ 9 files changed, 24 insertions(+), 9 deletions(-) diff --git a/ssh-key/src/mpint.rs b/ssh-key/src/mpint.rs index f3b047c..d632b44 100644 --- a/ssh-key/src/mpint.rs +++ b/ssh-key/src/mpint.rs @@ -3,6 +3,7 @@ use crate::{Error, Result}; use alloc::vec::Vec; use core::fmt; +use core::hash::{Hash, Hasher}; use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroize; @@ -114,6 +115,12 @@ impl PartialEq for Mpint { } } +impl Hash for Mpint { + fn hash(&self, state: &mut H) { + self.inner.hash(state) + } +} + impl Decode for Mpint { type Error = Error; diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index 50d9582..28b4ee3 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -78,7 +78,7 @@ use std::{fs, path::Path}; /// The serialization uses a binary encoding with binary formats like bincode /// and CBOR, and the OpenSSH string serialization when used with /// human-readable formats like JSON and TOML. -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct PublicKey { /// Key data. pub(crate) key_data: KeyData, diff --git a/ssh-key/src/public/dsa.rs b/ssh-key/src/public/dsa.rs index fefe488..b1c3bbb 100644 --- a/ssh-key/src/public/dsa.rs +++ b/ssh-key/src/public/dsa.rs @@ -6,7 +6,7 @@ use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; /// Digital Signature Algorithm (DSA) public key. /// /// Described in [FIPS 186-4 § 4.1](https://csrc.nist.gov/publications/detail/fips/186/4/final). -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DsaPublicKey { /// Prime modulus. pub p: Mpint, diff --git a/ssh-key/src/public/ecdsa.rs b/ssh-key/src/public/ecdsa.rs index 889c7ec..997274c 100644 --- a/ssh-key/src/public/ecdsa.rs +++ b/ssh-key/src/public/ecdsa.rs @@ -20,7 +20,7 @@ pub type EcdsaNistP521PublicKey = sec1::EncodedPoint; /// `sec1` feature of this crate is enabled (which it is by default). /// /// Described in [FIPS 186-4](https://csrc.nist.gov/publications/detail/fips/186/4/final). -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum EcdsaPublicKey { /// NIST P-256 ECDSA public key. NistP256(EcdsaNistP256PublicKey), diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs index 83b1510..176dc99 100644 --- a/ssh-key/src/public/key_data.rs +++ b/ssh-key/src/public/key_data.rs @@ -11,7 +11,7 @@ use super::{DsaPublicKey, OpaquePublicKey, RsaPublicKey}; use super::{EcdsaPublicKey, SkEcdsaSha2NistP256}; /// Public key data. -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Hash, Ord, Eq, PartialEq, PartialOrd)] #[non_exhaustive] pub enum KeyData { /// Digital Signature Algorithm (DSA) public key data. diff --git a/ssh-key/src/public/opaque.rs b/ssh-key/src/public/opaque.rs index b0c6411..0af61b8 100644 --- a/ssh-key/src/public/opaque.rs +++ b/ssh-key/src/public/opaque.rs @@ -16,7 +16,7 @@ use encoding::{Decode, Encode, Reader, Writer}; /// /// The encoded representation of an `OpaquePublicKey` is the encoded representation of its /// [`OpaquePublicKeyBytes`]. -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct OpaquePublicKey { /// The [`Algorithm`] of this public key. pub algorithm: Algorithm, @@ -28,7 +28,7 @@ pub struct OpaquePublicKey { /// /// The encoded representation of an `OpaquePublicKeyBytes` consists of a 4-byte length prefix, /// followed by its byte representation. -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct OpaquePublicKeyBytes(Vec); impl OpaquePublicKey { diff --git a/ssh-key/src/public/rsa.rs b/ssh-key/src/public/rsa.rs index 93935ac..e49d9c3 100644 --- a/ssh-key/src/public/rsa.rs +++ b/ssh-key/src/public/rsa.rs @@ -13,7 +13,7 @@ use { /// RSA public key. /// /// Described in [RFC4253 § 6.6](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6). -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct RsaPublicKey { /// RSA public exponent. pub e: Mpint, diff --git a/ssh-key/src/public/sk.rs b/ssh-key/src/public/sk.rs index deb759b..03ff599 100644 --- a/ssh-key/src/public/sk.rs +++ b/ssh-key/src/public/sk.rs @@ -18,7 +18,7 @@ const DEFAULT_APPLICATION_STRING: &str = "ssh:"; /// Security Key (FIDO/U2F) ECDSA/NIST P-256 public key as specified in /// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD). #[cfg(feature = "ecdsa")] -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] pub struct SkEcdsaSha2NistP256 { /// Elliptic curve point representing a public key. ec_point: EcdsaNistP256PublicKey, @@ -112,7 +112,7 @@ impl From for EcdsaNistP256PublicKey { /// Security Key (FIDO/U2F) Ed25519 public key as specified in /// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD). -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct SkEd25519 { /// Ed25519 public key. public_key: Ed25519PublicKey, diff --git a/ssh-key/tests/public_key.rs b/ssh-key/tests/public_key.rs index 05e235f..e536b82 100644 --- a/ssh-key/tests/public_key.rs +++ b/ssh-key/tests/public_key.rs @@ -2,6 +2,7 @@ use hex_literal::hex; use ssh_key::{Algorithm, PublicKey}; +use std::collections::HashSet; #[cfg(feature = "ecdsa")] use ssh_key::EcdsaCurve; @@ -378,3 +379,10 @@ fn encode_rsa_4096_openssh() { let key = PublicKey::from_openssh(OPENSSH_RSA_4096_EXAMPLE).unwrap(); assert_eq!(OPENSSH_RSA_4096_EXAMPLE.trim_end(), &key.to_string()); } + +#[test] +fn public_keys_are_hashable() { + let key = PublicKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let set = HashSet::from([&key]); + assert_eq!(true, set.contains(&key)); +}