synta-certificate 0.2.6

X.509 certificate structures for synta ASN.1 library
Documentation
//! CMS encryption/decryption, PKCS#12, and key-transport traits.

use super::errors::{NoCryptoError, NoEncryptorError, NoEnvelopedDataDecryptorError};

// ── CmsDecryptor ──────────────────────────────────────────────────────────────

/// Trait for decrypting CMS `EncryptedData` content.
///
/// The key is a raw symmetric key (not a password).  Algorithm parameters
/// such as IV or GCM nonce are read from `algorithm_der`.
///
/// # Contract
///
/// - `algorithm_der`: complete DER encoding of the `AlgorithmIdentifier` from
///   `EncryptedContentInfo.contentEncryptionAlgorithm`.
/// - `ciphertext`: raw ciphertext bytes extracted from the
///   `encryptedContent [0] IMPLICIT OCTET STRING` field.
/// - `key`: raw symmetric key bytes.
///
/// Returns the decrypted plaintext on success.
pub trait CmsDecryptor {
    type Error: std::error::Error + Send + Sync + 'static;

    fn decrypt(
        &self,
        algorithm_der: &[u8],
        ciphertext: &[u8],
        key: &[u8],
    ) -> Result<Vec<u8>, Self::Error>;
}

/// Sentinel decryptor for `CmsDecryptor` that always returns an error.
///
/// Use when no crypto backend is available.
pub struct NoCmsDecryptor;

impl CmsDecryptor for NoCmsDecryptor {
    type Error = NoCryptoError;

    fn decrypt(
        &self,
        _algorithm_der: &[u8],
        _ciphertext: &[u8],
        _key: &[u8],
    ) -> Result<Vec<u8>, NoCryptoError> {
        Err(NoCryptoError)
    }
}

// ── Encryptor ─────────────────────────────────────────────────────────────────

/// Low-level symmetric encryption primitive.
///
/// Implementations select and configure the cipher, generate a fresh random
/// IV/nonce, perform the encryption, and encode the result.
///
/// # Contract
///
/// - `alg_oid`: OID component slice identifying the cipher
///   (e.g. `&ID_AES128_CBC`).
/// - `plaintext`: raw plaintext bytes.
/// - `key`: raw symmetric key bytes (length must match the cipher's
///   key size).
///
/// Returns `(algorithm_identifier_der, ciphertext)` where
/// `algorithm_identifier_der` is the complete DER-encoded `AlgorithmIdentifier`
/// (OID + generated IV as OCTET STRING parameter) ready to embed in
/// `EncryptedContentInfo.contentEncryptionAlgorithm`.
pub trait Encryptor {
    type Error: std::error::Error + Send + Sync + 'static;

    /// Encrypt `plaintext` under `key` using the cipher identified by `alg_oid`.
    ///
    /// Generates a fresh random IV/nonce internally.  Returns
    /// `(algorithm_identifier_der, ciphertext)` — the caller must embed
    /// `algorithm_identifier_der` in `EncryptedContentInfo.contentEncryptionAlgorithm`
    /// so that the receiver can recover the IV for decryption.
    fn encrypt(
        &self,
        alg_oid: &[u32],
        plaintext: &[u8],
        key: &[u8],
    ) -> Result<(Vec<u8>, Vec<u8>), Self::Error>;
}

/// Sentinel [`Encryptor`] that always returns an error.
///
/// Use when no crypto backend is available and you want to surface a clear
/// error rather than panicking or silently succeeding.
pub struct NoEncryptor;

impl Encryptor for NoEncryptor {
    type Error = NoEncryptorError;

    fn encrypt(
        &self,
        _alg_oid: &[u32],
        _plaintext: &[u8],
        _key: &[u8],
    ) -> Result<(Vec<u8>, Vec<u8>), NoEncryptorError> {
        Err(NoEncryptorError)
    }
}

// ── CmsEncryptor ──────────────────────────────────────────────────────────────

/// CMS `EncryptedData` builder.
///
/// Extends [`Encryptor`] with the ability to assemble a complete RFC 5652 §8
/// `EncryptedData` SEQUENCE from plaintext and a symmetric key.
///
/// # Contract
///
/// - `content_type_oid`: OID components for `EncryptedContentInfo.contentType`
///   (typically `&ID_DATA` = `id-data`, 1.2.840.113549.1.7.1).
/// - `enc_alg_oid`: OID components for the content-encryption algorithm
///   (e.g. `&ID_AES128_CBC`).
/// - `plaintext` / `key`: as for [`Encryptor::encrypt`].
///
/// Returns the DER-encoded `EncryptedData` SEQUENCE.
pub trait CmsEncryptor: Encryptor {
    /// Encrypt `plaintext` and return a complete DER-encoded `EncryptedData`
    /// SEQUENCE (RFC 5652 §8).
    ///
    /// The returned bytes contain `version`, `encryptedContentInfo` (with OID,
    /// fresh random IV, and ciphertext), and are ready to be wrapped in a
    /// `ContentInfo` SEQUENCE or stored directly.
    fn create_encrypted_data(
        &self,
        content_type_oid: &[u32],
        enc_alg_oid: &[u32],
        plaintext: &[u8],
        key: &[u8],
    ) -> Result<Vec<u8>, Self::Error>;
}

