Skip to main content

synta_certificate/crypto/
key_id.rs

1//! X.509 key identifier hashing (RFC 5280 / RFC 7093).
2
3use super::errors::{NoKeyIdHasherError, PrivateKeyError};
4
5// ── KeyIdMethod ───────────────────────────────────────────────────────────────
6
7/// Method for computing an X.509 key identifier value.
8///
9/// RFC 5280 §4.2.1.2 originally defined a single method (SHA-1 of the
10/// `subjectPublicKey` BIT STRING value).  RFC 7093 adds four supplementary
11/// methods covering stronger hash algorithms and the full
12/// `SubjectPublicKeyInfo` DER encoding as input:
13///
14/// | Method | Input | Hash | Output |
15/// |--------|-------|------|--------|
16/// | RFC 5280 | BIT STRING value of `subjectPublicKey` | SHA-1 | full 20 bytes |
17/// | RFC 7093 §2 m1 | BIT STRING value of `subjectPublicKey` | SHA-256 | leftmost 160 bits (20 bytes) |
18/// | RFC 7093 §2 m2 | BIT STRING value of `subjectPublicKey` | SHA-384 | leftmost 160 bits (20 bytes) |
19/// | RFC 7093 §2 m3 | BIT STRING value of `subjectPublicKey` | SHA-512 | leftmost 160 bits (20 bytes) |
20/// | RFC 7093 §2 m4 | Full `SubjectPublicKeyInfo` DER encoding | configurable | full hash bytes |
21#[derive(Clone, Debug)]
22pub enum KeyIdMethod {
23    /// RFC 5280 §4.2.1.2: SHA-1 of the BIT STRING value of `subjectPublicKey` (full 20 bytes).
24    Rfc5280Sha1,
25    /// RFC 7093 §2 method 1: SHA-256 of the BIT STRING value, leftmost 160 bits (20 bytes).
26    Rfc7093Method1Sha256,
27    /// RFC 7093 §2 method 2: SHA-384 of the BIT STRING value, leftmost 160 bits (20 bytes).
28    Rfc7093Method2Sha384,
29    /// RFC 7093 §2 method 3: SHA-512 of the BIT STRING value, leftmost 160 bits (20 bytes).
30    Rfc7093Method3Sha512,
31    /// RFC 7093 §2 method 4: hash of the full `SubjectPublicKeyInfo` DER encoding.
32    ///
33    /// `algorithm_oid` selects the hash algorithm; the full hash value is used
34    /// (no truncation).  The RFC recommends SHA-256 as the example algorithm.
35    Rfc7093Method4 { algorithm_oid: Vec<u32> },
36}
37
38impl KeyIdMethod {
39    /// OID component array of the hash algorithm used by this method.
40    pub fn algorithm_oid(&self) -> &[u32] {
41        match self {
42            Self::Rfc5280Sha1 => crate::ID_SHA1,
43            Self::Rfc7093Method1Sha256 => crate::ID_SHA256,
44            Self::Rfc7093Method2Sha384 => crate::ID_SHA384,
45            Self::Rfc7093Method3Sha512 => crate::ID_SHA512,
46            Self::Rfc7093Method4 { algorithm_oid } => algorithm_oid.as_slice(),
47        }
48    }
49
50    /// Whether this method hashes the full `SubjectPublicKeyInfo` DER encoding
51    /// rather than the BIT STRING value of `subjectPublicKey`.
52    ///
53    /// Returns `true` only for [`Rfc7093Method4`][Self::Rfc7093Method4].
54    pub fn uses_full_spki_der(&self) -> bool {
55        matches!(self, Self::Rfc7093Method4 { .. })
56    }
57
58    /// Apply the output-length rule for this method.
59    ///
60    /// RFC 7093 methods 1–3 truncate the hash to its leftmost 160 bits
61    /// (20 bytes).  All other methods return the full hash unchanged.
62    pub fn apply_output_length(&self, hash: Vec<u8>) -> Vec<u8> {
63        match self {
64            Self::Rfc7093Method1Sha256
65            | Self::Rfc7093Method2Sha384
66            | Self::Rfc7093Method3Sha512 => hash.into_iter().take(20).collect(),
67            _ => hash,
68        }
69    }
70}
71
72// ── KeyIdHasher ───────────────────────────────────────────────────────────────
73
74/// Compute a cryptographic hash for use as an X.509 key identifier.
75///
76/// Implementations call into a crypto backend (e.g. OpenSSL) to compute
77/// the hash requested by a [`KeyIdMethod`].  Implementations should support
78/// at minimum:
79///
80/// - `ID_SHA1` — required for [`KeyIdMethod::Rfc5280Sha1`]
81/// - `ID_SHA256`, `ID_SHA384`, `ID_SHA512` — required for RFC 7093 methods 1–4
82///
83/// The hash result is returned in full; any truncation required by a specific
84/// method is applied by the caller via [`KeyIdMethod::apply_output_length`].
85pub trait KeyIdHasher {
86    /// Error type returned on hash failure.
87    type Error: std::error::Error + Send + Sync + 'static;
88
89    /// Compute a hash of `data` using the algorithm identified by `algorithm_oid`.
90    ///
91    /// `algorithm_oid` is an OID component array, e.g. `crate::ID_SHA1`.
92    /// Returns the full un-truncated hash bytes on success.
93    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, Self::Error>;
94}
95
96/// Sentinel [`KeyIdHasher`] that always returns an error.
97///
98/// Use as a placeholder when no crypto backend is available.
99pub struct NoKeyIdHasher;
100
101impl KeyIdHasher for NoKeyIdHasher {
102    type Error = NoKeyIdHasherError;
103
104    fn hash(&self, _algorithm_oid: &[u32], _data: &[u8]) -> Result<Vec<u8>, NoKeyIdHasherError> {
105        Err(NoKeyIdHasherError)
106    }
107}
108
109// ── ErasedKeyIdHasher ─────────────────────────────────────────────────────────
110
111/// Object-safe [`KeyIdHasher`] variant with a type-erased error.
112///
113/// Used as the return type of [`default_key_id_hasher`] so that callers
114/// receive a `Box<dyn ErasedKeyIdHasher>` without naming the backend type.
115/// The [`encode_subject_key_identifier`] and [`encode_authority_key_identifier`]
116/// helpers accept any `&dyn ErasedKeyIdHasher` via the blanket [`KeyIdHasher`]
117/// impl below.
118///
119/// [`encode_subject_key_identifier`]: crate::encode_subject_key_identifier
120/// [`encode_authority_key_identifier`]: crate::encode_authority_key_identifier
121pub trait ErasedKeyIdHasher {
122    /// Hash `data` with the algorithm identified by `algorithm_oid`;
123    /// returns [`PrivateKeyError`] on failure.
124    fn hash_erased(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError>;
125}
126
127/// Blanket impl: `&dyn ErasedKeyIdHasher` implements [`KeyIdHasher`].
128impl KeyIdHasher for dyn ErasedKeyIdHasher + '_ {
129    type Error = PrivateKeyError;
130
131    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
132        self.hash_erased(algorithm_oid, data)
133    }
134}
135
136/// Blanket impl: `Box<dyn ErasedKeyIdHasher>` implements [`KeyIdHasher`].
137///
138/// This lets a `Box<dyn ErasedKeyIdHasher>` (as returned by
139/// [`default_key_id_hasher`]) be passed directly to
140/// [`crate::encode_subject_key_identifier`] and
141/// [`crate::encode_authority_key_identifier`] without calling `.as_ref()`.
142impl KeyIdHasher for Box<dyn ErasedKeyIdHasher> {
143    type Error = PrivateKeyError;
144
145    fn hash(&self, algorithm_oid: &[u32], data: &[u8]) -> Result<Vec<u8>, PrivateKeyError> {
146        self.as_ref().hash_erased(algorithm_oid, data)
147    }
148}
149
150/// Return a key-identifier hasher backed by the default crypto backend.
151///
152/// When the `openssl` feature is enabled, returns an OpenSSL-backed hasher.
153/// Returns a [`PrivateKeyError`]-yielding stub when no backend is available.
154///
155/// The returned object implements [`KeyIdHasher`] via a blanket impl on
156/// `dyn ErasedKeyIdHasher`.
157///
158/// # Example
159///
160/// ```rust,ignore
161/// use synta_certificate::{default_key_id_hasher, encode_subject_key_identifier, KeyIdMethod};
162///
163/// let hasher = default_key_id_hasher();
164/// let ski_der = encode_subject_key_identifier(&spki_der, KeyIdMethod::Rfc5280Sha1, hasher.as_ref())?;
165/// ```
166pub fn default_key_id_hasher() -> Box<dyn ErasedKeyIdHasher> {
167    #[cfg(all(feature = "nss", not(feature = "openssl")))]
168    {
169        crate::nss_backend::nss_key_id_hasher()
170    }
171    #[cfg(feature = "openssl")]
172    {
173        crate::openssl_backend::openssl_key_id_hasher()
174    }
175    #[cfg(not(any(feature = "openssl", feature = "nss")))]
176    {
177        Box::new(NoKeyIdHasherErased)
178    }
179}
180
181/// No-op erased hasher used when no backend is compiled in.
182#[cfg(not(any(feature = "openssl", feature = "nss")))]
183struct NoKeyIdHasherErased;
184
185#[cfg(not(any(feature = "openssl", feature = "nss")))]
186impl ErasedKeyIdHasher for NoKeyIdHasherErased {
187    fn hash_erased(
188        &self,
189        _algorithm_oid: &[u32],
190        _data: &[u8],
191    ) -> Result<Vec<u8>, PrivateKeyError> {
192        Err(PrivateKeyError::new(NoKeyIdHasherError))
193    }
194}