Skip to content

Commit

Permalink
add support for rsa3072 and rsa4096 (#598)
Browse files Browse the repository at this point in the history
  • Loading branch information
joostd authored Feb 12, 2025
1 parent 13bdf9a commit 0d8096f
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.8.0"
description = """
Pure Rust cross-platform host-side driver for YubiKey devices from Yubico with
support for hardware-backed public-key decryption and digital signatures using
the Personal Identity Verification (PIV) application. Supports RSA (1024/2048)
the Personal Identity Verification (PIV) application. Supports RSA (1024/2048/3072/4096)
or ECC (NIST P-256/P-384) algorithms e.g, PKCS#1v1.5, ECDSA
"""
authors = ["Tony Arcieri <[email protected]>", "Yubico AB"]
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ access provided by the [`pcsc` crate].
## About

YubiKeys are versatile devices and through their PIV support, you can use them
to store a number of RSA (2048/1024) and ECC (NIST P-256/P-384) private keys
to store a number of RSA (1024/2048/3072/4096) and ECC (NIST P-256/P-384) private keys
with configurable access control policies. Both the signing (RSASSA/ECDSA) and
encryption (PKCS#1v1.5/ECIES) use cases are supported for either key type.

Expand Down Expand Up @@ -56,13 +56,16 @@ on which devices support PIV and the available functionality.
### Supported Algorithms
- **Authentication**: `3DES`
- **Encryption**:
- RSA: `RSA1024`, `RSA2048`
- RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
- **Signatures**:
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
- RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
- ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)

NOTE: RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
NOTE:

- RSASSA-PSS signatures and RSA-OAEP encryption may be supportable (TBD)
- `RSA3072` and `RSA4096` require a YubiKey with firmware 5.7 or newer.

## Minimum Supported Rust Version

Expand Down
16 changes: 16 additions & 0 deletions src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,22 @@ pub mod yubikey_signer {
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa2048;
}

/// RSA 3072 bits key
pub struct Rsa3072;

impl RsaLength for Rsa3072 {
const BIT_LENGTH: usize = 3072;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa3072;
}

/// RSA 4096 bits key
pub struct Rsa4096;

impl RsaLength for Rsa4096 {
const BIT_LENGTH: usize = 4096;
const ALGORITHM: AlgorithmId = AlgorithmId::Rsa4096;
}

/// RSA keys used to sign certificates
pub struct YubiRsa<N: RsaLength> {
_len: PhantomData<N>,
Expand Down
38 changes: 31 additions & 7 deletions src/piv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
//! Supported algorithms:
//!
//! - **Encryption**:
//! - RSA: `RSA1024`, `RSA2048`
//! - RSA: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECC: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
//! - **Signatures**:
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`
//! - RSASSA-PKCS#1v1.5: `RSA1024`, `RSA2048`, `RSA3072`, `RSA4096`
//! - ECDSA: `ECCP256`, `ECCP384` (NIST curves: P-256, P-384)
// Adapted from yubico-piv-tool:
Expand Down Expand Up @@ -485,6 +485,12 @@ pub enum AlgorithmId {
/// 2048-bit RSA.
Rsa2048,

/// 3072-bit RSA. Requires firmware 5.7 or newer
Rsa3072,

/// 4096-bit RSA. Requires firmware 5.7 or newer
Rsa4096,

/// ECDSA with the NIST P256 curve.
EccP256,

Expand All @@ -499,6 +505,8 @@ impl TryFrom<u8> for AlgorithmId {
match value {
0x06 => Ok(AlgorithmId::Rsa1024),
0x07 => Ok(AlgorithmId::Rsa2048),
0x05 => Ok(AlgorithmId::Rsa3072),
0x16 => Ok(AlgorithmId::Rsa4096),
0x11 => Ok(AlgorithmId::EccP256),
0x14 => Ok(AlgorithmId::EccP384),
_ => Err(Error::AlgorithmError),
Expand All @@ -511,6 +519,8 @@ impl From<AlgorithmId> for u8 {
match id {
AlgorithmId::Rsa1024 => 0x06,
AlgorithmId::Rsa2048 => 0x07,
AlgorithmId::Rsa3072 => 0x05,
AlgorithmId::Rsa4096 => 0x16,
AlgorithmId::EccP256 => 0x11,
AlgorithmId::EccP384 => 0x14,
}
Expand All @@ -528,6 +538,8 @@ impl AlgorithmId {
match self {
AlgorithmId::Rsa1024 => 64,
AlgorithmId::Rsa2048 => 128,
AlgorithmId::Rsa3072 => 192,
AlgorithmId::Rsa4096 => 256,
AlgorithmId::EccP256 => 32,
AlgorithmId::EccP384 => 48,
}
Expand All @@ -536,7 +548,10 @@ impl AlgorithmId {
#[cfg(feature = "untested")]
fn get_param_tag(self) -> u8 {
match self {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => 0x01,
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => 0x01,
AlgorithmId::EccP256 | AlgorithmId::EccP384 => 0x6,
}
}
Expand Down Expand Up @@ -609,7 +624,10 @@ pub fn generate(
let setting_roca: setting::Setting;

match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => {
if yubikey.version.major == 4
&& (yubikey.version.minor < 3
|| yubikey.version.minor == 3 && (yubikey.version.patch < 5))
Expand Down Expand Up @@ -813,7 +831,7 @@ impl RsaKeyData {

/// Imports a private RSA encryption or signing key into the YubiKey.
///
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048`.
/// Errors if `algorithm` isn't `AlgorithmId::Rsa1024` or `AlgorithmId::Rsa2048` or `AlgorithmId::Rsa3072` or `AlgorithmId::Rsa4096`.
#[cfg(feature = "untested")]
pub fn import_rsa_key(
yubikey: &mut YubiKey,
Expand All @@ -824,7 +842,10 @@ pub fn import_rsa_key(
pin_policy: PinPolicy,
) -> Result<()> {
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => (),
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => (),
_ => return Err(Error::AlgorithmError),
}

Expand Down Expand Up @@ -1101,7 +1122,10 @@ fn read_public_key(
//
// 0x7f 0x49 -> Application | Constructed | 0x49
match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
AlgorithmId::Rsa1024
| AlgorithmId::Rsa2048
| AlgorithmId::Rsa3072
| AlgorithmId::Rsa4096 => {
// It appears that the inner application-specific value returned by the
// YubiKey is constructed such that RSA pubkeys can be parsed in two ways:
//
Expand Down
25 changes: 17 additions & 8 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,23 @@ impl<'tx> Transaction<'tx> {
let templ = [0, Ins::Authenticate.code(), algorithm.into(), key.into()];

match algorithm {
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
let key_len = if let AlgorithmId::Rsa1024 = algorithm {
128
} else {
256
};

if in_len != key_len {
AlgorithmId::Rsa1024 => {
if in_len != 128 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa2048 => {
if in_len != 256 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa3072 => {
if in_len != 384 {
return Err(Error::SizeError);
}
}
AlgorithmId::Rsa4096 => {
if in_len != 512 {
return Err(Error::SizeError);
}
}
Expand Down
28 changes: 28 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,34 @@ fn generate_self_signed_rsa_cert() {
assert!(pubkey.verify_prehash(&hash, &sig).is_ok());
}

#[test]
#[ignore]
fn generate_rsa3072() {
let mut yubikey = YUBIKEY.lock().unwrap();
let version = yubikey.version();

assert!(yubikey.authenticate(MgmKey::default()).is_ok());

let slot = SlotId::Retired(RetiredSlotId::R1);

// Generate a new key in the selected slot.
let generated = piv::generate(
&mut yubikey,
slot,
AlgorithmId::Rsa3072,
PinPolicy::Default,
TouchPolicy::Default,
);

match generated {
Ok(key) => {
let pubkey = key.subject_public_key;
assert!(pubkey.bit_len() > 3072)
}
Err(e) => assert!((version.major, version.minor) < (5, 7) && e == Error::AlgorithmError),
}
}

#[test]
#[ignore]
fn generate_self_signed_ec_cert() {
Expand Down

0 comments on commit 0d8096f

Please sign in to comment.