// ── Pkcs12Encryptor ───────────────────────────────────────────────────────────

/// Password-based encryptor and MAC generator for PKCS#12 archives.
///
/// Implementations provide two operations used by [`crate::Pkcs12Builder`]:
///
/// 1. **`encrypt`** — encrypt a private-key DER blob for storage in a
///    `pkcs8ShroudedKeyBag` `EncryptedPrivateKeyInfo`.
/// 2. **`compute_mac`** — produce a `MacData` DER for the `PFX` integrity
///    check (RFC 7292 §4).
///
/// # Contract
///
/// - `encrypt(plaintext, password)` returns
///   `(algorithm_identifier_der, ciphertext)` where `algorithm_identifier_der`
///   is a complete DER-encoded `AlgorithmIdentifier` SEQUENCE (e.g.
///   PBES2 + PBKDF2-SHA256 + AES-256-CBC) suitable for embedding directly in
///   `EncryptedPrivateKeyInfo.encryptionAlgorithm`.
///
/// - `compute_mac(auth_safe_content, password)` returns the DER-encoded
///   `MacData` SEQUENCE ready to embed as the third field of a `PFX`.
///   `auth_safe_content` is the raw DER bytes of the `AuthenticatedSafe`
///   SEQUENCE (i.e. the value inside the outer `id-data` ContentInfo's
///   `[0] EXPLICIT OCTET STRING`).
pub trait Pkcs12Encryptor {
    type Error: std::error::Error + Send + Sync + 'static;

    /// Encrypt `plaintext` under `password` using a password-based scheme.
    ///
    /// Returns `(algorithm_identifier_der, ciphertext)`.
    fn encrypt(&self, plaintext: &[u8], password: &[u8])
        -> Result<(Vec<u8>, Vec<u8>), Self::Error>;

    /// Compute a `MacData` DER for the given `AuthenticatedSafe` content.
    ///
    /// Returns the DER-encoded `MacData` SEQUENCE.
    fn compute_mac(
        &self,
        auth_safe_content: &[u8],
        password: &[u8],
    ) -> Result<Vec<u8>, Self::Error>;
}

/// Sentinel [`Pkcs12Encryptor`] that always returns an error.
///
/// Use when no crypto backend is available and you want a clear error
/// rather than panicking.
pub struct NoPkcs12Encryptor;

impl Pkcs12Encryptor for NoPkcs12Encryptor {
    type Error = NoEncryptorError;

    fn encrypt(
        &self,
        _plaintext: &[u8],
        _password: &[u8],
    ) -> Result<(Vec<u8>, Vec<u8>), NoEncryptorError> {
        Err(NoEncryptorError)
    }

    fn compute_mac(
        &self,
        _auth_safe_content: &[u8],
        _password: &[u8],
    ) -> Result<Vec<u8>, NoEncryptorError> {
        Err(NoEncryptorError)
    }
}

// ── Pkcs12Decryptor ───────────────────────────────────────────────────────────

/// Trait for decrypting PKCS#12 encrypted safe-contents bags.
///
/// Callers provide an implementation to `certs_from_pkcs12`; the parser
/// itself does not link against any crypto library.  A concrete
/// implementation using rust-openssl is available as `crate::OpensslDecryptor`
/// when the `openssl` feature is enabled.
///
/// # Contract
///
/// - `algorithm_der`: complete DER encoding of the `AlgorithmIdentifier` from
///   `EncryptedContentInfo.contentEncryptionAlgorithm`.
/// - `ciphertext`: raw ciphertext bytes extracted from the
///   `encryptedContent [0] IMPLICIT OCTET STRING` field.
/// - `password`: UTF-8 bytes, no NUL terminator.  For PBKDF2/PBES2 the bytes
///   are used as-is (RFC 8018).  For the legacy PKCS12-KDF the OpenSSL
///   implementation converts to BMPString internally via `PKCS12_key_gen_utf8`.
///
/// Returns the decrypted plaintext (a SafeContents DER SEQUENCE) on success.
pub trait Pkcs12Decryptor {
    type Error: std::error::Error + Send + Sync + 'static;

    fn decrypt(
        &self,
        algorithm_der: &[u8],
        ciphertext: &[u8],
        password: &[u8],
    ) -> Result<Vec<u8>, Self::Error>;
}

