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}