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}