Skip to content
Snippets Groups Projects
Commit 8f20a75b authored by Fabian Kaczmarczyck's avatar Fabian Kaczmarczyck
Browse files

add 2.1 features to GetInfo

parent 50c5a1a4
No related branches found
No related tags found
No related merge requests found
......@@ -28,7 +28,9 @@ few limitations:
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),
our implementation was not reviewed nor officially tested and doesn't claim to
be FIDO Certified.
be FIDO Certified. With the upcoming next version of the
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html),
we started adding features, so master is currently between version 2.0 and 2.1.
### Cryptography
......
......@@ -13,16 +13,20 @@
// limitations under the License.
use super::data_formats::{
ok_or_missing, read_array, read_byte_string, read_integer, read_map, read_text_string,
read_unsigned, ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions,
MakeCredentialOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity,
PublicKeyCredentialType, PublicKeyCredentialUserEntity,
ok_or_missing, read_array, read_byte_string, read_map, read_text_string, read_unsigned,
ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions, MakeCredentialOptions,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use super::status_code::Ctap2StatusCode;
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryFrom;
// Depending on your memory, you can use Some(n) to limit request sizes.
// 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;
// CTAP specification (version 20190130) section 6.1
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum Command {
......@@ -106,7 +110,7 @@ pub struct AuthenticatorMakeCredentialParameters {
pub client_data_hash: Vec<u8>,
pub rp: PublicKeyCredentialRpEntity,
pub user: PublicKeyCredentialUserEntity,
pub pub_key_cred_params: Vec<(PublicKeyCredentialType, i64)>,
pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>,
pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
pub extensions: Option<Extensions>,
// Even though options are optional, we can use the default if not present.
......@@ -134,12 +138,9 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?;
let mut pub_key_cred_params = vec![];
for cred_param_map_value in cred_param_vec {
let cred_param_map = read_map(cred_param_map_value)?;
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_param_map.get(&cbor_text!("type")),
)?)?;
let alg = read_integer(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
pub_key_cred_params.push((cred_type, alg));
if let Ok(cred_param) = PublicKeyCredentialParameter::try_from(cred_param_map_value) {
pub_key_cred_params.push(cred_param);
}
}
let exclude_list = match param_map.get(&cbor_unsigned!(5)) {
......@@ -147,6 +148,11 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
let exclude_list_vec = read_array(entry)?;
let mut exclude_list = vec![];
for exclude_list_value in exclude_list_vec {
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST {
if exclude_list.len() as u64 >= count {
break;
}
}
exclude_list.push(PublicKeyCredentialDescriptor::try_from(exclude_list_value)?);
}
Some(exclude_list)
......@@ -218,6 +224,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
let allow_list_vec = read_array(entry)?;
let mut allow_list = vec![];
for allow_list_value in allow_list_vec {
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST {
if allow_list.len() as u64 >= count {
break;
}
}
allow_list.push(PublicKeyCredentialDescriptor::try_from(allow_list_value)?);
}
Some(allow_list)
......@@ -316,8 +327,10 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
#[cfg(test)]
mod test {
use super::super::data_formats::{
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
PublicKeyCredentialUserEntity,
};
use super::super::CREDENTIAL_PARAMETER;
use super::*;
use alloc::collections::BTreeMap;
......@@ -336,10 +349,7 @@ mod test {
"displayName" => "bar",
"icon" => "example.com/foo/icon.png",
},
4 => cbor_array![ cbor_map! {
"type" => "public-key",
"alg" => -7
} ],
4 => cbor_array![CREDENTIAL_PARAMETER],
5 => cbor_array![],
8 => vec![0x12, 0x34],
9 => 1,
......@@ -362,7 +372,6 @@ mod test {
user_display_name: Some("bar".to_string()),
user_icon: Some("example.com/foo/icon.png".to_string()),
};
let pub_key_cred_param = (PublicKeyCredentialType::PublicKey, -7);
let options = MakeCredentialOptions {
rk: false,
uv: false,
......@@ -371,7 +380,7 @@ mod test {
client_data_hash,
rp,
user,
pub_key_cred_params: vec![pub_key_cred_param],
pub_key_cred_params: vec![CREDENTIAL_PARAMETER],
exclude_list: Some(vec![]),
extensions: None,
options,
......
......@@ -121,6 +121,36 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialType {
}
}
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PublicKeyCredentialParameter {
pub cred_type: PublicKeyCredentialType,
pub alg: SignatureAlgorithm,
}
impl TryFrom<&cbor::Value> for PublicKeyCredentialParameter {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
let cred_param_map = read_map(cbor_value)?;
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_param_map.get(&cbor_text!("type")),
)?)?;
let alg =
SignatureAlgorithm::try_from(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
Ok(Self { cred_type, alg })
}
}
impl From<PublicKeyCredentialParameter> for cbor::Value {
fn from(cred_param: PublicKeyCredentialParameter) -> Self {
cbor_map_options! {
"type" => cred_param.cred_type,
"alg" => cred_param.alg as i64,
}
}
}
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum AuthenticatorTransport {
Usb,
......@@ -369,12 +399,23 @@ impl From<PackedAttestationStatement> for cbor::Value {
}
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum SignatureAlgorithm {
ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize,
}
impl TryFrom<&cbor::Value> for SignatureAlgorithm {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
match read_integer(cbor_value)? {
ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
_ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
}
}
}
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
......
......@@ -25,12 +25,13 @@ mod timed_permission;
use self::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command,
AuthenticatorMakeCredentialParameters, Command, MAX_CREDENTIAL_COUNT_IN_LIST,
};
use self::data_formats::{
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
PublicKeyCredentialUserEntity, SignatureAlgorithm,
AuthenticatorTransport, ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput,
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
......@@ -99,6 +100,11 @@ pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
#[cfg(feature = "with_ctap1")]
pub const U2F_VERSION_STRING: &str = "U2F_V2";
pub const CREDENTIAL_PARAMETER: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256,
};
fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
if pin_auth.len() != PIN_AUTH_LENGTH {
return false;
......@@ -413,15 +419,7 @@ where
}
}
let has_es_256 = pub_key_cred_params
.iter()
.any(|(credential_type, algorithm)| {
// Even though there is only one type now, checking seems safer in
// case of extension so you can't forget to update here.
*credential_type == PublicKeyCredentialType::PublicKey
&& *algorithm == SignatureAlgorithm::ES256 as i64
});
if !has_es_256 {
if !pub_key_cred_params.contains(&CREDENTIAL_PARAMETER) {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
......@@ -751,7 +749,7 @@ where
fn process_get_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
let mut options_map = BTreeMap::new();
// TODO(kaczmarczyck) add FIDO 2.1 options
// TODO(kaczmarczyck) add authenticatorConfig and credProtect options
options_map.insert(String::from("rk"), true);
options_map.insert(String::from("up"), true);
options_map.insert(
......@@ -772,6 +770,13 @@ where
pin_protocols: Some(vec![
CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION,
]),
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST,
// You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your
// browser passes that value, it might be used to fingerprint.
max_credential_id_length: None,
transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![CREDENTIAL_PARAMETER]),
firmware_version: None,
},
))
}
......@@ -1093,7 +1098,7 @@ mod test {
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
let mut expected_response = vec![0x00, 0xA6, 0x01];
let mut expected_response = vec![0x00, 0xA8, 0x01];
// The difference here is a longer array of supported versions.
#[cfg(not(feature = "with_ctap1"))]
expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]);
......@@ -1107,10 +1112,16 @@ mod test {
0x03, 0x50,
]);
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,
]);
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,
0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B,
0x65, 0x79,
]
.iter(),
);
assert_eq!(info_reponse, expected_response);
}
......@@ -1128,10 +1139,7 @@ mod test {
user_display_name: None,
user_icon: None,
};
let pub_key_cred_params = vec![(
PublicKeyCredentialType::PublicKey,
SignatureAlgorithm::ES256 as i64,
)];
let pub_key_cred_params = vec![CREDENTIAL_PARAMETER];
let options = MakeCredentialOptions {
rk: true,
uv: false,
......@@ -1228,12 +1236,8 @@ mod test {
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let pub_key_cred_params = vec![(
PublicKeyCredentialType::PublicKey,
SignatureAlgorithm::ES256 as i64 + 1, // any different number works
)];
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.pub_key_cred_params = pub_key_cred_params;
make_credential_params.pub_key_cred_params = vec![];
let make_credential_response =
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
......
......@@ -13,8 +13,8 @@
// limitations under the License.
use super::data_formats::{
CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialUserEntity,
AuthenticatorTransport, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameter, PublicKeyCredentialUserEntity,
};
use alloc::collections::BTreeMap;
use alloc::string::String;
......@@ -109,6 +109,11 @@ pub struct AuthenticatorGetInfoResponse {
pub options: Option<BTreeMap<String, bool>>,
pub max_msg_size: Option<u64>,
pub pin_protocols: Option<Vec<u64>>,
pub max_credential_count_in_list: Option<u64>,
pub max_credential_id_length: Option<u64>,
pub transports: Option<Vec<AuthenticatorTransport>>,
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
pub firmware_version: Option<u64>,
}
impl From<AuthenticatorGetInfoResponse> for cbor::Value {
......@@ -120,6 +125,11 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
options,
max_msg_size,
pin_protocols,
max_credential_count_in_list,
max_credential_id_length,
transports,
algorithms,
firmware_version,
} = get_info_response;
let options_cbor: Option<cbor::Value> = options.map(|options| {
......@@ -131,12 +141,17 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
});
cbor_map_options! {
1 => cbor_array_vec!(versions),
2 => extensions.map(|vec| cbor_array_vec!(vec)),
3 => &aaguid,
4 => options_cbor,
5 => max_msg_size,
6 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
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)),
0x07 => max_credential_count_in_list,
0x08 => max_credential_id_length,
0x09 => transports.map(|vec| cbor_array_vec!(vec)),
0x0A => algorithms.map(|vec| cbor_array_vec!(vec)),
0x0E => firmware_version,
}
}
}
......@@ -228,6 +243,11 @@ mod test {
options: None,
max_msg_size: None,
pin_protocols: None,
max_credential_count_in_list: None,
max_credential_id_length: None,
transports: None,
algorithms: None,
firmware_version: None,
};
let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment