From f8fb2d2a140a8495615e8067c1ac965d0b50a426 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 21 Jan 2026 14:38:06 -0700 Subject: [PATCH] [WIP] Add `RsaPrivateKey`/`RsaPublicKey` inherent methods Adds algorithm-specific inherent methods for encryption and signing prefixed with the algorithm name, e.g. `pkcs1v15_decrypt`, `pkcs1v15_sign`, as an alternative to the `PaddingScheme` and `SignatureScheme` traits. We already have an entirely separate trait-based API from `PaddingScheme` and `SignatureScheme`, namely traits like `RandomizedEncryptor`, `RandomizedDecryptor`, and `Decryptor`, along with the traits from the `signature` crate. These traits are well-typed and enforce usage of a particular algorithm's family of types, but also seem to have overlapping responsibilities / use cases to the `PaddingScheme` and `SignatureScheme` traits. The current `PaddingScheme` and `SignatureScheme` traits do let you mix encryption and signing operations with a single key type (along with different algorithms), but they're a little awkward to use and I don't think we're getting a whole lot out of the trait-based abstraction they provide which isn't provided better by the aforementioned well-typed traits. The goal of being able to execute any operation of any algorithm you want on a given key type can also be met by just having a bunch of methods which do exactly what you want, which is what this PR proposes. --- Cargo.toml | 2 +- src/pkcs1v15.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 150353ac..48993175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ rstest = "0.26.1" name = "key" [features] -default = ["std", "encoding"] +default = ["std", "encoding", "getrandom"] encoding = ["dep:pkcs1", "dep:pkcs8", "dep:spki"] hazmat = [] getrandom = ["crypto-bigint/getrandom", "crypto-common"] diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index 62a08dc1..c059882d 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -31,6 +31,140 @@ use crate::errors::{Error, Result}; use crate::key::{self, RsaPrivateKey, RsaPublicKey}; use crate::traits::{PaddingScheme, PublicKeyParts, SignatureScheme}; +#[cfg(not(feature = "getrandom"))] +use crate::dummy_rng::DummyRng; +#[cfg(feature = "getrandom")] +use crypto_common::getrandom::SysRng; + +impl RsaPublicKey { + /// Perform PKCS#1v1.5 encryption, encrypting the given `plaintext` message with this public + /// key. + #[cfg(feature = "getrandom")] + pub fn pkcs1v15_encrypt(&self, plaintext: &[u8]) -> Result> { + self.pkcs1v15_encrypt_with_rng(&mut SysRng, plaintext) + } + + /// Perform PKCS#1v1.5 encryption, obtaining randomness from the provided RNG. + pub fn pkcs1v15_encrypt_with_rng(&self, rng: &mut Rng, plaintext: &[u8]) -> Result> + where + Rng: TryCryptoRng + ?Sized, + { + encrypt(rng, self, plaintext) + } + + /// Verify the given message after first hashing it with the [`Digest`] algorithm specified by + /// `D`. + pub fn pkcs1v15_verify(&self, msg: &[u8], sig: &[u8]) -> Result<()> + where + D: Digest + AssociatedOid, + { + let hash = D::digest(msg); + self.pkcs1v15_verify_prehash::(&hash, sig) + } + + /// Verify the hash of a message computed using the [`Digest`] algorithm specified by `D`. + pub fn pkcs1v15_verify_prehash(&self, hash: &[u8], sig: &[u8]) -> Result<()> + where + D: Digest + AssociatedOid, + { + if hash.len() != ::output_size() { + return Err(Error::InputNotHashed); + } + + let prefix = pkcs1v15_generate_prefix::(); + let sig = BoxedUint::from_be_slice_vartime(sig); + verify(self, &prefix, hash, &sig) + } +} + +impl RsaPrivateKey { + /// Perform PKCS#1v1.5 decryption, decrypting the given `ciphertext` message with this private + /// key. + pub fn pkcs1v15_decrypt(&self, ciphertext: &[u8]) -> Result> { + #[cfg(feature = "getrandom")] + { + self.pkcs1v15_decrypt_with_rng(&mut SysRng, ciphertext) + } + #[cfg(not(feature = "getrandom"))] + { + decrypt(Option::<&mut DummyRng>::None, self, ciphertext) + } + } + + /// Perform PKCS#1v1.5 decryption, blinding the operation with the provided RNG. + pub fn pkcs1v15_decrypt_with_rng( + &self, + rng: &mut Rng, + ciphertext: &[u8], + ) -> Result> + where + Rng: TryCryptoRng + ?Sized, + { + decrypt(Some(rng), self, ciphertext) + } + + /// Sign the given message after first hashing it with the [`Digest`] algorithm specified by + /// `D`. + pub fn pkcs1v15_sign(&self, msg: &[u8]) -> Result> + where + D: Digest + AssociatedOid, + { + let hash = D::digest(msg); + self.pkcs1v15_sign_prehash::(&hash) + } + + /// Sign the given message after first hashing it with the [`Digest`] algorithm specified by + /// `D`. + pub fn pkcs1v15_sign_prehash(&self, hash: &[u8]) -> Result> + where + D: Digest + AssociatedOid, + { + #[cfg(feature = "getrandom")] + { + self.pkcs1v15_sign_prehash_with_rng::(&mut SysRng, hash) + } + #[cfg(not(feature = "getrandom"))] + { + if hash.len() != ::output_size() { + return Err(Error::InputNotHashed); + } + + let prefix = pkcs1v15_generate_prefix::(); + sign(Option::<&mut DummyRng>::None, self, &prefix, hash) + } + } + + /// Sign the given message after first hashing it with the [`Digest`] algorithm specified by + /// `D`. + pub fn pkcs1v15_sign_with_rng(&self, rng: &mut Rng, msg: &[u8]) -> Result> + where + D: Digest + AssociatedOid, + Rng: TryCryptoRng + ?Sized, + { + let hash = D::digest(msg); + self.pkcs1v15_sign_prehash_with_rng::(rng, &hash) + } + + /// Sign the resulting hash of a message after first hashing it with the [`Digest`] algorithm + /// specified by `D`. + pub fn pkcs1v15_sign_prehash_with_rng( + &self, + rng: &mut Rng, + hash: &[u8], + ) -> Result> + where + D: Digest + AssociatedOid, + Rng: TryCryptoRng + ?Sized, + { + if hash.len() != ::output_size() { + return Err(Error::InputNotHashed); + } + + let prefix = pkcs1v15_generate_prefix::(); + sign(Some(rng), self, &prefix, hash) + } +} + /// Encryption using PKCS#1 v1.5 padding. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct Pkcs1v15Encrypt;