Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions benches/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use base64ct::{Base64, Encoding};
use num_bigint::BigUint;
use num_traits::{FromPrimitive, Num};
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use rsa::signature::RandomizedSigner;
use rsa::{PaddingScheme, RsaPrivateKey};
use sha2::{Digest, Sha256};
use test::Bencher;
Expand Down Expand Up @@ -41,17 +42,25 @@ fn bench_rsa_2048_pkcsv1_decrypt(b: &mut Bencher) {
#[bench]
fn bench_rsa_2048_pkcsv1_sign_blinded(b: &mut Bencher) {
let priv_key = get_key();
let signing_key = rsa::pkcs1v15::SigningKey::<Sha256>::new_with_prefix(priv_key);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a regression in API usability: having to create a newtype struct to call a single function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dignifiedquire why? The signing_key is a long term object, which can be stored and further used. This is more or less what we have for other PK types. Also I think we have all API in place to work with these keys as a first-class objects (generate, read, write, etc). The only thing that prevents me from thinking about turning RsaPublicKey and RsaPrivateKey into create-private structs is the existence of S+E keys.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because now I am forced to decide on the padding + hashing when storing the key. Most of my use cases are that these are dynamically chosen parameters, so I need to store the key itself, and apply the different algorithms depending on runtime inputs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more or less what we have for other PK types

Yes, and I think it is not great there either, if you have dynamic cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as an example, the code I would have to write now will look sth like this

match padding {
  "pkcs" => {
    let key = rsa::pkcs1v15::SigningKey::<Sha256>::new_with_prefix(priv_key);
    key.sign()
  }
  ...
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lumag I think it's somewhat more common with RSA due to legacy reasons for keys to be used in multiple roles, even across encryption and signing.

See for example TLS < 1.3 using RSA keys for both key encipherment and digital signatures as part of a handshake.

Which algorithm is used depends on a combination of server-side configuration and the capabilities of the client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Or the (rare) GPG S+E keys.
So, how do we want to proceed? Do we drop these two PRs (perfectly fine with me) or would you like for me to improve them? Let's probably decide on this one first.

let digest = Sha256::digest(b"testing").to_vec();
let mut rng = ChaCha8Rng::from_seed([42; 32]);

b.iter(|| {
let res = priv_key
.sign_blinded(
&mut rng,
PaddingScheme::new_pkcs1v15_sign::<Sha256>(),
&digest,
)
.unwrap();
let res = signing_key.sign_with_rng(&mut rng, &digest);
test::black_box(res);
});
}

#[bench]
fn bench_rsa_2048_pss_sign_blinded(b: &mut Bencher) {
let priv_key = get_key();
let signing_key = rsa::pss::SigningKey::<Sha256>::new(priv_key);
let digest = Sha256::digest(b"testing").to_vec();
let mut rng = ChaCha8Rng::from_seed([42; 32]);

b.iter(|| {
let res = signing_key.sign_with_rng(&mut rng, &digest);
test::black_box(res);
});
}
86 changes: 1 addition & 85 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::errors::{Error, Result};

use crate::padding::PaddingScheme;
use crate::raw::{DecryptionPrimitive, EncryptionPrimitive};
use crate::{oaep, pkcs1v15, pss};
use crate::{oaep, pkcs1v15};

/// Components of an RSA public key.
pub trait PublicKeyParts {
Expand Down Expand Up @@ -179,12 +179,6 @@ pub trait PublicKey: EncryptionPrimitive + PublicKeyParts {
padding: PaddingScheme,
msg: &[u8],
) -> Result<Vec<u8>>;

/// Verify a signed message.
/// `hashed`must be the result of hashing the input using the hashing function
/// passed in through `hash`.
/// If the message is valid `Ok(())` is returned, otherwiese an `Err` indicating failure.
fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()>;
}

impl PublicKeyParts for RsaPublicKey {
Expand All @@ -211,22 +205,6 @@ impl PublicKey for RsaPublicKey {
mut mgf_digest,
label,
} => oaep::encrypt(rng, self, msg, &mut *digest, &mut *mgf_digest, label),
_ => Err(Error::InvalidPaddingScheme),
}
}

fn verify(&self, padding: PaddingScheme, hashed: &[u8], sig: &[u8]) -> Result<()> {
match padding {
PaddingScheme::PKCS1v15Sign { hash_len, prefix } => {
if let Some(hash_len) = hash_len {
if hashed.len() != hash_len {
return Err(Error::InputNotHashed);
}
}
pkcs1v15::verify(self, prefix.as_ref(), hashed, sig)
}
PaddingScheme::PSS { mut digest, .. } => pss::verify(self, hashed, sig, &mut *digest),
_ => Err(Error::InvalidPaddingScheme),
}
}
}
Expand Down Expand Up @@ -466,7 +444,6 @@ impl RsaPrivateKey {
&mut *mgf_digest,
label,
),
_ => Err(Error::InvalidPaddingScheme),
}
}

