diff --git a/spki/src/fingerprint.rs b/spki/src/fingerprint.rs new file mode 100644 index 000000000..6a3901fb1 --- /dev/null +++ b/spki/src/fingerprint.rs @@ -0,0 +1,43 @@ +//! SPKI fingerprint support. + +use der::Writer; +use sha2::{Digest, Sha256}; + +/// Size of a SHA-256 SPKI fingerprint in bytes. +pub(crate) const SIZE: usize = 32; + +/// Raw bytes of a SPKI fingerprint i.e. SHA-256 digest of +/// `SubjectPublicKeyInfo`'s DER encoding. +/// +/// See [RFC7469 § 2.1.1] for more information. +/// +/// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1 +#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] +pub type FingerprintBytes = [u8; SIZE]; + +/// Writer newtype which accepts DER being serialized on-the-fly and computes a +/// hash of the contents. +#[derive(Clone, Default)] +pub(crate) struct Builder { + /// In-progress digest being computed from streaming DER. + digest: Sha256, +} + +impl Builder { + /// Create a new fingerprint builder. + pub fn new() -> Self { + Self::default() + } + + /// Finish computing a fingerprint, returning the computed digest. + pub fn finish(self) -> FingerprintBytes { + self.digest.finalize().into() + } +} + +impl Writer for Builder { + fn write(&mut self, der_bytes: &[u8]) -> der::Result<()> { + self.digest.update(der_bytes); + Ok(()) + } +} diff --git a/spki/src/lib.rs b/spki/src/lib.rs index d76bc10ce..f4c8fe08c 100644 --- a/spki/src/lib.rs +++ b/spki/src/lib.rs @@ -38,6 +38,9 @@ mod error; mod spki; mod traits; +#[cfg(feature = "fingerprint")] +mod fingerprint; + pub use crate::{ algorithm::AlgorithmIdentifier, error::{Error, Result}, @@ -48,3 +51,6 @@ pub use der::{self, asn1::ObjectIdentifier}; #[cfg(feature = "alloc")] pub use {crate::traits::EncodePublicKey, der::Document}; + +#[cfg(feature = "fingerprint")] +pub use crate::fingerprint::FingerprintBytes; diff --git a/spki/src/spki.rs b/spki/src/spki.rs index e8bcf56d7..c7961c309 100644 --- a/spki/src/spki.rs +++ b/spki/src/spki.rs @@ -8,7 +8,7 @@ use der::{asn1::BitString, Decode, Decoder, DerOrd, Encode, Sequence, ValueOrd}; use der::Document; #[cfg(feature = "fingerprint")] -use sha2::{digest, Digest, Sha256}; +use crate::{fingerprint, FingerprintBytes}; #[cfg(all(feature = "alloc", feature = "fingerprint"))] use { @@ -19,7 +19,7 @@ use { #[cfg(feature = "pem")] use der::pem::PemLabel; -/// X.509 `SubjectPublicKeyInfo` (SPKI) as defined in [RFC 5280 Section 4.1.2.7]. +/// X.509 `SubjectPublicKeyInfo` (SPKI) as defined in [RFC 5280 § 4.1.2.7]. /// /// ASN.1 structure containing an [`AlgorithmIdentifier`] and public key /// data in an algorithm specific format. @@ -30,7 +30,7 @@ use der::pem::PemLabel; /// subjectPublicKey BIT STRING } /// ``` /// -/// [RFC 5280 Section 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7 +/// [RFC 5280 § 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7 #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct SubjectPublicKeyInfo<'a> { /// X.509 [`AlgorithmIdentifier`] for the public key type @@ -41,24 +41,34 @@ pub struct SubjectPublicKeyInfo<'a> { } impl<'a> SubjectPublicKeyInfo<'a> { - /// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`]. - #[cfg(feature = "fingerprint")] - #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] - pub fn fingerprint(&self) -> Result> { - let mut buf = [0u8; 4096]; - Ok(Sha256::digest(self.encode_to_slice(&mut buf)?)) - } - /// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`] and /// encode it as a Base64 string. + /// + /// See [RFC7469 § 2.1.1] for more information. + /// + /// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1 #[cfg(all(feature = "fingerprint", feature = "alloc"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "fingerprint", feature = "alloc"))))] pub fn fingerprint_base64(&self) -> Result { - Ok(Base64::encode_string(self.fingerprint()?.as_slice())) + Ok(Base64::encode_string(&self.fingerprint_bytes()?)) + } + + /// Calculate the SHA-256 fingerprint of this [`SubjectPublicKeyInfo`] as + /// a raw byte array. + /// + /// See [RFC7469 § 2.1.1] for more information. + /// + /// [RFC7469 § 2.1.1]: https://datatracker.ietf.org/doc/html/rfc7469#section-2.1.1 + #[cfg(feature = "fingerprint")] + #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))] + pub fn fingerprint_bytes(&self) -> Result { + let mut builder = fingerprint::Builder::new(); + self.encode(&mut builder)?; + Ok(builder.finish()) } /// Get a [`BitString`] representing the `subject_public_key` - fn subject_public_key_bitstring(&self) -> der::Result> { + fn bitstring(&self) -> der::Result> { BitString::from_bytes(self.subject_public_key) } } @@ -85,7 +95,7 @@ impl<'a> Sequence<'a> for SubjectPublicKeyInfo<'a> { where F: FnOnce(&[&dyn Encode]) -> der::Result, { - f(&[&self.algorithm, &self.subject_public_key_bitstring()?]) + f(&[&self.algorithm, &self.bitstring()?]) } } @@ -100,9 +110,7 @@ impl<'a> TryFrom<&'a [u8]> for SubjectPublicKeyInfo<'a> { impl ValueOrd for SubjectPublicKeyInfo<'_> { fn value_cmp(&self, other: &Self) -> der::Result { match self.algorithm.der_cmp(&other.algorithm)? { - Ordering::Equal => self - .subject_public_key_bitstring()? - .der_cmp(&other.subject_public_key_bitstring()?), + Ordering::Equal => self.bitstring()?.der_cmp(&other.bitstring()?), other => Ok(other), } } diff --git a/spki/tests/spki.rs b/spki/tests/spki.rs index ec51bd987..3d6b19f48 100644 --- a/spki/tests/spki.rs +++ b/spki/tests/spki.rs @@ -73,7 +73,7 @@ fn decode_ed25519_and_fingerprint_spki() { // Check the fingerprint assert_eq!( - spki.fingerprint().unwrap().as_slice(), + spki.fingerprint_bytes().unwrap().as_slice(), ED25519_SPKI_FINGERPRINT ); }