Skip to main content

smime_tree/
key.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::SmimeError;
6
7/// Identifies the recipient of an encrypted message.
8/// Used by [`DecryptionKey::matches_recipient`] to find the right key.
9///
10/// The CMS standard defines two ways to identify a recipient certificate.
11/// Your `matches_recipient` implementation should handle both variants.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[non_exhaustive]
14pub enum RecipientIdentifier {
15    /// Identified by certificate issuer name and serial number (PKCS #7 compatibility).
16    IssuerAndSerialNumber {
17        /// DER-encoded `Name` (X.501 RDN sequence).
18        /// Obtain from `cert.tbs_certificate().issuer().to_der()?`.
19        issuer_der: Vec<u8>,
20        /// Big-endian serial number bytes.
21        /// Obtain from `cert.tbs_certificate().serial_number().as_bytes()`.
22        serial: Vec<u8>,
23    },
24    /// Identified by the raw bytes of the Subject Key Identifier extension value
25    /// (RFC 5652 §6.2.2).
26    /// Obtain from the cert's Subject Key Identifier extension:
27    /// `cert.tbs_certificate().get_extension::<SubjectKeyIdentifier>()`.
28    SubjectKeyIdentifier(Vec<u8>),
29}
30
31impl fmt::Display for RecipientIdentifier {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            RecipientIdentifier::IssuerAndSerialNumber { issuer_der, serial } => {
35                write!(
36                    f,
37                    "IssuerAndSerial(issuer={} bytes, serial=",
38                    issuer_der.len()
39                )?;
40                for (i, b) in serial.iter().enumerate() {
41                    if i > 0 {
42                        write!(f, ":")?;
43                    }
44                    write!(f, "{b:02x}")?;
45                }
46                write!(f, ")")
47            }
48            RecipientIdentifier::SubjectKeyIdentifier(ski) => {
49                write!(f, "SubjectKeyIdentifier(")?;
50                for (i, b) in ski.iter().enumerate() {
51                    if i > 0 {
52                        write!(f, ":")?;
53                    }
54                    write!(f, "{b:02x}")?;
55                }
56                write!(f, ")")
57            }
58        }
59    }
60}
61
62/// Elliptic curve selection for ECDH key agreement.
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[non_exhaustive]
65pub enum EcCurve {
66    P256,
67    P384,
68}
69
70impl fmt::Display for EcCurve {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            EcCurve::P256 => f.write_str("P-256"),
74            EcCurve::P384 => f.write_str("P-384"),
75        }
76    }
77}
78
79/// Algorithm used to encrypt (wrap) the content-encryption key.
80///
81/// # ECDH / KARI
82///
83/// ECDH key agreement (`KeyAgreeRecipientInfo`, RFC 5753) uses a *separate*
84/// trait method: [`DecryptionKey::agree_ecdh`].  ECDH key agreement for
85/// decryption uses `DecryptionKey::agree_ecdh()` directly, not `decrypt_cek()`.
86/// The `EcdhEs` variant exists for completeness but `decrypt_cek()` implementations
87/// do not need to handle it.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[non_exhaustive]
90pub enum KeyEncryptionAlgorithm {
91    /// RSA PKCS#1 v1.5 key transport (RFC 8017).
92    RsaPkcs1v15,
93    /// RSA-OAEP key transport (RFC 8017).
94    RsaOaep,
95    /// Reserved for future ECDH-ES key agreement support. Never produced by
96    /// the current implementation of `decrypt()`; present for forward
97    /// compatibility only.
98    ///
99    /// ECDH key agreement is handled via [`DecryptionKey::agree_ecdh`] instead.
100    /// `decrypt_cek()` implementations do not need to handle this variant.
101    EcdhEs { curve: EcCurve },
102}
103
104impl fmt::Display for KeyEncryptionAlgorithm {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            KeyEncryptionAlgorithm::RsaPkcs1v15 => f.write_str("RSA-PKCS1v15"),
108            KeyEncryptionAlgorithm::RsaOaep => f.write_str("RSA-OAEP"),
109            KeyEncryptionAlgorithm::EcdhEs { curve } => write!(f, "ECDH-ES/{curve}"),
110        }
111    }
112}
113
114/// AES key wrap algorithm used to protect the content-encryption key in KARI.
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116#[non_exhaustive]
117pub enum KeyWrapAlgorithm {
118    /// AES-128 key wrap (`id-aes128-Wrap`, RFC 3394).
119    Aes128Kw,
120    /// AES-256 key wrap (`id-aes256-Wrap`, RFC 3394).
121    Aes256Kw,
122}
123
124impl fmt::Display for KeyWrapAlgorithm {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            KeyWrapAlgorithm::Aes128Kw => f.write_str("AES-128-KW"),
128            KeyWrapAlgorithm::Aes256Kw => f.write_str("AES-256-KW"),
129        }
130    }
131}
132
133/// ECDH key derivation scheme used in `KeyAgreeRecipientInfo` (RFC 5753 §7.1.4).
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135#[non_exhaustive]
136pub enum KariKeyAgreement {
137    /// `dhSinglePass-stdDH-sha256kdf-scheme` — P-256 with X9.63 KDF using SHA-256.
138    StdDhSha256Kdf,
139    /// `dhSinglePass-stdDH-sha384kdf-scheme` — P-384 with X9.63 KDF using SHA-384.
140    StdDhSha384Kdf,
141}
142
143impl fmt::Display for KariKeyAgreement {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            KariKeyAgreement::StdDhSha256Kdf => f.write_str("stdDH-SHA256-KDF"),
147            KariKeyAgreement::StdDhSha384Kdf => f.write_str("stdDH-SHA384-KDF"),
148        }
149    }
150}
151
152/// Combined algorithm parameters for ECDH key agreement (`KeyAgreeRecipientInfo`).
153///
154/// Passed to [`DecryptionKey::agree_ecdh`] so the implementor knows which
155/// ECDH variant and key wrap algorithm to apply.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157#[non_exhaustive]
158pub struct KariAlgorithm {
159    /// ECDH key derivation scheme (determines curve and KDF hash).
160    pub key_agreement: KariKeyAgreement,
161    /// AES key wrap algorithm used to protect the CEK.
162    pub key_wrap: KeyWrapAlgorithm,
163}
164
165impl KariAlgorithm {
166    /// Construct a `KariAlgorithm` with the given key agreement and wrap schemes.
167    ///
168    /// `KariAlgorithm` is `#[non_exhaustive]` so external callers cannot use
169    /// struct expression syntax. Use this constructor instead.
170    #[must_use]
171    pub fn new(key_agreement: KariKeyAgreement, key_wrap: KeyWrapAlgorithm) -> Self {
172        Self {
173            key_agreement,
174            key_wrap,
175        }
176    }
177}
178
179/// Digest algorithm used when creating or verifying a signature.
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[non_exhaustive]
182pub enum DigestAlgorithm {
183    Sha256,
184    Sha384,
185    Sha512,
186}
187
188impl fmt::Display for DigestAlgorithm {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        match self {
191            DigestAlgorithm::Sha256 => f.write_str("sha-256"),
192            DigestAlgorithm::Sha384 => f.write_str("sha-384"),
193            DigestAlgorithm::Sha512 => f.write_str("sha-512"),
194        }
195    }
196}
197
198/// Abstraction over a private key capable of decrypting an S/MIME message.
199///
200/// Implementors provide the key-unwrap step: given an encrypted CEK and the
201/// algorithm that was used to wrap it, return the raw CEK bytes.
202pub trait DecryptionKey {
203    /// Decrypt an encrypted content-encryption key.
204    fn decrypt_cek(
205        &self,
206        encrypted_key: &[u8],
207        algorithm: &KeyEncryptionAlgorithm,
208    ) -> Result<Vec<u8>, SmimeError>;
209
210    /// Returns `true` if this key matches the given `RecipientIdentifier`.
211    ///
212    /// # Example
213    ///
214    /// `RecipientIdentifier` is `#[non_exhaustive]`, so your implementation
215    /// **must** include a catch-all arm (`_ => false`) to remain forward-compatible
216    /// with future identifier variants.
217    ///
218    /// ```ignore
219    /// fn matches_recipient(&self, id: &RecipientIdentifier) -> bool {
220    ///     match id {
221    ///         RecipientIdentifier::IssuerAndSerialNumber { issuer_der, serial } => {
222    ///             // issuer_der: DER-encoded Name — compare against
223    ///             //   cert.tbs_certificate().issuer().to_der().unwrap_or_default()
224    ///             // serial: big-endian bytes — compare against
225    ///             //   cert.tbs_certificate().serial_number().as_bytes()
226    ///             self.cert_issuer_der() == issuer_der
227    ///                 && self.cert_serial() == serial
228    ///         }
229    ///         RecipientIdentifier::SubjectKeyIdentifier(ski) => {
230    ///             // ski: raw bytes of the Subject Key Identifier extension value —
231    ///             //   obtain via cert.tbs_certificate()
232    ///             //       .get_extension::<SubjectKeyIdentifier>()
233    ///             self.cert_ski() == ski
234    ///         }
235    ///         _ => false, // required: RecipientIdentifier is #[non_exhaustive]
236    ///     }
237    /// }
238    /// ```
239    fn matches_recipient(&self, id: &RecipientIdentifier) -> bool;
240
241    /// Perform ECDH key agreement and unwrap the content-encryption key.
242    ///
243    /// Called when the `EnvelopedData` contains a KARI (`KeyAgreeRecipientInfo`)
244    /// entry that matches this key (as determined by `matches_recipient`).
245    ///
246    /// The implementor must:
247    /// 1. Perform ECDH using the recipient's private key and
248    ///    `ephemeral_public_key_bytes` (SEC1 uncompressed EC point) to obtain
249    ///    the shared secret *Z*.
250    /// 2. Apply the X9.63 KDF (RFC 5753 §3.6.1): hash *Z* concatenated with a
251    ///    counter and the `EccCmsSharedInfo` structure to derive the
252    ///    key-encryption key (KEK).  `SharedInfo` encodes `alg.key_wrap` OID in
253    ///    `keyInfo` and, when `ukm` is `Some`, the UKM bytes in
254    ///    `entityUInfo [0] EXPLICIT`.  When `ukm` is `None` (which is always
255    ///    the case when this crate's `encrypt()` produced the message, as this
256    ///    crate never generates UKM), omit `entityUInfo`.
257    /// 3. Unwrap `enc_cek` with AES-128-KW or AES-256-KW (per `alg.key_wrap`)
258    ///    using the derived KEK.
259    /// 4. Return the raw CEK bytes.
260    ///
261    /// # Default — IMPORTANT for EC key implementors
262    ///
263    /// Returns `Err(SmimeError::UnsupportedAlgorithm(...))`.  **EC key
264    /// implementors MUST override this method** or KARI-encrypted messages
265    /// will silently fail to decrypt (the error is caught per-recipient
266    /// and the next `RecipientInfo` is tried; if no other recipient matches,
267    /// `NoMatchingRecipient` is returned with no indication that ECDH was
268    /// attempted).  RSA-only keys can leave the default.
269    fn agree_ecdh(
270        &self,
271        ephemeral_public_key_bytes: &[u8],
272        ukm: Option<&[u8]>,
273        enc_cek: &[u8],
274        alg: &KariAlgorithm,
275    ) -> Result<Vec<u8>, SmimeError> {
276        let _ = (ephemeral_public_key_bytes, ukm, enc_cek, alg);
277        Err(SmimeError::UnsupportedAlgorithm(
278            "KARI (ECDH key agreement) not supported by this key".into(),
279        ))
280    }
281
282    /// Returns a hint about which key encryption algorithms this key supports.
283    ///
284    /// This crate does **not** consult this hint internally — `decrypt()` always
285    /// tries all recipients and lets `decrypt_cek()` / `agree_ecdh()` reject
286    /// unsupported algorithms.  This method is provided as an extension point for
287    /// callers that want to pre-screen or log algorithm capabilities.
288    ///
289    /// Default: returns `None` (no hint; let `decrypt_cek()` reject unsupported ones).
290    fn supported_key_enc_algorithm(&self) -> Option<KeyEncryptionAlgorithm> {
291        None
292    }
293}
294
295/// Trait for checking certificate revocation status during signature verification.
296///
297/// This crate makes no network calls. Callers that need OCSP or CRL checking
298/// implement this trait and inject it into [`crate::verify()`].
299///
300/// The no-op implementation [`NoRevocationCheck`] is provided for cases where
301/// revocation checking is not needed (e.g. tests, trusted internal PKI,
302/// air-gapped deployments).
303pub trait RevocationChecker {
304    /// Check whether `cert` has been revoked.
305    ///
306    /// Return `Ok(())` if the certificate is valid (not revoked or revocation
307    /// status is acceptable).  Return `Err(SmimeError::CertChain(...))` if the
308    /// certificate is revoked or its revocation status cannot be confirmed.
309    fn check(&self, cert: &x509_cert::Certificate) -> Result<(), SmimeError>;
310}
311
312/// A no-op [`RevocationChecker`] that accepts all certificates without
313/// consulting OCSP or CRL.
314///
315/// Use this when revocation checking is managed out-of-band, not required
316/// for the deployment, or during testing with synthetic certificates.
317///
318/// # Security
319///
320/// **This disables all certificate revocation checks.** Use only in environments where
321/// revocation is verified by other means, or in testing. Never use in production without
322/// explicit awareness that revoked certificates will be accepted.
323#[derive(Debug)]
324pub struct NoRevocationCheck;
325
326impl RevocationChecker for NoRevocationCheck {
327    fn check(&self, _cert: &x509_cert::Certificate) -> Result<(), SmimeError> {
328        Ok(())
329    }
330}
331
332/// Abstraction over a private key capable of signing an S/MIME message.
333///
334/// Implementors supply the raw signature bytes and the signer's certificate,
335/// which is embedded in the `SignedData` structure.
336pub trait SigningKey {
337    /// Sign `data` and return the signature bytes.
338    ///
339    /// The `data` argument is the DER-encoded `SignedAttributes` structure (a SET OF Attribute
340    /// per RFC 5652 §5.4), with tag byte `0x31`. The implementor must sign exactly these bytes
341    /// — do not pre-hash, do not add framing. For HSM integrations, the entire byte slice must
342    /// be transmitted as-is to the signing operation.
343    ///
344    /// **ECDSA format requirement**: For ECDSA keys, the returned bytes MUST be DER-encoded
345    /// (ASN.1 `SEQUENCE { r INTEGER, s INTEGER }`), as produced by RustCrypto `p256`/`p384`
346    /// signers. Raw fixed-width (`r || s`) format (IEEE P1363) is NOT accepted — `verify()`
347    /// will reject it when calling `DerSignature::try_from`.
348    fn sign(&self, data: &[u8], algorithm: &DigestAlgorithm) -> Result<Vec<u8>, SmimeError>;
349
350    /// The signer's X.509 certificate, included in `SignedData`.
351    fn certificate(&self) -> &x509_cert::Certificate;
352
353    /// Returns the preferred digest algorithm for this key.
354    ///
355    /// [`crate::sign`] consults this method when selecting the digest algorithm:
356    /// if `Some(alg)` is returned, that algorithm is used; otherwise the default
357    /// is derived from the key's certificate (P-256 → SHA-256, P-384 → SHA-384,
358    /// P-521 → SHA-512, RSA → SHA-256).
359    ///
360    /// Override to force a specific algorithm regardless of the certificate's key type.
361    ///
362    /// Default: returns `None` (let `sign()` derive the algorithm from the cert).
363    fn preferred_digest_algorithm(&self) -> Option<DigestAlgorithm> {
364        None
365    }
366}