Expand All @@ -493,67 +470,6 @@ impl RsaPrivateKey {
&mut *mgf_digest,
label,
),
_ => Err(Error::InvalidPaddingScheme),
}
}

/// Sign the given digest.
pub fn sign(&self, padding: PaddingScheme, digest_in: &[u8]) -> Result<Vec<u8>> {
match padding {
// need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything
PaddingScheme::PKCS1v15Sign { hash_len, prefix } => {
if let Some(hash_len) = hash_len {
if digest_in.len() != hash_len {
return Err(Error::InputNotHashed);
}
}
pkcs1v15::sign::<DummyRng, _>(None, self, prefix.as_ref(), digest_in)
}
_ => Err(Error::InvalidPaddingScheme),
}
}

/// Sign the given digest using the provided rng
///
/// Use `rng` for signature process.
pub fn sign_with_rng<R: CryptoRngCore>(
&self,
rng: &mut R,
padding: PaddingScheme,
digest_in: &[u8],
) -> Result<Vec<u8>> {
match padding {
PaddingScheme::PSS {
mut digest,
salt_len,
} => pss::sign::<R, _>(rng, false, self, digest_in, salt_len, &mut *digest),
_ => Err(Error::InvalidPaddingScheme),
}
}

/// Sign the given digest.
///
/// Use `rng` for blinding.
pub fn sign_blinded<R: CryptoRngCore>(
&self,
rng: &mut R,
padding: PaddingScheme,
digest_in: &[u8],
) -> Result<Vec<u8>> {
match padding {
PaddingScheme::PKCS1v15Sign { hash_len, prefix } => {
if let Some(hash_len) = hash_len {
if digest_in.len() != hash_len {
return Err(Error::InputNotHashed);
}
}
pkcs1v15::sign(Some(rng), self, prefix.as_ref(), digest_in)
}
PaddingScheme::PSS {
mut digest,
salt_len,
} => pss::sign::<R, _>(rng, true, self, digest_in, salt_len, &mut *digest),
_ => Err(Error::InvalidPaddingScheme),
}
}
}
Expand Down
68 changes: 0 additions & 68 deletions src/padding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,12 @@ use alloc::string::{String, ToString};
use core::fmt;

use digest::{Digest, DynDigest};
use pkcs8::AssociatedOid;

use crate::pkcs1v15;

/// Available padding schemes.
pub enum PaddingScheme {
/// Encryption and Decryption using PKCS1v15 padding.
PKCS1v15Encrypt,

/// Sign and Verify using PKCS1v15 padding.
PKCS1v15Sign {
/// Length of hash to use.
hash_len: Option<usize>,

/// Prefix.
prefix: Box<[u8]>,
},

/// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
///
/// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`,
Expand All @@ -44,32 +32,16 @@ pub enum PaddingScheme {
/// Optional label.
label: Option<String>,
},

/// Sign and Verify using PSS padding.
PSS {
/// Digest type to use.
digest: Box<dyn DynDigest + Send + Sync>,

/// Salt length.
salt_len: Option<usize>,
},
}

impl fmt::Debug for PaddingScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PaddingScheme::PKCS1v15Encrypt => write!(f, "PaddingScheme::PKCS1v15Encrypt"),
PaddingScheme::PKCS1v15Sign { prefix, .. } => {
write!(f, "PaddingScheme::PKCS1v15Sign({:?})", prefix)
}
PaddingScheme::OAEP { ref label, .. } => {
// TODO: How to print the digest name?
write!(f, "PaddingScheme::OAEP({:?})", label)
}
PaddingScheme::PSS { ref salt_len, .. } => {
// TODO: How to print the digest name?
write!(f, "PaddingScheme::PSS(salt_len: {:?})", salt_len)
}
}
}
}
Expand All @@ -80,30 +52,6 @@ impl PaddingScheme {
PaddingScheme::PKCS1v15Encrypt
}

