Skip to content

Commit 5be7df0

Browse files
committed
rsa: Adds a signer for PSS and PKCS1#1.5
This adds a mockhsm implementation and provides an integration test for both signers.
1 parent 115a119 commit 5be7df0

File tree

20 files changed

+632
-57
lines changed

20 files changed

+632
-57
lines changed

Cargo.lock

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bitflags = "2"
2222
cmac = "0.7"
2323
cbc = "0.1"
2424
ccm = { version = "0.5", features = ["std"] }
25+
digest = { version = "0.10", default-features = false }
2526
ecdsa = { version = "0.16", default-features = false }
2627
ed25519 = "2"
2728
log = "0.4"
@@ -30,29 +31,30 @@ p256 = { version = "0.13", default-features = false, features = ["ecdsa"] }
3031
p384 = { version = "0.13", default-features = false, features = ["ecdsa"] }
3132
serde = { version = "1", features = ["serde_derive"] }
3233
rand_core = { version = "0.6", features = ["std"] }
33-
rsa = "0.9.5"
34+
rsa = "0.9.6"
3435
signature = { version = "2", features = ["derive"] }
36+
sha1 = { version = "0.10", features = ["oid"] }
37+
sha2 = { version = "0.10", features = ["oid"] }
3538
subtle = "2"
3639
thiserror = "1"
3740
time = { version = "0.3", features = ["serde"] }
3841
uuid = { version = "1", default-features = false }
3942
zeroize = { version = "1", features = ["zeroize_derive"] }
4043

4144
# optional dependencies
42-
digest = { version = "0.10", optional = true, default-features = false }
4345
ed25519-dalek = { version = "2", optional = true, features = ["rand_core"] }
4446
hmac = { version = "0.12", optional = true }
4547
k256 = { version = "0.13", optional = true, features = ["ecdsa", "sha256"] }
4648
pbkdf2 = { version = "0.12", optional = true, default-features = false, features = ["hmac"] }
4749
serde_json = { version = "1", optional = true }
4850
spki = { version = "0.7.3", optional = true, default-features = false }
4951
rusb = { version = "0.9", optional = true }
50-
sha2 = { version = "0.10", optional = true }
5152
tiny_http = { version = "0.12", optional = true }
5253

5354
[dev-dependencies]
5455
ed25519-dalek = "2"
5556
once_cell = "1"
57+
rsa = { version = "0.9.6", features = ["sha1", "sha2"] }
5658
p256 = { version = "0.13", features = ["ecdsa"] }
5759
x509-cert = { version = "0.2.4", features = ["builder"] }
5860

@@ -61,11 +63,11 @@ default = ["http", "passwords", "setup"]
6163
der-signer = ["spki", "sha2/oid"]
6264
http-server = ["tiny_http"]
6365
http = []
64-
mockhsm = ["digest", "ecdsa/arithmetic", "ed25519-dalek", "p256/ecdsa", "secp256k1"]
65-
passwords = ["hmac", "pbkdf2", "sha2"]
66+
mockhsm = ["ecdsa/arithmetic", "ed25519-dalek", "p256/ecdsa", "secp256k1"]
67+
passwords = ["hmac", "pbkdf2"]
6668
secp256k1 = ["k256"]
6769
setup = ["passwords", "serde_json", "uuid/serde"]
68-
untested = ["sha2"]
70+
untested = []
6971
usb = ["rusb"]
7072

7173
[package.metadata.docs.rs]

src/asymmetric/algorithm.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ impl Algorithm {
8989
Algorithm::EcBp512 => 64,
9090
}
9191
}
92+
93+
/// Returns true if the algorithm is RSA
94+
pub fn is_rsa(self) -> bool {
95+
matches!(
96+
self,
97+
Algorithm::Rsa2048 | Algorithm::Rsa3072 | Algorithm::Rsa4096
98+
)
99+
}
92100
}
93101

94102
impl_algorithm_serializers!(Algorithm);

src/asymmetric/public_key.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use ::ecdsa::elliptic_curve::{
55
bigint::Integer, generic_array::GenericArray, point::PointCompression, sec1, FieldBytesSize,
66
PrimeCurve,
77
};
8+
use num_traits::FromPrimitive;
9+
use rsa::{BigUint, RsaPublicKey};
810
use serde::{Deserialize, Serialize};
911

1012
/// Response from `command::get_public_key`
@@ -74,6 +76,20 @@ impl PublicKey {
7476
None
7577
}
7678
}
79+
80+
/// Return the RSA public key
81+
pub fn rsa(&self) -> Option<RsaPublicKey> {
82+
if !self.algorithm.is_rsa() {
83+
return None;
84+
}
85+
86+
const EXP: u64 = 65537;
87+
88+
let modulus = BigUint::from_bytes_be(&self.bytes);
89+
let exp = BigUint::from_u64(EXP).expect("invalid static exponent");
90+
91+
RsaPublicKey::new(modulus, exp).ok()
92+
}
7793
}
7894

