Skip to content

Commit

Permalink
rsa: Adds a signer for PSS and PKCS1#1.5
Browse files Browse the repository at this point in the history
This adds a mockhsm implementation and provides an integration test for
both signers.
  • Loading branch information
baloo committed Nov 28, 2023
1 parent 115a119 commit eab4620
Show file tree
Hide file tree
Showing 20 changed files with 618 additions and 56 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bitflags = "2"
cmac = "0.7"
cbc = "0.1"
ccm = { version = "0.5", features = ["std"] }
digest = { version = "0.10", default-features = false }
ecdsa = { version = "0.16", default-features = false }
ed25519 = "2"
log = "0.4"
Expand All @@ -32,27 +33,28 @@ serde = { version = "1", features = ["serde_derive"] }
rand_core = { version = "0.6", features = ["std"] }
rsa = "0.9.5"
signature = { version = "2", features = ["derive"] }
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
subtle = "2"
thiserror = "1"
time = { version = "0.3", features = ["serde"] }
uuid = { version = "1", default-features = false }
zeroize = { version = "1", features = ["zeroize_derive"] }

# optional dependencies
digest = { version = "0.10", optional = true, default-features = false }
ed25519-dalek = { version = "2", optional = true, features = ["rand_core"] }
hmac = { version = "0.12", optional = true }
k256 = { version = "0.13", optional = true, features = ["ecdsa", "sha256"] }
pbkdf2 = { version = "0.12", optional = true, default-features = false, features = ["hmac"] }
serde_json = { version = "1", optional = true }
spki = { version = "0.7.3", optional = true, default-features = false }
rusb = { version = "0.9", optional = true }
sha2 = { version = "0.10", optional = true }
tiny_http = { version = "0.12", optional = true }

[dev-dependencies]
ed25519-dalek = "2"
once_cell = "1"
rsa = { version = "0.9.5", features = ["sha1", "sha2"] }
p256 = { version = "0.13", features = ["ecdsa"] }
x509-cert = { version = "0.2.4", features = ["builder"] }

Expand All @@ -61,11 +63,11 @@ default = ["http", "passwords", "setup"]
der-signer = ["spki", "sha2/oid"]
http-server = ["tiny_http"]
http = []
mockhsm = ["digest", "ecdsa/arithmetic", "ed25519-dalek", "p256/ecdsa", "secp256k1"]
passwords = ["hmac", "pbkdf2", "sha2"]
mockhsm = ["ecdsa/arithmetic", "ed25519-dalek", "p256/ecdsa", "secp256k1"]
passwords = ["hmac", "pbkdf2"]
secp256k1 = ["k256"]
setup = ["passwords", "serde_json", "uuid/serde"]
untested = ["sha2"]
untested = []
usb = ["rusb"]

[package.metadata.docs.rs]
Expand All @@ -75,3 +77,6 @@ rustdoc-args = ["--cfg", "docsrs"]
[[example]]
name = "connector_http_server"
required-features = ["http-server", "usb"]

[patch.crates-io]
rsa = { git = "https://github.com/baloo/RSA.git", branch = "baloo/pkcs1v15/expose-RsaSignatureAssociatedOid" }
8 changes: 8 additions & 0 deletions src/asymmetric/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ impl Algorithm {
Algorithm::EcBp512 => 64,
}
}

/// Returns true if the algorithm is RSA
pub fn is_rsa(self) -> bool {
matches!(
self,
Algorithm::Rsa2048 | Algorithm::Rsa3072 | Algorithm::Rsa4096
)
}
}

impl_algorithm_serializers!(Algorithm);
16 changes: 16 additions & 0 deletions src/asymmetric/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use ::ecdsa::elliptic_curve::{
bigint::Integer, generic_array::GenericArray, point::PointCompression, sec1, FieldBytesSize,
PrimeCurve,
};
use num_traits::FromPrimitive;
use rsa::{BigUint, RsaPublicKey};
use serde::{Deserialize, Serialize};

/// Response from `command::get_public_key`
Expand Down Expand Up @@ -74,6 +76,20 @@ impl PublicKey {
None
}
}

/// Return the RSA public key
pub fn rsa(&self) -> Option<RsaPublicKey> {
if !self.algorithm.is_rsa() {
return None;
}

const EXP: u64 = 65537;

let modulus = BigUint::from_bytes_be(&self.bytes);
let exp = BigUint::from_u64(EXP).expect("invalid static exponent");

RsaPublicKey::new(modulus, exp).ok()
}
}

