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
43 changes: 43 additions & 0 deletions spki/src/fingerprint.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
6 changes: 6 additions & 0 deletions spki/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ mod error;
mod spki;
mod traits;

#[cfg(feature = "fingerprint")]
mod fingerprint;

pub use crate::{
algorithm::AlgorithmIdentifier,
error::{Error, Result},
Expand All @@ -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;
42 changes: 25 additions & 17 deletions spki/src/spki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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<digest::Output<Sha256>> {
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<String> {
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<FingerprintBytes> {
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<BitString<'a>> {
fn bitstring(&self) -> der::Result<BitString<'a>> {
BitString::from_bytes(self.subject_public_key)
}
}
Expand All @@ -85,7 +95,7 @@ impl<'a> Sequence<'a> for SubjectPublicKeyInfo<'a> {
where
F: FnOnce(&[&dyn Encode]) -> der::Result<T>,
{
f(&[&self.algorithm, &self.subject_public_key_bitstring()?])
f(&[&self.algorithm, &self.bitstring()?])
}
}

Expand All @@ -100,9 +110,7 @@ impl<'a> TryFrom<&'a [u8]> for SubjectPublicKeyInfo<'a> {
impl ValueOrd for SubjectPublicKeyInfo<'_> {
fn value_cmp(&self, other: &Self) -> der::Result<Ordering> {
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),
}
}
Expand Down
2 changes: 1 addition & 1 deletion spki/tests/spki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
Expand Down