Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "authenticator"
version = "0.5.0"
version = "0.6.0"
authors = [ "Dana Keeler <dkeeler@mozilla.com>", "J.C. Jones <jc@mozilla.com>", "John Schanck <jschanck@mozilla.com>", "Kyle Machulis <kyle@nonpolynomial.com>", "Martin Sirringhaus <martin.sirringhaus@suse.com", "Tim Taubert <ttaubert@mozilla.com>" ]
keywords = ["ctap2", "u2f", "fido", "webauthn"]
categories = ["cryptography", "hardware-support", "os"]
Expand Down Expand Up @@ -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 }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This disables all semver safety guards for future nss-rs versions, effectively saying that authenticator-rs is compatible with all future nss-rs versions, no matter what breaking changes nss-rs introduces.

Given that we (Mozilla) control both authenticator-rs and nss-rs, that might be fine, though it doesn't prevent human error on our end.

I would recommend:

nss-rs = { git = "https://github.com/mozilla/nss-rs", version = ">=0.12, <= 0.13", 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 }
Expand Down
94 changes: 52 additions & 42 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
jschanck marked this conversation as resolved.
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
Expand Down Expand Up @@ -622,6 +627,9 @@ impl TryFrom<i64> 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),
Expand Down Expand Up @@ -651,6 +659,7 @@ impl TryFrom<i64> 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),
Expand All @@ -668,6 +677,7 @@ impl TryFrom<i64> 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),
Expand Down
81 changes: 80 additions & 1 deletion src/ctap2/commands/get_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value> = 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);
Expand Down Expand Up @@ -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::<AuthenticatorInfo>(&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!(
Expand Down
Loading