7995
impl AsRef<[u8]> for PublicKey {

src/authentication/key.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ use rand_core::{OsRng, RngCore};
55
use std::fmt::{self, Debug};
66
use zeroize::Zeroize;
77

8+
#[cfg(feature = "passwords")]
9+
use sha2::Sha256;
10+
811
#[cfg(feature = "pbkdf2")]
912
use pbkdf2::pbkdf2_hmac;
1013

11-
#[cfg(feature = "sha2")]
12-
use sha2::Sha256;
13-
1414
/// Auth keys are 2 * AES-128 keys
1515
pub const SIZE: usize = 32;
1616

src/client.rs

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ use crate::{
2828
object::{self, commands::*, generate},
2929
opaque::{self, commands::*},
3030
otp::{self, commands::*},
31-
rsa::{self, oaep::commands::*},
31+
rsa::{self, oaep::commands::*, pkcs1::commands::*, pss::commands::*, SignatureAlgorithm},
3232
serialization::{deserialize, serialize},
3333
session::{self, Session},
3434
template::{commands::*, Template},
3535
uuid,
3636
wrap::{self, commands::*},
3737
};
38+
use sha2::Sha256;
3839
use std::{
3940
sync::{Arc, Mutex},
4041
time::{Duration, Instant},
@@ -44,14 +45,10 @@ use std::{
4445
use std::{thread, time::SystemTime};
4546

4647
#[cfg(feature = "untested")]
47-
use {
48-
crate::{
49-
algorithm::Algorithm,
50-
ecdh::{self, commands::*},
51-
rsa::{pkcs1::commands::*, pss::commands::*},
52-
ssh::{self, commands::*},
53-
},
54-
sha2::{Digest, Sha256},
48+
use crate::{
49+
algorithm::Algorithm,
50+
ecdh::{self, commands::*},
51+
ssh::{self, commands::*},
5552
};
5653

5754
#[cfg(docsrs)]
@@ -1019,64 +1016,75 @@ impl Client {
10191016

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

1042-
/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
1043-
///
1044-
/// **WARNING**: This functionality has not been tested and has not yet been
1045-
/// confirmed to actually work! USE AT YOUR OWN RISK!
1035+
/// Compute an RSASSA-PKCS#1v1.5 signature of the SHA-256 hash of the given data.
10461036
///
10471037
/// You will need to enable the `untested` cargo feature to use it.
10481038
///
1039+
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pkcs1.html>
1040+
pub fn sign_rsa_pkcs1v15_sha256(
1041+
&self,
1042+
key_id: object::Id,
1043+
data: &[u8],
1044+
) -> Result<rsa::pkcs1::Signature, Error> {
1045+
self.sign_rsa_pkcs1v15::<Sha256>(key_id, data)
1046+
}
1047+
1048+
/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
1049+
///
10491050
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pss.html>
1050-
#[cfg(feature = "untested")]
1051-
pub fn sign_rsa_pss_sha256(
1051+
pub(crate) fn sign_rsa_pss<S: SignatureAlgorithm>(
10521052
&self,
10531053
key_id: object::Id,
10541054
data: &[u8],
10551055
) -> Result<rsa::pss::Signature, Error> {
10561056
ensure!(
1057-
data.len() > rsa::pss::MAX_MESSAGE_SIZE,
1057+
data.len() < rsa::pss::MAX_MESSAGE_SIZE,
10581058
ErrorKind::ProtocolError,
10591059
"message too large to be signed (max: {})",
10601060
rsa::pss::MAX_MESSAGE_SIZE
10611061
);
10621062

1063-
let mut hasher = Sha256::default();
1064-
1065-
let length = data.len() as u16;
1066-
hasher.update(length.to_be_bytes());
1063+
let mut hasher = S::new();
10671064
hasher.update(data);
10681065
let digest = hasher.finalize();
10691066

10701067
Ok(self
10711068
.send_command(SignPssCommand {
10721069
key_id,
1073-
mgf1_hash_alg: rsa::mgf::Algorithm::Sha256,
1070+
mgf1_hash_alg: S::MGF_ALGORITHM,
10741071
salt_len: digest.as_slice().len() as u16,
10751072
digest: digest.as_slice().into(),
10761073
})?
10771074
.into())
10781075
}
10791076

1077+
/// Compute an RSASSA-PSS signature of the SHA-256 hash of the given data with the given key ID.
1078+
///
1079+
/// <https://developers.yubico.com/YubiHSM2/Commands/Sign_Pss.html>
1080+
pub fn sign_rsa_pss_sha256(
1081+
&self,
1082+
key_id: object::Id,
1083+
data: &[u8],
1084+
) -> Result<rsa::pss::Signature, Error> {
1085+
self.sign_rsa_pss::<Sha256>(key_id, data)
1086+
}
1087+
10801088
/// Sign an SSH certificate using the given template.
10811089
///
10821090
/// **WARNING**: This functionality has not been tested and has not yet been

0 commit comments

Comments
 (0)