Skip to main content

synta_certificate/crypto/
encryption.rs

1//! CMS encryption/decryption, PKCS#12, and key-transport traits.
2
3use super::errors::{NoCryptoError, NoEncryptorError, NoEnvelopedDataDecryptorError};
4
5// ── CmsDecryptor ──────────────────────────────────────────────────────────────
6
7/// Trait for decrypting CMS `EncryptedData` content.
8///
9/// The key is a raw symmetric key (not a password).  Algorithm parameters
10/// such as IV or GCM nonce are read from `algorithm_der`.
11///
12/// # Contract
13///
14/// - `algorithm_der`: complete DER encoding of the `AlgorithmIdentifier` from
15///   `EncryptedContentInfo.contentEncryptionAlgorithm`.
16/// - `ciphertext`: raw ciphertext bytes extracted from the
17///   `encryptedContent [0] IMPLICIT OCTET STRING` field.
18/// - `key`: raw symmetric key bytes.
19///
20/// Returns the decrypted plaintext on success.
21pub trait CmsDecryptor {
22    type Error: std::error::Error + Send + Sync + 'static;
23
24    fn decrypt(
25        &self,
26        algorithm_der: &[u8],
27        ciphertext: &[u8],
28        key: &[u8],
29    ) -> Result<Vec<u8>, Self::Error>;
30}
31
32/// Sentinel decryptor for `CmsDecryptor` that always returns an error.
33///
34/// Use when no crypto backend is available.
35pub struct NoCmsDecryptor;
36
37impl CmsDecryptor for NoCmsDecryptor {
38    type Error = NoCryptoError;
39
40    fn decrypt(
41        &self,
42        _algorithm_der: &[u8],
43        _ciphertext: &[u8],
44        _key: &[u8],
45    ) -> Result<Vec<u8>, NoCryptoError> {
46        Err(NoCryptoError)
47    }
48}
49
50// ── Encryptor ─────────────────────────────────────────────────────────────────
51
52/// Low-level symmetric encryption primitive.
53///
54/// Implementations select and configure the cipher, generate a fresh random
55/// IV/nonce, perform the encryption, and encode the result.
56///
57/// # Contract
58///
59/// - `alg_oid`: OID component slice identifying the cipher
60///   (e.g. `&ID_AES128_CBC`).
61/// - `plaintext`: raw plaintext bytes.
62/// - `key`: raw symmetric key bytes (length must match the cipher's
63///   key size).
64///
65/// Returns `(algorithm_identifier_der, ciphertext)` where
66/// `algorithm_identifier_der` is the complete DER-encoded `AlgorithmIdentifier`
67/// (OID + generated IV as OCTET STRING parameter) ready to embed in
68/// `EncryptedContentInfo.contentEncryptionAlgorithm`.
69pub trait Encryptor {
70    type Error: std::error::Error + Send + Sync + 'static;
71
72    /// Encrypt `plaintext` under `key` using the cipher identified by `alg_oid`.
73    ///
74    /// Generates a fresh random IV/nonce internally.  Returns
75    /// `(algorithm_identifier_der, ciphertext)` — the caller must embed
76    /// `algorithm_identifier_der` in `EncryptedContentInfo.contentEncryptionAlgorithm`
77    /// so that the receiver can recover the IV for decryption.
78    fn encrypt(
79        &self,
80        alg_oid: &[u32],
81        plaintext: &[u8],
82        key: &[u8],
83    ) -> Result<(Vec<u8>, Vec<u8>), Self::Error>;
84}
85
86/// Sentinel [`Encryptor`] that always returns an error.
87///
88/// Use when no crypto backend is available and you want to surface a clear
89/// error rather than panicking or silently succeeding.
90pub struct NoEncryptor;
91
92impl Encryptor for NoEncryptor {
93    type Error = NoEncryptorError;
94
95    fn encrypt(
96        &self,
97        _alg_oid: &[u32],
98        _plaintext: &[u8],
99        _key: &[u8],
100    ) -> Result<(Vec<u8>, Vec<u8>), NoEncryptorError> {
101        Err(NoEncryptorError)
102    }
103}
104
105// ── CmsEncryptor ──────────────────────────────────────────────────────────────
106
107/// CMS `EncryptedData` builder.
108///
109/// Extends [`Encryptor`] with the ability to assemble a complete RFC 5652 §8
110/// `EncryptedData` SEQUENCE from plaintext and a symmetric key.
111///
112/// # Contract
113///
114/// - `content_type_oid`: OID components for `EncryptedContentInfo.contentType`
115///   (typically `&ID_DATA` = `id-data`, 1.2.840.113549.1.7.1).
116/// - `enc_alg_oid`: OID components for the content-encryption algorithm
117///   (e.g. `&ID_AES128_CBC`).
118/// - `plaintext` / `key`: as for [`Encryptor::encrypt`].
119///
120/// Returns the DER-encoded `EncryptedData` SEQUENCE.
121pub trait CmsEncryptor: Encryptor {
122    /// Encrypt `plaintext` and return a complete DER-encoded `EncryptedData`
123    /// SEQUENCE (RFC 5652 §8).
124    ///
125    /// The returned bytes contain `version`, `encryptedContentInfo` (with OID,
126    /// fresh random IV, and ciphertext), and are ready to be wrapped in a
127    /// `ContentInfo` SEQUENCE or stored directly.
128    fn create_encrypted_data(
129        &self,
130        content_type_oid: &[u32],
131        enc_alg_oid: &[u32],
132        plaintext: &[u8],
133        key: &[u8],
134    ) -> Result<Vec<u8>, Self::Error>;
135}
136
137// ── Pkcs12Encryptor ───────────────────────────────────────────────────────────
138
139/// Password-based encryptor and MAC generator for PKCS#12 archives.
140///
141/// Implementations provide two operations used by [`crate::Pkcs12Builder`]:
142///
143/// 1. **`encrypt`** — encrypt a private-key DER blob for storage in a
144///    `pkcs8ShroudedKeyBag` `EncryptedPrivateKeyInfo`.
145/// 2. **`compute_mac`** — produce a `MacData` DER for the `PFX` integrity
146///    check (RFC 7292 §4).
147///
148/// # Contract
149///
150/// - `encrypt(plaintext, password)` returns
151///   `(algorithm_identifier_der, ciphertext)` where `algorithm_identifier_der`
152///   is a complete DER-encoded `AlgorithmIdentifier` SEQUENCE (e.g.
153///   PBES2 + PBKDF2-SHA256 + AES-256-CBC) suitable for embedding directly in
154///   `EncryptedPrivateKeyInfo.encryptionAlgorithm`.
155///
156/// - `compute_mac(auth_safe_content, password)` returns the DER-encoded
157///   `MacData` SEQUENCE ready to embed as the third field of a `PFX`.
158///   `auth_safe_content` is the raw DER bytes of the `AuthenticatedSafe`
159///   SEQUENCE (i.e. the value inside the outer `id-data` ContentInfo's
160///   `[0] EXPLICIT OCTET STRING`).
161pub trait Pkcs12Encryptor {
162    type Error: std::error::Error + Send + Sync + 'static;
163
164    /// Encrypt `plaintext` under `password` using a password-based scheme.
165    ///
166    /// Returns `(algorithm_identifier_der, ciphertext)`.
167    fn encrypt(&self, plaintext: &[u8], password: &[u8])
168        -> Result<(Vec<u8>, Vec<u8>), Self::Error>;
169
170    /// Compute a `MacData` DER for the given `AuthenticatedSafe` content.
171    ///
172    /// Returns the DER-encoded `MacData` SEQUENCE.
173    fn compute_mac(
174        &self,
175        auth_safe_content: &[u8],
176        password: &[u8],
177    ) -> Result<Vec<u8>, Self::Error>;
178}
179
180/// Sentinel [`Pkcs12Encryptor`] that always returns an error.
181///
182/// Use when no crypto backend is available and you want a clear error
183/// rather than panicking.
184pub struct NoPkcs12Encryptor;
185
186impl Pkcs12Encryptor for NoPkcs12Encryptor {
187    type Error = NoEncryptorError;
188
189    fn encrypt(
190        &self,
191        _plaintext: &[u8],
192        _password: &[u8],
193    ) -> Result<(Vec<u8>, Vec<u8>), NoEncryptorError> {
194        Err(NoEncryptorError)
195    }
196
197    fn compute_mac(
198        &self,
199        _auth_safe_content: &[u8],
200        _password: &[u8],
201    ) -> Result<Vec<u8>, NoEncryptorError> {
202        Err(NoEncryptorError)
203    }
204}
205
206// ── Pkcs12Decryptor ───────────────────────────────────────────────────────────
207
208/// Trait for decrypting PKCS#12 encrypted safe-contents bags.
209///
210/// Callers provide an implementation to `certs_from_pkcs12`; the parser
211/// itself does not link against any crypto library.  A concrete
212/// implementation using rust-openssl is available as `crate::OpensslDecryptor`
213/// when the `openssl` feature is enabled.
214///
215/// # Contract
216///
217/// - `algorithm_der`: complete DER encoding of the `AlgorithmIdentifier` from
218///   `EncryptedContentInfo.contentEncryptionAlgorithm`.
219/// - `ciphertext`: raw ciphertext bytes extracted from the
220///   `encryptedContent [0] IMPLICIT OCTET STRING` field.
221/// - `password`: UTF-8 bytes, no NUL terminator.  For PBKDF2/PBES2 the bytes
222///   are used as-is (RFC 8018).  For the legacy PKCS12-KDF the OpenSSL
223///   implementation converts to BMPString internally via `PKCS12_key_gen_utf8`.
224///
225/// Returns the decrypted plaintext (a SafeContents DER SEQUENCE) on success.
226pub trait Pkcs12Decryptor {
227    type Error: std::error::Error + Send + Sync + 'static;
228
229    fn decrypt(
230        &self,
231        algorithm_der: &[u8],
232        ciphertext: &[u8],
233        password: &[u8],
234    ) -> Result<Vec<u8>, Self::Error>;
235}
236
237/// Sentinel decryptor that always fails.
238///
239/// Use this as the `decryptor` argument to `certs_from_pkcs12` when you only
240/// expect unencrypted PKCS#12 files (plain `id-data` authSafe bags) and want
241/// a clear error instead of silently skipping encrypted bags.
242pub struct NoCrypto;
243
244impl Pkcs12Decryptor for NoCrypto {
245    type Error = NoCryptoError;
246
247    fn decrypt(
248        &self,
249        _algorithm_der: &[u8],
250        _ciphertext: &[u8],
251        _password: &[u8],
252    ) -> Result<Vec<u8>, NoCryptoError> {
253        Err(NoCryptoError)
254    }
255}
256
257// ── KeyWrapAlgorithm ──────────────────────────────────────────────────────────
258
259/// Key-transport wrapping algorithm for CMS `EnvelopedData` (RFC 5652 §6).
260///
261/// Selects how the content-encryption key (CEK) is encrypted for the
262/// recipient's public key.
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum KeyWrapAlgorithm {
265    /// RSA-OAEP with SHA-256 hash and MGF1-SHA-256 mask generation (RFC 8017
266    /// §7.1).  Recommended for new protocols.
267    RsaOaepSha256,
268    /// RSA PKCS#1 v1.5 padding (RFC 8017 §7.2).  Provided for interoperability
269    /// with legacy systems; prefer [`KeyWrapAlgorithm::RsaOaepSha256`] for new
270    /// deployments.
271    RsaPkcs1v15,
272}
273
274// ── KeyEncryptor / KeyDecryptor ───────────────────────────────────────────────
275
276/// Encrypts data under an asymmetric public key.
277///
278/// The primary use case is CMS `EnvelopedData` key transport (RFC 5652 §6.2.1):
279/// the sender encrypts the content-encryption key (CEK) under the recipient's
280/// public key from their X.509 certificate.  The same interface covers both
281/// RSA-OAEP (RFC 8017) and legacy RSA PKCS#1 v1.5 padding.
282///
283/// # Contract
284///
285/// - `plaintext`: the raw bytes to encrypt (typically a symmetric CEK, but any
286///   data within the key's capacity is accepted).
287/// - Returns the ciphertext as an owned `Vec<u8>`.
288pub trait KeyEncryptor {
289    /// Error type returned by this encryptor.
290    type Error: std::error::Error + Send + Sync + 'static;
291
292    /// Encrypt `plaintext` under the public key held by this encryptor.
293    fn encrypt_key(&self, plaintext: &[u8]) -> Result<Vec<u8>, Self::Error>;
294}
295
296/// Decrypts data using an asymmetric private key.
297///
298/// The primary use case is CMS `EnvelopedData` key transport (RFC 5652 §6.2.1):
299/// the recipient decrypts the encrypted CEK using their private key.  The same
300/// interface covers both RSA-OAEP (RFC 8017) and legacy RSA PKCS#1 v1.5.
301///
302/// # Contract
303///
304/// - `ciphertext`: the raw encrypted bytes to decrypt.
305/// - Returns the plaintext as an owned `Vec<u8>`.
306pub trait KeyDecryptor {
307    /// Error type returned by this decryptor.
308    type Error: std::error::Error + Send + Sync + 'static;
309
310    /// Decrypt `ciphertext` using the private key held by this decryptor.
311    fn decrypt_key(&self, ciphertext: &[u8]) -> Result<Vec<u8>, Self::Error>;
312}
313
314// ── EnvelopedDataDecryptor ────────────────────────────────────────────────────
315
316/// Decrypts a CMS `EnvelopedData` structure using key transport (RFC 5652 §6).
317///
318/// Implementations iterate the `RecipientInfos` SET looking for a
319/// `KeyTransRecipientInfo` entry whose encrypted content-encryption key (CEK)
320/// can be unwrapped with the held private key.  On success the recovered CEK
321/// is used to decrypt the `encryptedContent` field and the plaintext is
322/// returned.
323///
324/// # Contract
325///
326/// - `ed`: the fully parsed `EnvelopedData` SEQUENCE (use `synta::Decoder` to
327///   obtain it from raw DER bytes).
328/// - Returns the decrypted plaintext as an owned `Vec<u8>` on success, or an
329///   error if no matching recipient info is found or any crypto operation fails.
330pub trait EnvelopedDataDecryptor {
331    /// Error type returned by this decryptor.
332    type Error: std::error::Error + Send + Sync + 'static;
333
334    /// Decrypt the given `EnvelopedData`.
335    fn decrypt_enveloped(
336        &self,
337        ed: &crate::cms_rfc5652_types::EnvelopedData<'_>,
338    ) -> Result<Vec<u8>, Self::Error>;
339}
340
341/// Sentinel [`EnvelopedDataDecryptor`] that always returns an error.
342///
343/// Use when no crypto backend is available.
344pub struct NoEnvelopedDataDecryptor;
345
346impl EnvelopedDataDecryptor for NoEnvelopedDataDecryptor {
347    type Error = NoEnvelopedDataDecryptorError;
348
349    fn decrypt_enveloped(
350        &self,
351        _ed: &crate::cms_rfc5652_types::EnvelopedData<'_>,
352    ) -> Result<Vec<u8>, NoEnvelopedDataDecryptorError> {
353        Err(NoEnvelopedDataDecryptorError)
354    }
355}