/// Sentinel decryptor that always fails.
///
/// Use this as the `decryptor` argument to `certs_from_pkcs12` when you only
/// expect unencrypted PKCS#12 files (plain `id-data` authSafe bags) and want
/// a clear error instead of silently skipping encrypted bags.
pub struct NoCrypto;

impl Pkcs12Decryptor for NoCrypto {
    type Error = NoCryptoError;

    fn decrypt(
        &self,
        _algorithm_der: &[u8],
        _ciphertext: &[u8],
        _password: &[u8],
    ) -> Result<Vec<u8>, NoCryptoError> {
        Err(NoCryptoError)
    }
}

// ── KeyWrapAlgorithm ──────────────────────────────────────────────────────────

/// Key-transport wrapping algorithm for CMS `EnvelopedData` (RFC 5652 §6).
///
/// Selects how the content-encryption key (CEK) is encrypted for the
/// recipient's public key.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyWrapAlgorithm {
    /// RSA-OAEP with SHA-256 hash and MGF1-SHA-256 mask generation (RFC 8017
    /// §7.1).  Recommended for new protocols.
    RsaOaepSha256,
    /// RSA PKCS#1 v1.5 padding (RFC 8017 §7.2).  Provided for interoperability
    /// with legacy systems; prefer [`KeyWrapAlgorithm::RsaOaepSha256`] for new
    /// deployments.
    RsaPkcs1v15,
}

// ── KeyEncryptor / KeyDecryptor ───────────────────────────────────────────────

/// Encrypts data under an asymmetric public key.
///
/// The primary use case is CMS `EnvelopedData` key transport (RFC 5652 §6.2.1):
/// the sender encrypts the content-encryption key (CEK) under the recipient's
/// public key from their X.509 certificate.  The same interface covers both
/// RSA-OAEP (RFC 8017) and legacy RSA PKCS#1 v1.5 padding.
///
/// # Contract
///
/// - `plaintext`: the raw bytes to encrypt (typically a symmetric CEK, but any
///   data within the key's capacity is accepted).
/// - Returns the ciphertext as an owned `Vec<u8>`.
pub trait KeyEncryptor {
    /// Error type returned by this encryptor.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Encrypt `plaintext` under the public key held by this encryptor.
    fn encrypt_key(&self, plaintext: &[u8]) -> Result<Vec<u8>, Self::Error>;
}

/// Decrypts data using an asymmetric private key.
///
/// The primary use case is CMS `EnvelopedData` key transport (RFC 5652 §6.2.1):
/// the recipient decrypts the encrypted CEK using their private key.  The same
/// interface covers both RSA-OAEP (RFC 8017) and legacy RSA PKCS#1 v1.5.
///
/// # Contract
///
/// - `ciphertext`: the raw encrypted bytes to decrypt.
/// - Returns the plaintext as an owned `Vec<u8>`.
pub trait KeyDecryptor {
    /// Error type returned by this decryptor.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Decrypt `ciphertext` using the private key held by this decryptor.
    fn decrypt_key(&self, ciphertext: &[u8]) -> Result<Vec<u8>, Self::Error>;
}

// ── EnvelopedDataDecryptor ────────────────────────────────────────────────────

/// Decrypts a CMS `EnvelopedData` structure using key transport (RFC 5652 §6).
///
/// Implementations iterate the `RecipientInfos` SET looking for a
/// `KeyTransRecipientInfo` entry whose encrypted content-encryption key (CEK)
/// can be unwrapped with the held private key.  On success the recovered CEK
/// is used to decrypt the `encryptedContent` field and the plaintext is
/// returned.
///
/// # Contract
///
/// - `ed`: the fully parsed `EnvelopedData` SEQUENCE (use `synta::Decoder` to
///   obtain it from raw DER bytes).
/// - Returns the decrypted plaintext as an owned `Vec<u8>` on success, or an
///   error if no matching recipient info is found or any crypto operation fails.
pub trait EnvelopedDataDecryptor {
    /// Error type returned by this decryptor.
    type Error: std::error::Error + Send + Sync + 'static;

    /// Decrypt the given `EnvelopedData`.
    fn decrypt_enveloped(
        &self,
        ed: &crate::cms_rfc5652_types::EnvelopedData<'_>,
    ) -> Result<Vec<u8>, Self::Error>;
}

/// Sentinel [`EnvelopedDataDecryptor`] that always returns an error.
///
/// Use when no crypto backend is available.
pub struct NoEnvelopedDataDecryptor;

impl EnvelopedDataDecryptor for NoEnvelopedDataDecryptor {
    type Error = NoEnvelopedDataDecryptorError;

    fn decrypt_enveloped(
        &self,
        _ed: &crate::cms_rfc5652_types::EnvelopedData<'_>,
    ) -> Result<Vec<u8>, NoEnvelopedDataDecryptorError> {
        Err(NoEnvelopedDataDecryptorError)
    }
}