diff --git a/Cargo.toml b/Cargo.toml index 9f878e7..179cc33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 ", "Yubico AB"] diff --git a/README.md b/README.md index aba7e82..143c705 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/src/certificate.rs b/src/certificate.rs index 4da60b0..31885d0 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -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 { _len: PhantomData, diff --git a/src/piv.rs b/src/piv.rs index e59e40c..99a83aa 100644 --- a/src/piv.rs +++ b/src/piv.rs @@ -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: @@ -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, @@ -499,6 +505,8 @@ impl TryFrom 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), @@ -511,6 +519,8 @@ impl From for u8 { match id { AlgorithmId::Rsa1024 => 0x06, AlgorithmId::Rsa2048 => 0x07, + AlgorithmId::Rsa3072 => 0x05, + AlgorithmId::Rsa4096 => 0x16, AlgorithmId::EccP256 => 0x11, AlgorithmId::EccP384 => 0x14, } @@ -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, } @@ -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, } } @@ -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)) @@ -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, @@ -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), } @@ -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: // diff --git a/src/transaction.rs b/src/transaction.rs index d29af6d..ec89994 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -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); } } diff --git a/tests/integration.rs b/tests/integration.rs index 615426c..4194964 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -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() {