adds a feature flag for CTAP2.1, addresses comments

This commit is contained in:
Fabian Kaczmarczyck
2020-04-27 20:00:39 +02:00
parent 8f20a75b17
commit d9c4c729e8
8 changed files with 274 additions and 60 deletions

View File

@@ -25,6 +25,7 @@ std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
ram_storage = [] ram_storage = []
verbose = ["debug_ctap"] verbose = ["debug_ctap"]
with_ctap1 = ["crypto/with_ctap1"] with_ctap1 = ["crypto/with_ctap1"]
with_ctap2_1 = []
[dev-dependencies] [dev-dependencies]
elf2tab = "0.4.0" elf2tab = "0.4.0"

View File

@@ -28,9 +28,11 @@ few limitations:
Although we tested and implemented our firmware based on the published Although we tested and implemented our firmware based on the published
[CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html), [CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html),
our implementation was not reviewed nor officially tested and doesn't claim to our implementation was not reviewed nor officially tested and doesn't claim to
be FIDO Certified. With the upcoming next version of the be FIDO Certified.
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html), We started adding features of the upcoming next version of the
we started adding features, so master is currently between version 2.0 and 2.1. [CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
The development is currently between 2.0 and 2.1, with updates hidden behind a feature flag.
Please add the flag `shell --ctap2-1` to the deploy command to include them.
### Cryptography ### Cryptography

View File

@@ -741,6 +741,14 @@ if __name__ == "__main__":
help=("Compiles the OpenSK application without backward compatible " help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."), "support for U2F/CTAP1 protocol."),
) )
main_parser.add_argument(
"--ctap2-1",
action=RemoveConstAction,
const="with_ctap2_1",
dest="features",
help=("Compiles the OpenSK application with backward compatible "
"support for CTAP2.1 protocol."),
)
main_parser.add_argument( main_parser.add_argument(
"--regen-keys", "--regen-keys",
action="store_true", action="store_true",

View File

@@ -30,6 +30,7 @@ cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
echo "Checking that CTAP2 builds properly..." echo "Checking that CTAP2 builds properly..."
cargo check --release --target=thumbv7em-none-eabi cargo check --release --target=thumbv7em-none-eabi
cargo check --release --target=thumbv7em-none-eabi --features with_ctap1 cargo check --release --target=thumbv7em-none-eabi --features with_ctap1
cargo check --release --target=thumbv7em-none-eabi --features with_ctap2_1
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
cargo check --release --target=thumbv7em-none-eabi --features panic_console cargo check --release --target=thumbv7em-none-eabi --features panic_console
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
@@ -86,4 +87,10 @@ then
echo "Running unit tests on the desktop (debug mode + CTAP1)..." echo "Running unit tests on the desktop (debug mode + CTAP1)..."
cargo test --features std,with_ctap1 cargo test --features std,with_ctap1
echo "Running unit tests on the desktop (release mode + CTAP2.1)..."
cargo test --release --features std,with_ctap2_1
echo "Running unit tests on the desktop (debug mode + CTAP2.1)..."
cargo test --features std,with_ctap2_1
fi fi

View File

@@ -23,9 +23,10 @@ use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::convert::TryFrom; use core::convert::TryFrom;
// Depending on your memory, you can use Some(n) to limit request sizes. // Depending on your memory, you can use Some(n) to limit request sizes in
// MakeCredential and GetAssertion. This affects allowList and excludeList.
// You might also want to set the max credential size in process_get_info then. // You might also want to set the max credential size in process_get_info then.
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<u64> = None; pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
// CTAP specification (version 20190130) section 6.1 // CTAP specification (version 20190130) section 6.1
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
@@ -136,25 +137,19 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
)?)?; )?)?;
let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?; let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?;
let mut pub_key_cred_params = vec![]; let pub_key_cred_params = cred_param_vec
for cred_param_map_value in cred_param_vec { .iter()
if let Ok(cred_param) = PublicKeyCredentialParameter::try_from(cred_param_map_value) { .map(PublicKeyCredentialParameter::try_from)
pub_key_cred_params.push(cred_param); .collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
}
}
let exclude_list = match param_map.get(&cbor_unsigned!(5)) { let exclude_list = match param_map.get(&cbor_unsigned!(5)) {
Some(entry) => { Some(entry) => {
let exclude_list_vec = read_array(entry)?; let exclude_list_vec = read_array(entry)?;
let mut exclude_list = vec![]; let exclude_list = exclude_list_vec
for exclude_list_value in exclude_list_vec { .iter()
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST { .take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len()))
if exclude_list.len() as u64 >= count { .map(PublicKeyCredentialDescriptor::try_from)
break; .collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
}
}
exclude_list.push(PublicKeyCredentialDescriptor::try_from(exclude_list_value)?);
}
Some(exclude_list) Some(exclude_list)
} }
None => None, None => None,
@@ -222,15 +217,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
let allow_list = match param_map.get(&cbor_unsigned!(3)) { let allow_list = match param_map.get(&cbor_unsigned!(3)) {
Some(entry) => { Some(entry) => {
let allow_list_vec = read_array(entry)?; let allow_list_vec = read_array(entry)?;
let mut allow_list = vec![]; let allow_list = allow_list_vec
for allow_list_value in allow_list_vec { .iter()
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST { .take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len()))
if allow_list.len() as u64 >= count { .map(PublicKeyCredentialDescriptor::try_from)
break; .collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
}
}
allow_list.push(PublicKeyCredentialDescriptor::try_from(allow_list_value)?);
}
Some(allow_list) Some(allow_list)
} }
None => None, None => None,
@@ -330,7 +321,7 @@ mod test {
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType, AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
PublicKeyCredentialUserEntity, PublicKeyCredentialUserEntity,
}; };
use super::super::CREDENTIAL_PARAMETER; use super::super::ES256_CRED_PARAM;
use super::*; use super::*;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
@@ -349,7 +340,7 @@ mod test {
"displayName" => "bar", "displayName" => "bar",
"icon" => "example.com/foo/icon.png", "icon" => "example.com/foo/icon.png",
}, },
4 => cbor_array![CREDENTIAL_PARAMETER], 4 => cbor_array![ES256_CRED_PARAM],
5 => cbor_array![], 5 => cbor_array![],
8 => vec![0x12, 0x34], 8 => vec![0x12, 0x34],
9 => 1, 9 => 1,
@@ -380,7 +371,7 @@ mod test {
client_data_hash, client_data_hash,
rp, rp,
user, user,
pub_key_cred_params: vec![CREDENTIAL_PARAMETER], pub_key_cred_params: vec![ES256_CRED_PARAM],
exclude_list: Some(vec![]), exclude_list: Some(vec![]),
extensions: None, extensions: None,
options, options,

View File

@@ -19,6 +19,7 @@ use alloc::vec::Vec;
use core::convert::TryFrom; use core::convert::TryFrom;
use crypto::{ecdh, ecdsa}; use crypto::{ecdh, ecdsa};
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialRpEntity { pub struct PublicKeyCredentialRpEntity {
pub rp_id: String, pub rp_id: String,
@@ -48,6 +49,7 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialRpEntity {
} }
} }
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialUserEntity { pub struct PublicKeyCredentialUserEntity {
pub user_id: Vec<u8>, pub user_id: Vec<u8>,
@@ -94,16 +96,22 @@ impl From<PublicKeyCredentialUserEntity> for cbor::Value {
} }
} }
// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum PublicKeyCredentialType { pub enum PublicKeyCredentialType {
PublicKey, PublicKey,
// This is the default for all strings not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown,
} }
impl From<PublicKeyCredentialType> for cbor::Value { impl From<PublicKeyCredentialType> for cbor::Value {
fn from(cred_type: PublicKeyCredentialType) -> Self { fn from(cred_type: PublicKeyCredentialType) -> Self {
match cred_type { match cred_type {
PublicKeyCredentialType::PublicKey => "public-key", PublicKeyCredentialType::PublicKey => "public-key",
// We should never create this credential type.
PublicKeyCredentialType::Unknown => unreachable!(),
} }
.into() .into()
} }
@@ -116,11 +124,12 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialType {
let cred_type_string = read_text_string(cbor_value)?; let cred_type_string = read_text_string(cbor_value)?;
match &cred_type_string[..] { match &cred_type_string[..] {
"public-key" => Ok(PublicKeyCredentialType::PublicKey), "public-key" => Ok(PublicKeyCredentialType::PublicKey),
_ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), _ => Ok(PublicKeyCredentialType::Unknown),
} }
} }
} }
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
#[derive(PartialEq)] #[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PublicKeyCredentialParameter { pub struct PublicKeyCredentialParameter {
@@ -151,6 +160,7 @@ impl From<PublicKeyCredentialParameter> for cbor::Value {
} }
} }
// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum AuthenticatorTransport { pub enum AuthenticatorTransport {
Usb, Usb,
@@ -186,6 +196,7 @@ impl TryFrom<&cbor::Value> for AuthenticatorTransport {
} }
} }
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialDescriptor { pub struct PublicKeyCredentialDescriptor {
pub key_type: PublicKeyCredentialType, pub key_type: PublicKeyCredentialType,
@@ -205,10 +216,11 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialDescriptor {
let transports = match cred_desc_map.get(&cbor_text!("transports")) { let transports = match cred_desc_map.get(&cbor_text!("transports")) {
Some(exclude_entry) => { Some(exclude_entry) => {
let transport_vec = read_array(exclude_entry)?; let transport_vec = read_array(exclude_entry)?;
let mut transports = vec![]; let transports = transport_vec
for transport_value in transport_vec { .iter()
transports.push(AuthenticatorTransport::try_from(transport_value)?); .map(AuthenticatorTransport::try_from)
} .collect::<Result<Vec<AuthenticatorTransport>, Ctap2StatusCode>>(
)?;
Some(transports) Some(transports)
} }
None => None, None => None,
@@ -379,6 +391,7 @@ impl TryFrom<&cbor::Value> for GetAssertionOptions {
} }
} }
// https://www.w3.org/TR/webauthn/#packed-attestation
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PackedAttestationStatement { pub struct PackedAttestationStatement {
@@ -403,6 +416,9 @@ impl From<PackedAttestationStatement> for cbor::Value {
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum SignatureAlgorithm { pub enum SignatureAlgorithm {
ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize, ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize,
// This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown = 0,
} }
impl TryFrom<&cbor::Value> for SignatureAlgorithm { impl TryFrom<&cbor::Value> for SignatureAlgorithm {
@@ -411,11 +427,12 @@ impl TryFrom<&cbor::Value> for SignatureAlgorithm {
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> { fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
match read_integer(cbor_value)? { match read_integer(cbor_value)? {
ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256), ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
_ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), _ => Ok(SignatureAlgorithm::Unknown),
} }
} }
} }
// https://www.w3.org/TR/webauthn/#public-key-credential-source
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
@@ -679,6 +696,7 @@ mod test {
use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE; use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
use super::*; use super::*;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use crypto::rng256::{Rng256, ThreadRng256};
#[test] #[test]
fn test_read_unsigned() { fn test_read_unsigned() {
@@ -990,6 +1008,26 @@ mod test {
assert_eq!(credential_type, Ok(expected_credential_type)); assert_eq!(credential_type, Ok(expected_credential_type));
let created_cbor: cbor::Value = credential_type.unwrap().into(); let created_cbor: cbor::Value = credential_type.unwrap().into();
assert_eq!(created_cbor, cbor_credential_type); assert_eq!(created_cbor, cbor_credential_type);
let cbor_unknown_type = cbor_text!("unknown-type");
let unknown_type = PublicKeyCredentialType::try_from(&cbor_unknown_type);
let expected_unknown_type = PublicKeyCredentialType::Unknown;
assert_eq!(unknown_type, Ok(expected_unknown_type));
}
#[test]
fn test_from_into_signature_algorithm() {
let cbor_signature_algorithm = cbor_int!(ecdsa::PubKey::ES256_ALGORITHM);
let signature_algorithm = SignatureAlgorithm::try_from(&cbor_signature_algorithm);
let expected_signature_algorithm = SignatureAlgorithm::ES256;
assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
let created_cbor: cbor::Value = cbor_int!(signature_algorithm.unwrap() as i64);
assert_eq!(created_cbor, cbor_signature_algorithm);
let cbor_unknown_algorithm = cbor_int!(-1);
let unknown_algorithm = SignatureAlgorithm::try_from(&cbor_unknown_algorithm);
let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
assert_eq!(unknown_algorithm, Ok(expected_unknown_algorithm));
} }
#[test] #[test]
@@ -1006,6 +1044,23 @@ mod test {
assert_eq!(created_cbor, cbor_authenticator_transport); assert_eq!(created_cbor, cbor_authenticator_transport);
} }
#[test]
fn test_from_into_public_key_credential_parameter() {
let cbor_credential_parameter = cbor_map! {
"type" => "public-key",
"alg" => ecdsa::PubKey::ES256_ALGORITHM,
};
let credential_parameter =
PublicKeyCredentialParameter::try_from(&cbor_credential_parameter);
let expected_credential_parameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256,
};
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] #[test]
fn test_from_into_public_key_credential_descriptor() { fn test_from_into_public_key_credential_descriptor() {
let cbor_credential_descriptor = cbor_map! { let cbor_credential_descriptor = cbor_map! {
@@ -1026,7 +1081,7 @@ mod test {
} }
#[test] #[test]
fn test_from_extensions() { fn test_from_into_extensions() {
let cbor_extensions = cbor_map! { let cbor_extensions = cbor_map! {
"the_answer" => 42, "the_answer" => 42,
}; };
@@ -1036,6 +1091,53 @@ mod test {
.0 .0
.insert("the_answer".to_string(), cbor_int!(42)); .insert("the_answer".to_string(), cbor_int!(42));
assert_eq!(extensions, Ok(expected_extensions)); assert_eq!(extensions, Ok(expected_extensions));
let created_cbor: cbor::Value = extensions.unwrap().into();
assert_eq!(created_cbor, cbor_extensions);
}
#[test]
fn test_from_into_get_assertion_hmac_secret_output() {
let cbor_output = cbor_bytes![vec![0xC0; 32]];
let output = GetAssertionHmacSecretOutput::try_from(&cbor_output);
let expected_output = GetAssertionHmacSecretOutput(vec![0xC0; 32]);
assert_eq!(output, Ok(expected_output));
let created_cbor: cbor::Value = output.unwrap().into();
assert_eq!(created_cbor, cbor_output);
}
#[test]
fn test_hmac_secret_extension() {
let cbor_extensions = cbor_map! {
"hmac-secret" => true,
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
assert!(extensions.has_make_credential_hmac_secret().unwrap());
let cbor_extensions = cbor_map! {
"hmac-secret" => false,
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
assert!(!extensions.has_make_credential_hmac_secret().unwrap());
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk();
let cose_key = CoseKey::from(pk.clone());
let cbor_extensions = cbor_map! {
"hmac-secret" => cbor_map! {
1 => cbor::Value::Map(cose_key.0.clone()),
2 => vec![0x02; 32],
3 => vec![0x03; 32],
},
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
let get_assertion_input = extensions.get_assertion_hmac_secret();
let expected_input = GetAssertionHmacSecretInput {
key_agreement: cose_key,
salt_enc: vec![0x02; 32],
salt_auth: vec![0x03; 32],
};
assert_eq!(get_assertion_input, Some(Ok(expected_input)));
} }
#[test] #[test]
@@ -1087,8 +1189,6 @@ mod test {
#[test] #[test]
fn test_from_into_cose_key() { fn test_from_into_cose_key() {
use crypto::rng256::ThreadRng256;
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng); let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk(); let pk = sk.genpk();
@@ -1109,8 +1209,6 @@ mod test {
#[test] #[test]
fn test_credential_source_cbor_round_trip() { fn test_credential_source_cbor_round_trip() {
use crypto::rng256::{Rng256, ThreadRng256};
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let credential = PublicKeyCredentialSource { let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey, key_type: PublicKeyCredentialType::PublicKey,

View File

@@ -23,15 +23,18 @@ pub mod status_code;
mod storage; mod storage;
mod timed_permission; mod timed_permission;
#[cfg(feature = "with_ctap2_1")]
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
use self::command::{ use self::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command, MAX_CREDENTIAL_COUNT_IN_LIST, AuthenticatorMakeCredentialParameters, Command,
}; };
#[cfg(feature = "with_ctap2_1")]
use self::data_formats::AuthenticatorTransport;
use self::data_formats::{ use self::data_formats::{
AuthenticatorTransport, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
SignatureAlgorithm,
}; };
use self::hid::ChannelID; use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY}; use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
@@ -100,7 +103,9 @@ pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
pub const U2F_VERSION_STRING: &str = "U2F_V2"; pub const U2F_VERSION_STRING: &str = "U2F_V2";
pub const CREDENTIAL_PARAMETER: PublicKeyCredentialParameter = PublicKeyCredentialParameter { // We currently only support one algorithm for signatures: ES256.
// This algorithm is requested in MakeCredential and advertized in GetInfo.
pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey, cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256, alg: SignatureAlgorithm::ES256,
}; };
@@ -419,7 +424,7 @@ where
} }
} }
if !pub_key_cred_params.contains(&CREDENTIAL_PARAMETER) { if !pub_key_cred_params.contains(&ES256_CRED_PARAM) {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
} }
@@ -757,6 +762,7 @@ where
self.persistent_store.pin_hash().is_some(), self.persistent_store.pin_hash().is_some(),
); );
Ok(ResponseData::AuthenticatorGetInfo( Ok(ResponseData::AuthenticatorGetInfo(
#[cfg(feature = "with_ctap2_1")]
AuthenticatorGetInfoResponse { AuthenticatorGetInfoResponse {
versions: vec![ versions: vec![
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
@@ -770,14 +776,29 @@ where
pin_protocols: Some(vec![ pin_protocols: Some(vec![
CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION, CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION,
]), ]),
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST, max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
// You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your // You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your
// browser passes that value, it might be used to fingerprint. // browser passes that value, it might be used to fingerprint.
max_credential_id_length: None, max_credential_id_length: None,
transports: Some(vec![AuthenticatorTransport::Usb]), transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![CREDENTIAL_PARAMETER]), algorithms: Some(vec![ES256_CRED_PARAM]),
firmware_version: None, firmware_version: None,
}, },
#[cfg(not(feature = "with_ctap2_1"))]
AuthenticatorGetInfoResponse {
versions: vec![
#[cfg(feature = "with_ctap1")]
String::from(U2F_VERSION_STRING),
String::from(FIDO2_VERSION_STRING),
],
extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID,
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![
CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION,
]),
},
)) ))
} }
@@ -1098,7 +1119,10 @@ mod test {
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID); let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
#[cfg(feature = "with_ctap2_1")]
let mut expected_response = vec![0x00, 0xA8, 0x01]; let mut expected_response = vec![0x00, 0xA8, 0x01];
#[cfg(not(feature = "with_ctap2_1"))]
let mut expected_response = vec![0x00, 0xA6, 0x01];
// The difference here is a longer array of supported versions. // The difference here is a longer array of supported versions.
#[cfg(not(feature = "with_ctap1"))] #[cfg(not(feature = "with_ctap1"))]
expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]); expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]);
@@ -1112,10 +1136,13 @@ mod test {
0x03, 0x50, 0x03, 0x50,
]); ]);
expected_response.extend(AAGUID); expected_response.extend(AAGUID);
expected_response.extend(&[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
]);
#[cfg(feature = "with_ctap2_1")]
expected_response.extend( expected_response.extend(
[ [
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26,
0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B,
0x65, 0x79, 0x65, 0x79,
@@ -1139,7 +1166,7 @@ mod test {
user_display_name: None, user_display_name: None,
user_icon: None, user_icon: None,
}; };
let pub_key_cred_params = vec![CREDENTIAL_PARAMETER]; let pub_key_cred_params = vec![ES256_CRED_PARAM];
let options = MakeCredentialOptions { let options = MakeCredentialOptions {
rk: true, rk: true,
uv: false, uv: false,

View File

@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[cfg(feature = "with_ctap2_1")]
use super::data_formats::{AuthenticatorTransport, PublicKeyCredentialParameter};
use super::data_formats::{ use super::data_formats::{
AuthenticatorTransport, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameter, PublicKeyCredentialUserEntity, PublicKeyCredentialUserEntity,
}; };
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::string::String; use alloc::string::String;
@@ -102,21 +104,27 @@ impl From<AuthenticatorGetAssertionResponse> for cbor::Value {
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct AuthenticatorGetInfoResponse { pub struct AuthenticatorGetInfoResponse {
// TODO(kaczmarczyck) add fields from 2.1 // TODO(kaczmarczyck) add maxAuthenticatorConfigLength and defaultCredProtect
pub versions: Vec<String>, pub versions: Vec<String>,
pub extensions: Option<Vec<String>>, pub extensions: Option<Vec<String>>,
pub aaguid: [u8; 16], pub aaguid: [u8; 16],
pub options: Option<BTreeMap<String, bool>>, pub options: Option<BTreeMap<String, bool>>,
pub max_msg_size: Option<u64>, pub max_msg_size: Option<u64>,
pub pin_protocols: Option<Vec<u64>>, pub pin_protocols: Option<Vec<u64>>,
#[cfg(feature = "with_ctap2_1")]
pub max_credential_count_in_list: Option<u64>, pub max_credential_count_in_list: Option<u64>,
#[cfg(feature = "with_ctap2_1")]
pub max_credential_id_length: Option<u64>, pub max_credential_id_length: Option<u64>,
#[cfg(feature = "with_ctap2_1")]
pub transports: Option<Vec<AuthenticatorTransport>>, pub transports: Option<Vec<AuthenticatorTransport>>,
#[cfg(feature = "with_ctap2_1")]
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>, pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
#[cfg(feature = "with_ctap2_1")]
pub firmware_version: Option<u64>, pub firmware_version: Option<u64>,
} }
impl From<AuthenticatorGetInfoResponse> for cbor::Value { impl From<AuthenticatorGetInfoResponse> for cbor::Value {
#[cfg(feature = "with_ctap2_1")]
fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self { fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
let AuthenticatorGetInfoResponse { let AuthenticatorGetInfoResponse {
versions, versions,
@@ -154,6 +162,35 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
0x0E => firmware_version, 0x0E => firmware_version,
} }
} }
#[cfg(not(feature = "with_ctap2_1"))]
fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
let AuthenticatorGetInfoResponse {
versions,
extensions,
aaguid,
options,
max_msg_size,
pin_protocols,
} = get_info_response;
let options_cbor: Option<cbor::Value> = options.map(|options| {
let option_map: BTreeMap<_, _> = options
.into_iter()
.map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
.collect();
cbor_map_btree!(option_map)
});
cbor_map_options! {
0x01 => cbor_array_vec!(versions),
0x02 => extensions.map(|vec| cbor_array_vec!(vec)),
0x03 => &aaguid,
0x04 => options_cbor,
0x05 => max_msg_size,
0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
}
}
} }
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
@@ -183,6 +220,8 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::super::data_formats::PackedAttestationStatement; use super::super::data_formats::PackedAttestationStatement;
#[cfg(feature = "with_ctap2_1")]
use super::super::ES256_CRED_PARAM;
use super::*; use super::*;
#[test] #[test]
@@ -243,17 +282,58 @@ mod test {
options: None, options: None,
max_msg_size: None, max_msg_size: None,
pin_protocols: None, pin_protocols: None,
#[cfg(feature = "with_ctap2_1")]
max_credential_count_in_list: None, max_credential_count_in_list: None,
#[cfg(feature = "with_ctap2_1")]
max_credential_id_length: None, max_credential_id_length: None,
#[cfg(feature = "with_ctap2_1")]
transports: None, transports: None,
#[cfg(feature = "with_ctap2_1")]
algorithms: None, algorithms: None,
#[cfg(feature = "with_ctap2_1")]
firmware_version: None, firmware_version: None,
}; };
let response_cbor: Option<cbor::Value> = let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into(); ResponseData::AuthenticatorGetInfo(get_info_response).into();
let expected_cbor = cbor_map_options! { let expected_cbor = cbor_map_options! {
1 => cbor_array_vec![vec!["FIDO_2_0"]], 0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
3 => vec![0x00; 16], 0x03 => vec![0x00; 16],
};
assert_eq!(response_cbor, Some(expected_cbor));
}
#[test]
#[cfg(feature = "with_ctap2_1")]
fn test_get_info_optionals_into_cbor() {
let mut options_map = BTreeMap::new();
options_map.insert(String::from("rk"), true);
let get_info_response = AuthenticatorGetInfoResponse {
versions: vec!["FIDO_2_0".to_string()],
extensions: Some(vec!["extension".to_string()]),
aaguid: [0x00; 16],
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![1]),
max_credential_count_in_list: Some(20),
max_credential_id_length: Some(256),
transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![ES256_CRED_PARAM]),
firmware_version: Some(0),
};
let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into();
let expected_cbor = cbor_map_options! {
0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
0x02 => cbor_array_vec![vec!["extension"]],
0x03 => vec![0x00; 16],
0x04 => cbor_map! {"rk" => true},
0x05 => 1024,
0x06 => cbor_array_vec![vec![1]],
0x07 => 20,
0x08 => 256,
0x09 => cbor_array_vec![vec!["usb"]],
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
0x0E => 0,
}; };
assert_eq!(response_cbor, Some(expected_cbor)); assert_eq!(response_cbor, Some(expected_cbor));
} }