Skip to main content

pdf_oxide/crypto/
provider.rs

1//! The [`CryptoProvider`] trait family.
2//!
3//! These traits decouple PDF encryption and signature paths from any
4//! one cryptography crate, so deployments that need a FIPS 140-3
5//! validated module (`aws-lc-rs` with the `fips` feature) or a
6//! sovereign-jurisdiction provider (GOST R 34.11/34.10, Chinese
7//! SM2/SM3/SM4) can swap in a different backend without touching the
8//! parsing or signature-construction code.
9//!
10//! See `docs/CRYPTO_PROVIDERS.md` (added in Phase 8) for the
11//! end-to-end story; tracking issue #236.
12//!
13//! # Trait shape
14//!
15//! Three sub-traits handle independent concerns:
16//!
17//! - [`Hasher`] — incremental hashing (`update` / `finalize`).
18//! - [`SymmetricCipher`] — AES-CBC (PKCS#7 and no-padding) and RC4.
19//! - [`SignatureVerifier`] — RSA-PKCS#1-v1.5 / RSA-PSS / ECDSA verify.
20//! - [`Signer`] — opaque signing handle (decouples PEM/DER loading
21//!   from the call site so HSM / PKCS#11 providers can plug in).
22//!
23//! [`CryptoProvider`] composes them and adds policy
24//! ([`is_legacy_allowed`]) plus secure RNG.
25//!
26//! # FIPS posture
27//!
28//! Every provider documents what it permits via
29//! [`CryptoProvider::is_legacy_allowed`]. When `false`, MD5, SHA-1
30//! signing, RC4, and RSA-PKCS#1-v1.5 with SHA-1 return
31//! [`Error::AlgorithmNotPermitted`]. SHA-1 *verification* of
32//! historical signatures is permitted (NIST SP 800-131A) — the policy
33//! split happens in [`SignatureVerifier`] vs [`Signer`].
34
35use super::error::Result;
36use super::types::{
37    AesKeySize, EcCurve, HashAlgorithm, Padding, RsaPublicKey, RsaScheme, SigningAlgorithm,
38};
39
40/// Incremental hashing.
41///
42/// Modeled after the `digest` crate's `DynDigest` so providers can
43/// trivially adapt — but stripped to just the operations PDF needs
44/// (no XOF, no variable-output, no reset).
45pub trait Hasher: Send {
46    /// Feed input into the hash state. May be called any number of
47    /// times before [`Self::finalize`].
48    fn update(&mut self, data: &[u8]);
49
50    /// Finalize the hash, consuming `self`. The returned `Vec` is
51    /// exactly [`HashAlgorithm::output_size`] bytes long.
52    ///
53    /// Boxed receiver lets implementors live behind `Box<dyn Hasher>`
54    /// without paying for `Sized` constraints up the call stack.
55    fn finalize(self: Box<Self>) -> Vec<u8>;
56
57    /// Reports the algorithm so callers can sanity-check the output
58    /// size or feed the right OID into a CMS construction.
59    fn algorithm(&self) -> HashAlgorithm;
60}
61
62/// Symmetric encryption operations PDF needs.
63///
64/// All methods return owned `Vec<u8>` to match the existing
65/// `src/encryption/aes.rs` / `src/encryption/rc4.rs` shape so Phase 3
66/// migration is mechanical. Performance-critical callers can be
67/// converted to streaming later (in-place CBC, etc.) without breaking
68/// the trait — adding methods is non-breaking.
69pub trait SymmetricCipher: Send + Sync {
70    /// AES-CBC encrypt.
71    ///
72    /// `key.len()` must equal `key_size.key_bytes()`; `iv.len()` must
73    /// be 16. With [`Padding::None`], `data.len()` must be a multiple
74    /// of 16.
75    fn aes_cbc_encrypt(
76        &self,
77        key_size: AesKeySize,
78        key: &[u8],
79        iv: &[u8],
80        data: &[u8],
81        padding: Padding,
82    ) -> Result<Vec<u8>>;
83
84    /// AES-CBC decrypt. Same argument constraints as
85    /// [`Self::aes_cbc_encrypt`].
86    fn aes_cbc_decrypt(
87        &self,
88        key_size: AesKeySize,
89        key: &[u8],
90        iv: &[u8],
91        data: &[u8],
92        padding: Padding,
93    ) -> Result<Vec<u8>>;
94
95    /// RC4 encrypt/decrypt (the operation is symmetric so one method
96    /// covers both directions).
97    ///
98    /// Required for PDF Standard Security R≤4 (ISO 32000-1 §7.6.3).
99    /// Returns [`super::error::Error::AlgorithmNotPermitted`] under FIPS providers.
100    fn rc4(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>>;
101}
102
103/// Verify a digital signature.
104///
105/// SHA-1 is permitted here per NIST SP 800-131A (verification of
106/// historical signatures). Use [`Signer`] for generation — that path
107/// rejects SHA-1 under FIPS.
108pub trait SignatureVerifier: Send + Sync {
109    /// Verify an RSA-PKCS#1-v1.5 signature over the raw *message* bytes.
110    ///
111    /// The implementation hashes `message` with `hash` internally — the
112    /// same convention as `verify_rsa_pss` and `verify_ecdsa`. Passing
113    /// the message (rather than a pre-computed digest) allows providers
114    /// that only expose a message-level API (e.g., aws-lc-rs 1.x) to
115    /// implement this without a lower-level primitive.
116    fn verify_rsa_pkcs1v15(
117        &self,
118        pubkey: &RsaPublicKey<'_>,
119        hash: HashAlgorithm,
120        message: &[u8],
121        signature: &[u8],
122    ) -> Result<()>;
123
124    /// Verify an RSA-PSS signature over the *message* bytes (PSS
125    /// internally applies the hash; salt length defaults to digest
126    /// size per RFC 8017 §9.1).
127    fn verify_rsa_pss(
128        &self,
129        pubkey: &RsaPublicKey<'_>,
130        hash: HashAlgorithm,
131        message: &[u8],
132        signature: &[u8],
133    ) -> Result<()>;
134
135    /// Verify an ECDSA signature over the *message* bytes. The
136    /// implementation applies the standard hash for the curve
137    /// (SHA-256 for P-256, SHA-384 for P-384) — `aws-lc-rs` and the
138    /// `p256` / `p384` crates' `Verifier::verify` already hash
139    /// internally.
140    ///
141    /// `pubkey_sec1` is the SEC1-encoded uncompressed public point
142    /// (`0x04 || X || Y`); `signature_der` is the ASN.1 DER-encoded
143    /// signature (the form CMS / X.509 carry).
144    fn verify_ecdsa(
145        &self,
146        curve: EcCurve,
147        pubkey_sec1: &[u8],
148        message: &[u8],
149        signature_der: &[u8],
150    ) -> Result<()>;
151}
152
153/// Opaque signing handle.
154///
155/// Separating the handle type from the trait lets a provider back
156/// `Signer` with anything: a software RSA key parsed from PKCS#8, an
157/// HSM session, a Cloud KMS reference, a smart-card PKCS#11 slot. The
158/// handle just has to remember its [`SigningAlgorithm`] and
159/// `sign(message)`.
160///
161/// # PDF context
162///
163/// `signer.rs::create_pkcs7_signature` only needs the final signing
164/// step to be opaque — DER/PEM key loading + CMS construction stay in
165/// non-trait code. The trait is therefore intentionally narrow.
166pub trait Signer: Send {
167    /// Reports which `(asymmetric algo, hash)` pair this signer
168    /// produces. Callers use this to populate the CMS
169    /// `digestAlgorithm` and `signatureAlgorithm` fields.
170    fn algorithm(&self) -> SigningAlgorithm;
171
172    /// Sign `message`. For RSA-PKCS#1-v1.5 the caller passes a
173    /// pre-built `DigestInfo` (algorithm OID + hashed bytes) and the
174    /// signer applies raw RSA. For RSA-PSS and ECDSA the caller
175    /// passes either the raw message (PSS internally hashes) or the
176    /// pre-computed digest (ECDSA), as documented per scheme.
177    ///
178    /// The exact "what does `message` mean" contract follows what the
179    /// existing CMS construction expects, so Phase 4 can keep
180    /// `signer.rs` byte-equal.
181    fn sign(&self, message: &[u8]) -> Result<Vec<u8>>;
182}
183
184/// The root trait that ties everything together.
185///
186/// Implementations live in:
187///
188/// - [`super::rust_provider::RustCryptoProvider`] (Phase 2) — default,
189///   uses `sha2`/`sha1`/`md-5`/`aes`/`rsa`/`p256`/`p384`. Permits
190///   legacy.
191/// - `super::aws_lc_provider::AwsLcProvider` (Phase 6, behind
192///   `--features fips`) — FIPS 140-3 validated. Refuses
193///   legacy.
194pub trait CryptoProvider: Send + Sync + 'static {
195    /// Human-readable provider name for logs / SBOM annotations.
196    fn name(&self) -> &'static str;
197
198    /// Whether legacy algorithms (MD5, SHA-1 sign, RC4) are allowed.
199    /// FIPS providers return `false`.
200    ///
201    /// Note: SHA-1 *verification* is allowed regardless — a separate
202    /// policy via [`SignatureVerifier`].
203    fn is_legacy_allowed(&self) -> bool;
204
205    /// Construct a hasher for `algo`. Returns
206    /// [`super::error::Error::AlgorithmNotPermitted`] if the
207    /// algorithm is forbidden under this provider's policy.
208    fn hasher(&self, algo: HashAlgorithm) -> Result<Box<dyn Hasher>>;
209
210    /// Returns the symmetric cipher implementation. Always Some — the
211    /// provider trait guarantees AES support; only `rc4()` may fail
212    /// at call time under FIPS.
213    fn symmetric(&self) -> &dyn SymmetricCipher;
214
215    /// Returns the signature verification implementation.
216    fn verifier(&self) -> &dyn SignatureVerifier;
217
218    /// Fill `out` with cryptographically strong random bytes. Both
219    /// providers source this from the OS RNG; `RustCryptoProvider`
220    /// uses `getrandom`, `AwsLcProvider` uses
221    /// `aws_lc_rs::rand::SystemRandom`.
222    fn random_bytes(&self, out: &mut [u8]) -> Result<()>;
223
224    /// Build a [`Signer`] from the provided [`SigningKeyMaterial`].
225    /// Software providers parse the PEM/DER bytes; HSM/PKCS#11
226    /// providers ignore the bytes path and instead consume their
227    /// own handle variant (the enum is `#[non_exhaustive]` so adding
228    /// `Pkcs11Slot` later isn't a breaking change).
229    fn signer(&self, key: &SigningKeyMaterial<'_>) -> Result<Box<dyn Signer>>;
230}
231
232/// Material for constructing a [`Signer`].
233///
234/// `#[non_exhaustive]` so HSM/Cloud-KMS providers can add their own
235/// variants in a follow-up release.
236#[non_exhaustive]
237#[derive(Debug)]
238pub enum SigningKeyMaterial<'a> {
239    /// PKCS#8 DER-encoded private key bytes. Software providers
240    /// (`RustCryptoProvider`, `AwsLcProvider`) parse this directly.
241    Pkcs8Der {
242        /// Algorithm hint so the provider knows which scheme + hash
243        /// to bind into the resulting signer.
244        algo: SigningAlgorithm,
245        /// PKCS#8 DER bytes.
246        bytes: &'a [u8],
247    },
248    /// PKCS#1 DER-encoded RSA private key (legacy software keys).
249    Pkcs1Der {
250        /// RSA padding scheme the resulting signer should apply.
251        scheme: RsaScheme,
252        /// Message digest the signer should use.
253        hash: HashAlgorithm,
254        /// PKCS#1 DER bytes.
255        bytes: &'a [u8],
256    },
257}