From 983bc5c5b2121b5678df8a5163987f149cc9f67b Mon Sep 17 00:00:00 2001 From: Egor Duda Date: Thu, 12 May 2022 22:56:33 +0300 Subject: [PATCH] Add support for ed25519 keys (#335) --- Cargo.toml | 2 + deploy.py | 10 +- src/ctap/crypto_wrapper.rs | 235 ++++++++++++++++++++++++++++++++----- src/ctap/data_formats.rs | 88 ++++++++++++-- src/ctap/mod.rs | 35 +++++- third_party/libtock-rs | 2 +- 6 files changed, 325 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29527e7..8da47c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde_json = { version = "=1.0.69", default-features = false, features = ["alloc embedded-time = "0.12.1" arbitrary = { version = "0.4.7", features = ["derive"], optional = true } rand = { version = "0.8.4", optional = true } +ed25519-dalek = { version = "1", default-features = false, features = ["u32_backend", "rand"], optional = true } [features] debug_allocations = ["lang_items/debug_allocations"] @@ -36,6 +37,7 @@ with_ctap1 = ["crypto/with_ctap1"] with_nfc = ["libtock_drivers/with_nfc"] vendor_hid = ["libtock_drivers/vendor_hid"] fuzz = ["arbitrary", "std"] +with_ed25519 = ["ed25519-dalek"] [dev-dependencies] enum-iterator = "0.6.0" diff --git a/deploy.py b/deploy.py index 9b92fd4..24c1817 100755 --- a/deploy.py +++ b/deploy.py @@ -442,8 +442,6 @@ class OpenSKInstaller: f"link-arg=-T{props.app_ldscript}", "-C", "relocation-model=static", - "-D", - "warnings", f"--remap-path-prefix={os.getcwd()}=", "-C", "link-arg=-icf=all", @@ -1091,6 +1089,14 @@ if __name__ == "__main__": help=("When set, the output of elf2tab is appended to this file."), ) + main_parser.add_argument( + "--with_ed25519", + action="append_const", + const="with_ed25519", + dest="features", + help=("Enable Ed25519 support"), + ) + main_parser.set_defaults(features=["with_ctap1"]) # Start parsing to know if we're going to list things or not. diff --git a/src/ctap/crypto_wrapper.rs b/src/ctap/crypto_wrapper.rs index 3420081..68c7d01 100644 --- a/src/ctap/crypto_wrapper.rs +++ b/src/ctap/crypto_wrapper.rs @@ -14,7 +14,7 @@ use crate::ctap::data_formats::{ extract_array, extract_byte_string, CoseKey, PublicKeyCredentialSource, - PublicKeyCredentialType, SignatureAlgorithm, + PublicKeyCredentialType, SignatureAlgorithm, ES256_ALGORITHM, EDDSA_ALGORITHM, }; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::storage; @@ -30,6 +30,8 @@ use crypto::sha256::Sha256; use rng256::Rng256; use sk_cbor as cbor; use sk_cbor::{cbor_array, cbor_bytes, cbor_int}; +#[cfg(feature = "with_ed25519")] +use ed25519_dalek::Signer; // Legacy credential IDs consist of // - 16 bytes: initialization vector for AES-256, @@ -42,6 +44,16 @@ pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113; // See encrypt_key_handle v1 documentation. pub const MAX_CREDENTIAL_ID_SIZE: usize = 113; +const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01; +#[cfg(feature = "with_ed25519")] +const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02; +#[cfg(test)] +#[cfg(feature = "with_ed25519")] +const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x03; +#[cfg(test)] +#[cfg(not(feature = "with_ed25519"))] +const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x02; + /// Wraps the AES256-CBC encryption to match what we need in CTAP. pub fn aes256_cbc_encrypt( rng: &mut dyn Rng256, @@ -89,11 +101,37 @@ pub fn aes256_cbc_decrypt( } /// An asymmetric private key that can sign messages. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug)] pub enum PrivateKey { Ecdsa(ecdsa::SecKey), + #[cfg(feature = "with_ed25519")] + Ed25519(ed25519_dalek::Keypair), } +impl Clone for PrivateKey { + fn clone(&self) -> Self { + match self { + Self::Ecdsa(sk) => Self::Ecdsa (sk.clone ()), + #[cfg(feature = "with_ed25519")] + Self::Ed25519(keypair) => Self::Ed25519 (ed25519_dalek::Keypair::from_bytes (&keypair.to_bytes()).unwrap()), + } + } +} + +impl PartialEq for PrivateKey { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Ecdsa(ref a), &Self::Ecdsa(ref b)) => a == b, + #[cfg(feature = "with_ed25519")] + (&Self::Ed25519(ref a), &Self::Ed25519(ref b)) => a.to_bytes() == b.to_bytes(), + #[cfg(feature = "with_ed25519")] + _ => false, + } + } +} + +impl Eq for PrivateKey {} + impl PrivateKey { /// Creates a new private key for the given algorithm. /// @@ -103,6 +141,11 @@ impl PrivateKey { pub fn new(rng: &mut impl Rng256, alg: SignatureAlgorithm) -> Self { match alg { SignatureAlgorithm::ES256 => PrivateKey::Ecdsa(crypto::ecdsa::SecKey::gensk(rng)), + #[cfg(feature = "with_ed25519")] + SignatureAlgorithm::EDDSA => { + let bytes = rng.gen_uniform_u8x32(); + Self::new_ed25519_from_bytes(&bytes).unwrap() + }, SignatureAlgorithm::Unknown => unreachable!(), } } @@ -117,10 +160,25 @@ impl PrivateKey { ecdsa::SecKey::from_bytes(array_ref!(bytes, 0, 32)).map(PrivateKey::from) } + #[cfg(feature = "with_ed25519")] + pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option { + if bytes.len() != 32 { + return None; + } + if let Ok(secret) = ed25519_dalek::SecretKey::from_bytes(bytes) { + let public = ed25519_dalek::PublicKey::from (&secret); + Some(Self::Ed25519(ed25519_dalek::Keypair{secret, public})) + } else { + None + } + } + /// Returns the corresponding public key. pub fn get_pub_key(&self) -> CoseKey { match self { PrivateKey::Ecdsa(ecdsa_key) => CoseKey::from(ecdsa_key.genpk()), + #[cfg(feature = "with_ed25519")] + PrivateKey::Ed25519(ed25519_keypair) => CoseKey::from(ed25519_keypair.public), } } @@ -128,6 +186,8 @@ impl PrivateKey { pub fn sign_and_encode(&self, message: &[u8]) -> Vec { match self { PrivateKey::Ecdsa(ecdsa_key) => ecdsa_key.sign_rfc6979::(message).to_asn1_der(), + #[cfg(feature = "with_ed25519")] + PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.try_sign(message).unwrap().to_bytes().to_vec(), } } @@ -135,6 +195,8 @@ impl PrivateKey { pub fn signature_algorithm(&self) -> SignatureAlgorithm { match self { PrivateKey::Ecdsa(_) => SignatureAlgorithm::ES256, + #[cfg(feature = "with_ed25519")] + PrivateKey::Ed25519(_) => SignatureAlgorithm::EDDSA, } } @@ -146,6 +208,8 @@ impl PrivateKey { ecdsa_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); key_bytes } + #[cfg(feature = "with_ed25519")] + PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.secret.to_bytes().to_vec(), } } } @@ -171,6 +235,9 @@ impl TryFrom for PrivateKey { match SignatureAlgorithm::try_from(array.pop().unwrap())? { SignatureAlgorithm::ES256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes) .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), + #[cfg(feature = "with_ed25519")] + SignatureAlgorithm::EDDSA => PrivateKey::new_ed25519_from_bytes(&key_bytes) + .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), } } @@ -190,12 +257,19 @@ impl From for PrivateKey { /// Also, by limiting ourselves to private key and RP ID hash, we are compatible with U2F for /// ECDSA private keys. /// -/// This is v1, and we write the following data for ECDSA (algorithm -7): +/// For v1 we write the following data for ECDSA (algorithm -7): /// - 1 byte : version number /// - 16 bytes: initialization vector for AES-256, /// - 32 bytes: ECDSA private key for the credential, /// - 32 bytes: relying party ID hashed with SHA256, /// - 32 bytes: HMAC-SHA256 over everything else. +/// +/// For v2 we write the following data for EdDSA over curve Ed25519 (algorithm -8, curve 6): +/// - 1 byte : version number +/// - 16 bytes: initialization vector for AES-256, +/// - 32 bytes: Ed25519 private key for the credential, +/// - 32 bytes: relying party ID hashed with SHA256, +/// - 32 bytes: HMAC-SHA256 over everything else. pub fn encrypt_key_handle( env: &mut impl Env, private_key: &PrivateKey, @@ -204,17 +278,22 @@ pub fn encrypt_key_handle( let master_keys = storage::master_keys(env)?; let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); - let mut encrypted_id = match private_key { + let mut plaintext = [0; 64]; + let version; + match private_key { PrivateKey::Ecdsa(ecdsa_key) => { - let mut plaintext = [0; 64]; ecdsa_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); - plaintext[32..64].copy_from_slice(application); - let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; - // Version number - encrypted_id.insert(0, 0x01); - encrypted_id + version = ECDSA_CREDENTIAL_ID_VERSION; + } + #[cfg(feature = "with_ed25519")] + PrivateKey::Ed25519(keypair) => { + plaintext[0..32].copy_from_slice(&keypair.secret.to_bytes()); + version = ED25519_CREDENTIAL_ID_VERSION; } }; + plaintext[32..64].copy_from_slice(application); + let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; + encrypted_id.insert(0, version); let id_hmac = hmac_256::(&master_keys.hmac, &encrypted_id[..]); encrypted_id.extend(&id_hmac); @@ -231,6 +310,7 @@ pub fn encrypt_key_handle( /// This functions reads: /// - legacy credentials (no version number), /// - v1 (ECDSA) +/// - v2 (EdDSA over curve Ed25519) pub fn decrypt_credential_source( env: &mut impl Env, credential_id: Vec, @@ -249,12 +329,17 @@ pub fn decrypt_credential_source( return Ok(None); } + let algorithm; let payload = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE { + algorithm = ES256_ALGORITHM; &credential_id[..hmac_message_size] } else { // Version number check - if credential_id[0] != 1 { - return Ok(None); + match credential_id[0] { + ECDSA_CREDENTIAL_ID_VERSION => algorithm = ES256_ALGORITHM, + #[cfg(feature = "with_ed25519")] + ED25519_CREDENTIAL_ID_VERSION => algorithm = EDDSA_ALGORITHM, + _ => return Ok(None), } &credential_id[1..hmac_message_size] }; @@ -269,7 +354,15 @@ pub fn decrypt_credential_source( if rp_id_hash != &decrypted_id[32..] { return Ok(None); } - let sk_option = PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]); + let sk_option; + match algorithm { + ES256_ALGORITHM => sk_option = PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]), + #[cfg(feature = "with_ed25519")] + EDDSA_ALGORITHM => sk_option = PrivateKey::new_ed25519_from_bytes(&decrypted_id[..32]), + #[cfg(not(feature = "with_ed25519"))] + EDDSA_ALGORITHM => return Ok(None), + _ => return Ok(None), + } Ok(sk_option.map(|sk| PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, @@ -368,6 +461,18 @@ mod test { ); } + #[test] + #[cfg(feature = "with_ed25519")] + fn test_new_ed25519_from_bytes() { + let mut env = TestEnv::new(); + let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::EDDSA); + let key_bytes = private_key.to_bytes(); + assert_eq!( + PrivateKey::new_ed25519_from_bytes(&key_bytes), + Some(private_key) + ); + } + #[test] fn test_new_ecdsa_from_bytes_wrong_length() { assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None); @@ -376,6 +481,15 @@ mod test { assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None); } + #[test] + #[cfg(feature = "with_ed25519")] + fn test_new_ed25519_from_bytes_wrong_length() { + assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None); + assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None); + assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None); + assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None); + } + #[test] fn test_private_key_get_pub_key() { let mut env = TestEnv::new(); @@ -395,22 +509,41 @@ mod test { assert_eq!(private_key.sign_and_encode(&message), signature); } - #[test] - fn test_private_key_signature_algorithm() { + fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); - let algorithm = SignatureAlgorithm::ES256; - let private_key = PrivateKey::new(env.rng(), algorithm); - assert_eq!(private_key.signature_algorithm(), algorithm); + let private_key = PrivateKey::new(env.rng(), signature_algorithm); + assert_eq!(private_key.signature_algorithm(), signature_algorithm); } #[test] - fn test_private_key_from_to_cbor() { + fn test_ecdsa_private_key_signature_algorithm() { + test_private_key_signature_algorithm(SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_ed25519_private_key_signature_algorithm() { + test_private_key_signature_algorithm(SignatureAlgorithm::EDDSA); + } + + fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); - let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); + let private_key = PrivateKey::new(env.rng(), signature_algorithm); let cbor = cbor::Value::from(private_key.clone()); assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),); } + #[test] + fn test_ecdsa_private_key_from_to_cbor() { + test_private_key_from_to_cbor(SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_ed25519_private_key_from_to_cbor() { + test_private_key_from_to_cbor(SignatureAlgorithm::EDDSA); + } + #[test] fn test_private_key_from_bad_cbor() { let cbor = cbor_array![ @@ -424,6 +557,20 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), ); + #[cfg(feature = "with_ed25519")] + { + let cbor = cbor_array![ + cbor_int!(SignatureAlgorithm::EDDSA as i64), + cbor_bytes!(vec![0x88; 32]), + // The array is too long. + cbor_int!(0), + ]; + assert_eq!( + PrivateKey::try_from(cbor), + Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), + ); + } + let cbor = cbor_array![ // This algorithms doesn't exist. cbor_int!(-1), @@ -435,11 +582,10 @@ mod test { ); } - #[test] - fn test_encrypt_decrypt_credential() { + fn test_encrypt_decrypt_credential(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); - let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); + let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); @@ -450,6 +596,17 @@ mod test { assert_eq!(private_key, decrypted_source.private_key); } + #[test] + fn test_encrypt_decrypt_ecdsa_credential() { + test_encrypt_decrypt_credential(SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_encrypt_decrypt_ed25519_credential() { + test_encrypt_decrypt_credential(SignatureAlgorithm::EDDSA); + } + #[test] fn test_encrypt_decrypt_bad_version() { let mut env = TestEnv::new(); @@ -459,7 +616,7 @@ mod test { let rp_id_hash = [0x55; 32]; let mut encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); // Version 2 does not exist yet. - encrypted_id[0] = 0x02; + encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION; // Override the HMAC to pass the check. encrypted_id.truncate(&encrypted_id.len() - 32); let master_keys = storage::master_keys(&mut env).unwrap(); @@ -472,11 +629,10 @@ mod test { ); } - #[test] - fn test_encrypt_decrypt_bad_hmac() { + fn test_encrypt_decrypt_bad_hmac(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); - let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); + let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); @@ -491,10 +647,20 @@ mod test { } #[test] - fn test_decrypt_credential_missing_blocks() { + fn test_ecdsa_encrypt_decrypt_bad_hmac() { + test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_ed25519_encrypt_decrypt_bad_hmac() { + test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::EDDSA); + } + + fn test_decrypt_credential_missing_blocks(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); - let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); + let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); @@ -507,6 +673,17 @@ mod test { } } + #[test] + fn test_ecdsa_decrypt_credential_missing_blocks() { + test_decrypt_credential_missing_blocks(SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_ed25519_decrypt_credential_missing_blocks() { + test_decrypt_credential_missing_blocks(SignatureAlgorithm::EDDSA); + } + /// This is a copy of the function that genereated deprecated key handles. fn legacy_encrypt_key_handle( env: &mut impl Env, diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index b5866de..e7c9cc4 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -27,7 +27,8 @@ use sk_cbor as cbor; use sk_cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map}; // Used as the identifier for ECDSA in assertion signatures and COSE. -const ES256_ALGORITHM: i64 = -7; +pub const ES256_ALGORITHM: i64 = -7; +pub const EDDSA_ALGORITHM: i64 = -8; // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity #[derive(Clone, Debug, PartialEq, Eq)] @@ -503,6 +504,8 @@ impl From for cbor::Value { #[cfg_attr(feature = "fuzz", derive(Arbitrary))] pub enum SignatureAlgorithm { ES256 = ES256_ALGORITHM as isize, + #[cfg(feature = "with_ed25519")] + EDDSA = EDDSA_ALGORITHM as isize, // This is the default for all numbers not covered above. // Unknown types should be ignored, instead of returning errors. Unknown = 0, @@ -518,6 +521,8 @@ impl From for SignatureAlgorithm { fn from(int: i64) -> Self { match int { ES256_ALGORITHM => SignatureAlgorithm::ES256, + #[cfg(feature = "with_ed25519")] + EDDSA_ALGORITHM => SignatureAlgorithm::EDDSA, _ => SignatureAlgorithm::Unknown, } } @@ -720,6 +725,8 @@ pub struct CoseKey { x_bytes: [u8; ecdh::NBYTES], y_bytes: [u8; ecdh::NBYTES], algorithm: i64, + key_type: i64, + curve: i64, } impl CoseKey { @@ -729,8 +736,12 @@ impl CoseKey { const ECDH_ALGORITHM: i64 = -25; // The parameter behind map key 1. const EC2_KEY_TYPE: i64 = 2; + #[cfg(feature = "with_ed25519")] + const OKP_KEY_TYPE: i64 = 1; // The parameter behind map key -1. const P_256_CURVE: i64 = 1; + #[cfg(feature = "with_ed25519")] + const ED25519_CURVE: i64 = 6; } // This conversion accepts both ECDH and ECDSA. @@ -776,6 +787,8 @@ impl TryFrom for CoseKey { x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES], y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES], algorithm, + key_type, + curve, }) } } @@ -786,12 +799,14 @@ impl From for cbor::Value { x_bytes, y_bytes, algorithm, + key_type, + curve, } = cose_key; cbor_map! { - 1 => CoseKey::EC2_KEY_TYPE, + 1 => key_type, 3 => algorithm, - -1 => CoseKey::P_256_CURVE, + -1 => curve, -2 => x_bytes, -3 => y_bytes, } @@ -807,6 +822,8 @@ impl From for CoseKey { x_bytes, y_bytes, algorithm: CoseKey::ECDH_ALGORITHM, + key_type: CoseKey::EC2_KEY_TYPE, + curve: CoseKey::P_256_CURVE, } } } @@ -820,6 +837,21 @@ impl From for CoseKey { x_bytes, y_bytes, algorithm: ES256_ALGORITHM, + key_type: CoseKey::EC2_KEY_TYPE, + curve: CoseKey::P_256_CURVE, + } + } +} + +#[cfg(feature = "with_ed25519")] +impl From for CoseKey { + fn from(pk: ed25519_dalek::PublicKey) -> Self { + CoseKey { + x_bytes: pk.to_bytes(), + y_bytes: [0u8; 32], + key_type: CoseKey::OKP_KEY_TYPE, + curve: CoseKey::ED25519_CURVE, + algorithm: EDDSA_ALGORITHM, } } } @@ -832,6 +864,8 @@ impl TryFrom for ecdh::PubKey { x_bytes, y_bytes, algorithm, + key_type, + curve, } = cose_key; // Since algorithm can be used for different COSE key types, we check @@ -841,6 +875,9 @@ impl TryFrom for ecdh::PubKey { if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM { return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); } + if key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes) .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) } @@ -854,9 +891,11 @@ impl TryFrom for ecdsa::PubKey { x_bytes, y_bytes, algorithm, + key_type, + curve, } = cose_key; - if algorithm != ES256_ALGORITHM { + if algorithm != ES256_ALGORITHM || key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE { return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); } ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes) @@ -904,7 +943,11 @@ impl TryFrom for ecdsa::Signature { match cose_signature.algorithm { SignatureAlgorithm::ES256 => ecdsa::Signature::from_bytes(&cose_signature.bytes) .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER), - SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), + #[cfg(feature = "with_ed25519")] + SignatureAlgorithm::EDDSA => + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), + SignatureAlgorithm::Unknown => + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), } } } @@ -1564,6 +1607,13 @@ mod test { let signature_algorithm = SignatureAlgorithm::from(alg_int); assert_eq!(signature_algorithm, SignatureAlgorithm::ES256); + #[cfg(feature = "with_ed25519")] + { + let alg_int = SignatureAlgorithm::EDDSA as i64; + let signature_algorithm = SignatureAlgorithm::from(alg_int); + assert_eq!(signature_algorithm, SignatureAlgorithm::EDDSA); + } + let unknown_alg_int = -1; let unknown_algorithm = SignatureAlgorithm::from(unknown_alg_int); assert_eq!(unknown_algorithm, SignatureAlgorithm::Unknown); @@ -1578,6 +1628,16 @@ mod test { let created_cbor: cbor::Value = signature_algorithm.unwrap().into(); assert_eq!(created_cbor, cbor_signature_algorithm); + #[cfg(feature = "with_ed25519")] + { + let cbor_signature_algorithm: cbor::Value = cbor_int!(EDDSA_ALGORITHM); + let signature_algorithm = SignatureAlgorithm::try_from(cbor_signature_algorithm.clone()); + let expected_signature_algorithm = SignatureAlgorithm::EDDSA; + assert_eq!(signature_algorithm, Ok(expected_signature_algorithm)); + let created_cbor: cbor::Value = signature_algorithm.unwrap().into(); + assert_eq!(created_cbor, cbor_signature_algorithm); + } + let cbor_unknown_algorithm: cbor::Value = cbor_int!(-1); let unknown_algorithm = SignatureAlgorithm::try_from(cbor_unknown_algorithm); let expected_unknown_algorithm = SignatureAlgorithm::Unknown; @@ -1641,23 +1701,33 @@ mod test { } } - #[test] - fn test_from_into_public_key_credential_parameter() { + fn test_from_into_public_key_credential_parameter(alg_int: i64, signature_algorithm: SignatureAlgorithm) { let cbor_credential_parameter = cbor_map! { - "alg" => ES256_ALGORITHM, + "alg" => alg_int, "type" => "public-key", }; let credential_parameter = PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone()); let expected_credential_parameter = PublicKeyCredentialParameter { cred_type: PublicKeyCredentialType::PublicKey, - alg: SignatureAlgorithm::ES256, + alg: signature_algorithm, }; assert_eq!(credential_parameter, Ok(expected_credential_parameter)); let created_cbor: cbor::Value = credential_parameter.unwrap().into(); assert_eq!(created_cbor, cbor_credential_parameter); } + #[test] + fn test_from_into_ecdsa_public_key_credential_parameter() { + test_from_into_public_key_credential_parameter(ES256_ALGORITHM, SignatureAlgorithm::ES256); + } + + #[test] + #[cfg(feature = "with_ed25519")] + fn test_from_into_ed25519_public_key_credential_parameter() { + test_from_into_public_key_credential_parameter(EDDSA_ALGORITHM, SignatureAlgorithm::EDDSA); + } + #[test] fn test_from_into_public_key_credential_descriptor() { let cbor_credential_descriptor = cbor_map! { diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 4cc392d..547629d 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -117,6 +117,30 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa alg: SignatureAlgorithm::ES256, }; +#[cfg(feature = "with_ed25519")] +pub const EDDSA_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter { + cred_type: PublicKeyCredentialType::PublicKey, + alg: SignatureAlgorithm::EDDSA, +}; + +fn get_supported_cred_params() -> Vec { + let mut ret_val = vec!(); + ret_val.push(ES256_CRED_PARAM); + #[cfg(feature = "with_ed25519")] + ret_val.push(EDDSA_CRED_PARAM); + ret_val +} + +fn get_preferred_cred_param (params: &Vec) -> Option<&PublicKeyCredentialParameter> { + let supported_cred_params = get_supported_cred_params(); + for param in params { + if supported_cred_params.contains(param) { + return Some(param); + } + } + return None; +} + /// Transports supported by OpenSK. /// /// An OpenSK library user annotates incoming data with this data type. @@ -604,10 +628,9 @@ impl CtapState { self.pin_uv_auth_precheck(env, &pin_uv_auth_param, pin_uv_auth_protocol, channel)?; // When more algorithms are supported, iterate and pick the first match. - if !pub_key_cred_params.contains(&ES256_CRED_PARAM) { - return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); - } - let algorithm = SignatureAlgorithm::ES256; + let cred_param = get_preferred_cred_param(&pub_key_cred_params) + .ok_or(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)?; + let algorithm = cred_param.alg; let rp_id = rp.rp_id; let ep_att = if let Some(enterprise_attestation) = enterprise_attestation { @@ -1146,7 +1169,7 @@ impl CtapState { .map(|c| c as u64), max_credential_id_length: Some(MAX_CREDENTIAL_ID_SIZE as u64), transports: Some(vec![AuthenticatorTransport::Usb]), - algorithms: Some(vec![ES256_CRED_PARAM]), + algorithms: Some(get_supported_cred_params()), max_serialized_large_blob_array: Some( env.customization().max_large_blob_array_size() as u64, ), @@ -1461,7 +1484,7 @@ mod test { 0x07 => env.customization().max_credential_count_in_list().map(|c| c as u64), 0x08 => MAX_CREDENTIAL_ID_SIZE as u64, 0x09 => cbor_array!["usb"], - 0x0A => cbor_array![ES256_CRED_PARAM], + 0x0A => cbor_array_vec!(get_supported_cred_params()), 0x0B => env.customization().max_large_blob_array_size() as u64, 0x0C => false, 0x0D => storage::min_pin_length(&mut env).unwrap() as u64, diff --git a/third_party/libtock-rs b/third_party/libtock-rs index 15e837e..1ece29b 160000 --- a/third_party/libtock-rs +++ b/third_party/libtock-rs @@ -1 +1 @@ -Subproject commit 15e837e4495df2d7a39ae6d6c057fdec515cc430 +Subproject commit 1ece29ba2b061e6d06c3344ba4c6cd6005c17eb3