impl AsRef<[u8]> for PublicKey {
Expand Down
6 changes: 3 additions & 3 deletions src/authentication/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ use rand_core::{OsRng, RngCore};
use std::fmt::{self, Debug};
use zeroize::Zeroize;

#[cfg(feature = "passwords")]
use sha2::Sha256;

#[cfg(feature = "pbkdf2")]
use pbkdf2::pbkdf2_hmac;

#[cfg(feature = "sha2")]
use sha2::Sha256;

/// Auth keys are 2 * AES-128 keys
pub const SIZE: usize = 32;

Expand Down
62 changes: 35 additions & 27 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ use crate::{
object::{self, commands::*, generate},
opaque::{self, commands::*},
otp::{self, commands::*},
rsa::{self, oaep::commands::*},
rsa::{self, oaep::commands::*, pkcs1::commands::*, pss::commands::*, SignatureAlgorithm},
serialization::{deserialize, serialize},
session::{self, Session},
template::{commands::*, Template},
uuid,
wrap::{self, commands::*},
};
use sha2::Sha256;
use std::{
sync::{Arc, Mutex},
time::{Duration, Instant},
Expand All @@ -44,14 +45,10 @@ use std::{
use std::{thread, time::SystemTime};

#[cfg(feature = "untested")]
use {
crate::{
algorithm::Algorithm,
ecdh::{self, commands::*},
rsa::{pkcs1::commands::*, pss::commands::*},
ssh::{self, commands::*},
},
sha2::{Digest, Sha256},
use crate::{
algorithm::Algorithm,
ecdh::{self, commands::*},
ssh::{self, commands::*},
};

#[cfg(docsrs)]
Expand Down Expand Up @@ -1019,64 +1016,75 @@ impl Client {

/// Compute an RSASSA-PKCS#1v1.5 signature of the SHA-256 hash of the given data.
///
/// **WARNING**: This functionality has not been tested and has not yet been
/// confirmed to actually work! USE AT YOUR OWN RISK!
///
/// You will need to enable the `untested` cargo feature to use it.
///
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pkcs1.html>
#[cfg(feature = "untested")]
pub fn sign_rsa_pkcs1v15_sha256(
pub(crate) fn sign_rsa_pkcs1v15<S: SignatureAlgorithm>(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pkcs1::Signature, Error> {
Ok(self
.send_command(SignPkcs1Command {
key_id,
digest: Sha256::digest(data).as_slice().into(),
digest: S::digest(data).as_slice().into(),
})?
.into())
}

/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
///
/// **WARNING**: This functionality has not been tested and has not yet been
/// confirmed to actually work! USE AT YOUR OWN RISK!
/// Compute an RSASSA-PKCS#1v1.5 signature of the SHA-256 hash of the given data.
///
/// You will need to enable the `untested` cargo feature to use it.
///
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pkcs1.html>
pub fn sign_rsa_pkcs1v15_sha256(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pkcs1::Signature, Error> {
self.sign_rsa_pkcs1v15::<Sha256>(key_id, data)
}

/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
///
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pss.html>
#[cfg(feature = "untested")]
pub fn sign_rsa_pss_sha256(
pub(crate) fn sign_rsa_pss<S: SignatureAlgorithm>(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pss::Signature, Error> {
ensure!(
data.len() > rsa::pss::MAX_MESSAGE_SIZE,
data.len() < rsa::pss::MAX_MESSAGE_SIZE,
ErrorKind::ProtocolError,
"message too large to be signed (max: {})",
rsa::pss::MAX_MESSAGE_SIZE
);

let mut hasher = Sha256::default();

let length = data.len() as u16;
hasher.update(length.to_be_bytes());
let mut hasher = S::new();
hasher.update(data);
let digest = hasher.finalize();

Ok(self
.send_command(SignPssCommand {
key_id,
mgf1_hash_alg: rsa::mgf::Algorithm::Sha256,
mgf1_hash_alg: S::MGF_ALGORITHM,
salt_len: digest.as_slice().len() as u16,
digest: digest.as_slice().into(),
})?
.into())
}

/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
///
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pss.html>
pub fn sign_rsa_pss_sha256(
&self,
key_id: object::Id,
data: &[u8],
) -> Result<rsa::pss::Signature, Error> {
self.sign_rsa_pss::<Sha256>(key_id, data)
}

/// Sign an SSH certificate using the given template.
///
/// **WARNING**: This functionality has not been tested and has not yet been
Expand Down
Loading

0 comments on commit eab4620

Please sign in to comment.