/// Create new PKCS#1 v1.5 padding for computing a raw signature.
///
/// This sets `hash_len` to `None` and uses an empty `prefix`.
pub fn new_pkcs1v15_sign_raw() -> Self {
PaddingScheme::PKCS1v15Sign {
hash_len: None,
prefix: Box::new([]),
}
}

/// Create new PKCS#1 v1.5 padding for the given digest.
///
/// The digest must have an [`AssociatedOid`]. Make sure to enable the `oid`
/// feature of the relevant digest crate.
pub fn new_pkcs1v15_sign<D>() -> Self
where
D: Digest + AssociatedOid,
{
PaddingScheme::PKCS1v15Sign {
hash_len: Some(<D as Digest>::output_size()),
prefix: pkcs1v15::generate_prefix::<D>().into_boxed_slice(),
}
}

/// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1.
/// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`.
///
Expand Down Expand Up @@ -183,20 +131,4 @@ impl PaddingScheme {
label: Some(label.as_ref().to_string()),
}
}

/// New PSS padding for the given digest.
pub fn new_pss<T: 'static + Digest + DynDigest + Send + Sync>() -> Self {
PaddingScheme::PSS {
digest: Box::new(T::new()),
salt_len: None,
}
}

/// New PSS padding for the given digest with a salt value of the given length.
pub fn new_pss_with_salt<T: 'static + Digest + DynDigest + Send + Sync>(len: usize) -> Self {
PaddingScheme::PSS {
digest: Box::new(T::new()),
salt_len: Some(len),
}
}
}
88 changes: 1 addition & 87 deletions src/pkcs1v15.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ mod tests {
use sha3::Sha3_256;
use signature::{RandomizedSigner, Signer, Verifier};

use crate::{PaddingScheme, PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use crate::{PaddingScheme, PublicKeyParts, RsaPrivateKey, RsaPublicKey};

#[test]
fn test_non_zero_bytes() {
Expand Down Expand Up @@ -719,39 +719,6 @@ mod tests {
}
}

#[test]
fn test_sign_pkcs1v15() {
let priv_key = get_private_key();

let tests = [(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
)];

for (text, expected) in &tests {
let digest = Sha1::digest(text.as_bytes()).to_vec();

let out = priv_key
.sign(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), &digest)
.unwrap();
assert_ne!(out, digest);
assert_eq!(out, expected);

let mut rng = ChaCha8Rng::from_seed([42; 32]);
let out2 = priv_key
.sign_blinded(
&mut rng,
PaddingScheme::new_pkcs1v15_sign::<Sha1>(),
&digest,
)
.unwrap();
assert_eq!(out2, expected);
}
}

#[test]
fn test_sign_pkcs1v15_signer() {
let priv_key = get_private_key();
Expand Down Expand Up @@ -858,43 +825,6 @@ mod tests {
}
}

#[test]
fn test_verify_pkcs1v15() {
let priv_key = get_private_key();

let tests = [
(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362ae"
),
true,
),
(
"Test.\n",
hex!(
"a4f3fa6ea93bcdd0c57be020c1193ecbfd6f200a3d95c409769b029578fa0e33"
"6ad9a347600e40d3ae823b8c7e6bad88cc07c1d54c3a1523cbbb6d58efc362af"
),
false,
),
];
let pub_key: RsaPublicKey = priv_key.into();

for (text, sig, expected) in &tests {
let digest = Sha1::digest(text.as_bytes()).to_vec();

let result = pub_key.verify(PaddingScheme::new_pkcs1v15_sign::<Sha1>(), &digest, sig);
match expected {
true => result.expect("failed to verify"),
false => {
result.expect_err("expected verifying error");
}
}
}
}

#[test]
fn test_verify_pkcs1v15_signer() {
let priv_key = get_private_key();
Expand Down Expand Up @@ -972,22 +902,6 @@ mod tests {
}
}
}
#[test]
fn test_unpadded_signature() {
let msg = b"Thu Dec 19 18:06:16 EST 2013\n";
let expected_sig = Base64::decode_vec("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==").unwrap();
let priv_key = get_private_key();

let sig = priv_key
.sign(PaddingScheme::new_pkcs1v15_sign_raw(), msg)
.unwrap();
assert_eq!(expected_sig, sig);

let pub_key: RsaPublicKey = priv_key.into();
pub_key
.verify(PaddingScheme::new_pkcs1v15_sign_raw(), msg, &sig)
.expect("failed to verify");
}

#[test]
fn test_unpadded_signature_hazmat() {
Expand Down
Loading