diff --git a/Cargo.toml b/Cargo.toml index c2d1839d..b85a9c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "authenticator" -version = "0.5.0" +version = "0.6.0" authors = [ "Dana Keeler ", "J.C. Jones ", "John Schanck ", "Kyle Machulis ", "Martin Sirringhaus " ] keywords = ["ctap2", "u2f", "fido", "webauthn"] categories = ["cryptography", "hardware-support", "os"] @@ -68,7 +68,7 @@ cfg-if = "1.0" # Crypto backends openssl-sys = { version = "0.9", optional = true} openssl = { version = "0.10", optional = true} -nss-rs = { git = "https://github.com/mozilla/nss-rs", version = "^0.12", optional = true } +nss-rs = { git = "https://github.com/mozilla/nss-rs", version = ">=0.12", optional = true } pkcs11-bindings = { version = "0.1.4", optional = true } aes = { version = "0.8", optional = true } cbc = { version = "0.1", default-features = false, features = ["std"], optional = true } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 735705c3..20284eae 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -514,54 +514,59 @@ pub enum COSEAlgorithm { // /// used by validators, but can exist in some windows hello tpm's // INSECURE_RS1 = -65535, INSECURE_RS1 = -65535, // RSASSA-PKCS1-v1_5 using SHA-1 - RS512 = -259, // RSASSA-PKCS1-v1_5 using SHA-512 - RS384 = -258, // RSASSA-PKCS1-v1_5 using SHA-384 - RS256 = -257, // RSASSA-PKCS1-v1_5 using SHA-256 - ES256K = -47, // ECDSA using secp256k1 curve and SHA-256 - HSS_LMS = -46, // HSS/LMS hash-based digital signature - SHAKE256 = -45, // SHAKE-256 512-bit Hash Value - SHA512 = -44, // SHA-2 512-bit Hash - SHA384 = -43, // SHA-2 384-bit Hash - RSAES_OAEP_SHA_512 = -42, // RSAES-OAEP w/ SHA-512 - RSAES_OAEP_SHA_256 = -41, // RSAES-OAEP w/ SHA-256 - RSAES_OAEP_RFC_8017_default = -40, // RSAES-OAEP w/ SHA-1 - PS512 = -39, // RSASSA-PSS w/ SHA-512 - PS384 = -38, // RSASSA-PSS w/ SHA-384 - PS256 = -37, // RSASSA-PSS w/ SHA-256 - ES512 = -36, // ECDSA w/ SHA-512 - ES384 = -35, // ECDSA w/ SHA-384 - ECDH_SS_A256KW = -34, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key - ECDH_SS_A192KW = -33, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key - ECDH_SS_A128KW = -32, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key - ECDH_ES_A256KW = -31, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key - ECDH_ES_A192KW = -30, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key - ECDH_ES_A128KW = -29, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key - ECDH_SS_HKDF512 = -28, // ECDH SS w/ HKDF - generate key directly - ECDH_SS_HKDF256 = -27, // ECDH SS w/ HKDF - generate key directly - ECDH_ES_HKDF512 = -26, // ECDH ES w/ HKDF - generate key directly - ECDH_ES_HKDF256 = -25, // ECDH ES w/ HKDF - generate key directly - SHAKE128 = -18, // SHAKE-128 256-bit Hash Value - SHA512_256 = -17, // SHA-2 512-bit Hash truncated to 256-bits - SHA256 = -16, // SHA-2 256-bit Hash - SHA256_64 = -15, // SHA-2 256-bit Hash truncated to 64-bits - SHA1 = -14, // SHA-1 Hash - Direct_HKDF_AES256 = -13, // Shared secret w/ AES-MAC 256-bit key - Direct_HKDF_AES128 = -12, // Shared secret w/ AES-MAC 128-bit key - Direct_HKDF_SHA512 = -11, // Shared secret w/ HKDF and SHA-512 - Direct_HKDF_SHA256 = -10, // Shared secret w/ HKDF and SHA-256 + RS512 = -259, // RSASSA-PKCS1-v1_5 using SHA-512 + RS384 = -258, // RSASSA-PKCS1-v1_5 using SHA-384 + RS256 = -257, // RSASSA-PKCS1-v1_5 using SHA-256 + Ed448 = -53, // EdDSA using the Ed448 parameter set + ESP512 = -52, // ECDSA using P-521 curve and SHA-512 + ESP384 = -51, // ECDSA using P-384 curve and SHA-384 + ES256K = -47, // ECDSA using secp256k1 curve and SHA-256 + HSS_LMS = -46, // HSS/LMS hash-based digital signature + SHAKE256 = -45, // SHAKE-256 512-bit Hash Value + SHA512 = -44, // SHA-2 512-bit Hash + SHA384 = -43, // SHA-2 384-bit Hash + RSAES_OAEP_SHA_512 = -42, // RSAES-OAEP w/ SHA-512 + RSAES_OAEP_SHA_256 = -41, // RSAES-OAEP w/ SHA-256 + RSAES_OAEP_RFC_8017_default = -40, // RSAES-OAEP w/ SHA-1 + PS512 = -39, // RSASSA-PSS w/ SHA-512 + PS384 = -38, // RSASSA-PSS w/ SHA-384 + PS256 = -37, // RSASSA-PSS w/ SHA-256 + ES512 = -36, // ECDSA w/ SHA-512 + ES384 = -35, // ECDSA w/ SHA-384 + ECDH_SS_A256KW = -34, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key + ECDH_SS_A192KW = -33, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key + ECDH_SS_A128KW = -32, // ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key + ECDH_ES_A256KW = -31, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key + ECDH_ES_A192KW = -30, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key + ECDH_ES_A128KW = -29, // ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key + ECDH_SS_HKDF512 = -28, // ECDH SS w/ HKDF - generate key directly + ECDH_SS_HKDF256 = -27, // ECDH SS w/ HKDF - generate key directly + ECDH_ES_HKDF512 = -26, // ECDH ES w/ HKDF - generate key directly + ECDH_ES_HKDF256 = -25, // ECDH ES w/ HKDF - generate key directly + Ed25519 = -19, // EdDSA using the Ed25519 parameter set + SHAKE128 = -18, // SHAKE-128 256-bit Hash Value + SHA512_256 = -17, // SHA-2 512-bit Hash truncated to 256-bits + SHA256 = -16, // SHA-2 256-bit Hash + SHA256_64 = -15, // SHA-2 256-bit Hash truncated to 64-bits + SHA1 = -14, // SHA-1 Hash + Direct_HKDF_AES256 = -13, // Shared secret w/ AES-MAC 256-bit key + Direct_HKDF_AES128 = -12, // Shared secret w/ AES-MAC 128-bit key + Direct_HKDF_SHA512 = -11, // Shared secret w/ HKDF and SHA-512 + Direct_HKDF_SHA256 = -10, // Shared secret w/ HKDF and SHA-256 + ESP256 = -9, // ECDSA using P-256 curve and SHA-256 EDDSA = -8, // EdDSA ES256 = -7, // ECDSA w/ SHA-256 Direct = -6, // Direct use of CEK A256KW = -5, // AES Key Wrap w/ 256-bit key A192KW = -4, // AES Key Wrap w/ 192-bit key A128KW = -3, // AES Key Wrap w/ 128-bit key - A128GCM = 1, // AES-GCM mode w/ 128-bit key, 128-bit tag - A192GCM = 2, // AES-GCM mode w/ 192-bit key, 128-bit tag - A256GCM = 3, // AES-GCM mode w/ 256-bit key, 128-bit tag - HMAC256_64 = 4, // HMAC w/ SHA-256 truncated to 64 bits - HMAC256_256 = 5, // HMAC w/ SHA-256 - HMAC384_384 = 6, // HMAC w/ SHA-384 - HMAC512_512 = 7, // HMAC w/ SHA-512 + A128GCM = 1, // AES-GCM mode w/ 128-bit key, 128-bit tag + A192GCM = 2, // AES-GCM mode w/ 192-bit key, 128-bit tag + A256GCM = 3, // AES-GCM mode w/ 256-bit key, 128-bit tag + HMAC256_64 = 4, // HMAC w/ SHA-256 truncated to 64 bits + HMAC256_256 = 5, // HMAC w/ SHA-256 + HMAC384_384 = 6, // HMAC w/ SHA-384 + HMAC512_512 = 7, // HMAC w/ SHA-512 AES_CCM_16_64_128 = 10, // AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce AES_CCM_16_64_256 = 11, // AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce AES_CCM_64_64_128 = 12, // AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce @@ -622,6 +627,9 @@ impl TryFrom for COSEAlgorithm { i if i == COSEAlgorithm::RS512 as i64 => Ok(COSEAlgorithm::RS512), i if i == COSEAlgorithm::RS384 as i64 => Ok(COSEAlgorithm::RS384), i if i == COSEAlgorithm::RS256 as i64 => Ok(COSEAlgorithm::RS256), + i if i == COSEAlgorithm::Ed448 as i64 => Ok(COSEAlgorithm::Ed448), + i if i == COSEAlgorithm::ESP512 as i64 => Ok(COSEAlgorithm::ESP512), + i if i == COSEAlgorithm::ESP384 as i64 => Ok(COSEAlgorithm::ESP384), i if i == COSEAlgorithm::ES256K as i64 => Ok(COSEAlgorithm::ES256K), i if i == COSEAlgorithm::HSS_LMS as i64 => Ok(COSEAlgorithm::HSS_LMS), i if i == COSEAlgorithm::SHAKE256 as i64 => Ok(COSEAlgorithm::SHAKE256), @@ -651,6 +659,7 @@ impl TryFrom for COSEAlgorithm { i if i == COSEAlgorithm::ECDH_SS_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_SS_HKDF256), i if i == COSEAlgorithm::ECDH_ES_HKDF512 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF512), i if i == COSEAlgorithm::ECDH_ES_HKDF256 as i64 => Ok(COSEAlgorithm::ECDH_ES_HKDF256), + i if i == COSEAlgorithm::Ed25519 as i64 => Ok(COSEAlgorithm::Ed25519), i if i == COSEAlgorithm::SHAKE128 as i64 => Ok(COSEAlgorithm::SHAKE128), i if i == COSEAlgorithm::SHA512_256 as i64 => Ok(COSEAlgorithm::SHA512_256), i if i == COSEAlgorithm::SHA256 as i64 => Ok(COSEAlgorithm::SHA256), @@ -668,6 +677,7 @@ impl TryFrom for COSEAlgorithm { i if i == COSEAlgorithm::Direct_HKDF_SHA256 as i64 => { Ok(COSEAlgorithm::Direct_HKDF_SHA256) } + i if i == COSEAlgorithm::ESP256 as i64 => Ok(COSEAlgorithm::ESP256), i if i == COSEAlgorithm::EDDSA as i64 => Ok(COSEAlgorithm::EDDSA), i if i == COSEAlgorithm::ES256 as i64 => Ok(COSEAlgorithm::ES256), i if i == COSEAlgorithm::Direct as i64 => Ok(COSEAlgorithm::Direct), diff --git a/src/ctap2/commands/get_info.rs b/src/ctap2/commands/get_info.rs index 562b7753..a0eef804 100644 --- a/src/ctap2/commands/get_info.rs +++ b/src/ctap2/commands/get_info.rs @@ -463,7 +463,34 @@ impl<'de> Deserialize<'de> for AuthenticatorInfo { parse_next_optional_value!(transports, map); } 0x0a => { - parse_next_optional_value!(algorithms, map); + if algorithms.is_some() { + return Err(serde::de::Error::duplicate_field("algorithms")); + } + // Parse the advertised algorithm list leniently. An + // authenticator may advertise COSE algorithms that we don't + // recognize (e.g. Ed25519 variants). Per CTAP2 we must ignore + // unknown algorithm identifiers rather than rejecting the entire + // GetInfo response. + let raw: Vec = map.next_value()?; + let parsed = raw + .into_iter() + .filter_map(|v| { + match serde_cbor::value::from_value::< + PublicKeyCredentialParameters, + >(v) + { + Ok(p) => Some(p), + Err(e) => { + warn!( + "GetInfo: ignoring unsupported algorithm: {:?}", + e + ); + None + } + } + }) + .collect(); + algorithms = Some(parsed); } 0x0b => { parse_next_optional_value!(max_ser_large_blob_array, map); @@ -1132,6 +1159,58 @@ pub mod tests { ); } + #[test] + fn parse_authenticator_info_unknown_algorithm() { + // An authenticator may advertise COSE algorithms that we don't + // recognize (here -20, which is unassigned). Per CTAP2 such unknown + // algorithm identifiers must be ignored rather than causing us to + // reject the entire GetInfo response. Known algorithms in the same + // list (including the Ed25519 variants -8/-19) must still be parsed. + use std::collections::BTreeMap; + let alg_entry = |alg: i128| { + let mut m = BTreeMap::new(); + m.insert(Value::Text("alg".to_string()), Value::Integer(alg)); + m.insert( + Value::Text("type".to_string()), + Value::Text("public-key".to_string()), + ); + Value::Map(m) + }; + let mut info = BTreeMap::new(); + info.insert( + Value::Integer(0x01), + Value::Array(vec![Value::Text("FIDO_2_0".to_string())]), + ); + info.insert(Value::Integer(0x03), Value::Bytes(AAGUID_RAW.to_vec())); + info.insert( + Value::Integer(0x0a), + // -7 (ES256), -8 (EdDSA) and -19 (Ed25519) are known; -20 is not. + Value::Array(vec![ + alg_entry(-7), + alg_entry(-8), + alg_entry(-19), + alg_entry(-20), + ]), + ); + let payload = serde_cbor::to_vec(&Value::Map(info)).unwrap(); + + let parsed = from_slice::(&payload).unwrap(); + assert_eq!( + parsed.algorithms, + Some(vec![ + PublicKeyCredentialParameters { + alg: COSEAlgorithm::ES256 + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::EDDSA + }, + PublicKeyCredentialParameters { + alg: COSEAlgorithm::Ed25519 + }, + ]), + ); + } + #[test] fn parse_authenticator_info_unknown_versions() { assert_eq!(