Output parameters for CTAP2.1 (#297)
* finalizes output parameters for CTAP2.1 * explanation for internal UV
This commit is contained in:
@@ -200,6 +200,7 @@ impl ClientPin {
|
|||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_token: None,
|
pin_token: None,
|
||||||
retries: Some(persistent_store.pin_retries()? as u64),
|
retries: Some(persistent_store.pin_retries()? as u64),
|
||||||
|
power_cycle_state: Some(self.consecutive_pin_mismatches >= 3),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +216,7 @@ impl ClientPin {
|
|||||||
key_agreement,
|
key_agreement,
|
||||||
pin_token: None,
|
pin_token: None,
|
||||||
retries: None,
|
retries: None,
|
||||||
|
power_cycle_state: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +333,7 @@ impl ClientPin {
|
|||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_token: Some(pin_token),
|
pin_token: Some(pin_token),
|
||||||
retries: None,
|
retries: None,
|
||||||
|
power_cycle_state: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,6 +815,24 @@ mod test {
|
|||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
pin_token: None,
|
pin_token: None,
|
||||||
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
||||||
|
power_cycle_state: Some(false),
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
params.clone(),
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
|
);
|
||||||
|
|
||||||
|
client_pin.consecutive_pin_mismatches = 3;
|
||||||
|
let expected_response = Some(AuthenticatorClientPinResponse {
|
||||||
|
key_agreement: None,
|
||||||
|
pin_token: None,
|
||||||
|
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
||||||
|
power_cycle_state: Some(true),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
@@ -840,6 +861,7 @@ mod test {
|
|||||||
key_agreement: params.key_agreement.clone(),
|
key_agreement: params.key_agreement.clone(),
|
||||||
pin_token: None,
|
pin_token: None,
|
||||||
retries: None,
|
retries: None,
|
||||||
|
power_cycle_state: None,
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
@@ -1266,7 +1288,7 @@ mod test {
|
|||||||
|
|
||||||
let salt_enc = vec![0x01; 32];
|
let salt_enc = vec![0x01; 32];
|
||||||
let mut salt_auth = shared_secret.authenticate(&salt_enc);
|
let mut salt_auth = shared_secret.authenticate(&salt_enc);
|
||||||
salt_auth[0] = 0x00;
|
salt_auth[0] ^= 0x01;
|
||||||
let hmac_secret_input = GetAssertionHmacSecretInput {
|
let hmac_secret_input = GetAssertionHmacSecretInput {
|
||||||
key_agreement: client_pin
|
key_agreement: client_pin
|
||||||
.get_pin_protocol(pin_uv_auth_protocol)
|
.get_pin_protocol(pin_uv_auth_protocol)
|
||||||
|
|||||||
@@ -1101,6 +1101,7 @@ where
|
|||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64),
|
max_cred_blob_length: Some(MAX_CRED_BLOB_LENGTH as u64),
|
||||||
max_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64),
|
max_rp_ids_for_set_min_pin_length: Some(MAX_RP_IDS_LENGTH as u64),
|
||||||
|
certifications: None,
|
||||||
remaining_discoverable_credentials: Some(
|
remaining_discoverable_credentials: Some(
|
||||||
self.persistent_store.remaining_credentials()? as u64,
|
self.persistent_store.remaining_credentials()? as u64,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use super::data_formats::{
|
|||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use cbor::{cbor_array_vec, cbor_bool, cbor_map_btree, cbor_map_options, cbor_text};
|
use cbor::{cbor_array_vec, cbor_bool, cbor_int, cbor_map_btree, cbor_map_options, cbor_text};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ResponseData {
|
pub enum ResponseData {
|
||||||
@@ -92,6 +92,7 @@ pub struct AuthenticatorGetAssertionResponse {
|
|||||||
pub signature: Vec<u8>,
|
pub signature: Vec<u8>,
|
||||||
pub user: Option<PublicKeyCredentialUserEntity>,
|
pub user: Option<PublicKeyCredentialUserEntity>,
|
||||||
pub number_of_credentials: Option<u64>,
|
pub number_of_credentials: Option<u64>,
|
||||||
|
// 0x06: userSelected missing as we don't support displays.
|
||||||
pub large_blob_key: Option<Vec<u8>>,
|
pub large_blob_key: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +136,14 @@ pub struct AuthenticatorGetInfoResponse {
|
|||||||
pub firmware_version: Option<u64>,
|
pub firmware_version: Option<u64>,
|
||||||
pub max_cred_blob_length: Option<u64>,
|
pub max_cred_blob_length: Option<u64>,
|
||||||
pub max_rp_ids_for_set_min_pin_length: Option<u64>,
|
pub max_rp_ids_for_set_min_pin_length: Option<u64>,
|
||||||
|
// Missing response fields as they are only relevant for internal UV:
|
||||||
|
// - 0x11: preferredPlatformUvAttempts
|
||||||
|
// - 0x12: uvModality
|
||||||
|
// Add them when your hardware supports any kind of user verification within
|
||||||
|
// the boundary of the device, e.g. fingerprint or built-in keyboard.
|
||||||
|
pub certifications: Option<BTreeMap<String, i64>>,
|
||||||
pub remaining_discoverable_credentials: Option<u64>,
|
pub remaining_discoverable_credentials: Option<u64>,
|
||||||
|
// - 0x15: vendorPrototypeConfigCommands missing as we don't support it.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
||||||
@@ -157,15 +165,24 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
firmware_version,
|
firmware_version,
|
||||||
max_cred_blob_length,
|
max_cred_blob_length,
|
||||||
max_rp_ids_for_set_min_pin_length,
|
max_rp_ids_for_set_min_pin_length,
|
||||||
|
certifications,
|
||||||
remaining_discoverable_credentials,
|
remaining_discoverable_credentials,
|
||||||
} = get_info_response;
|
} = get_info_response;
|
||||||
|
|
||||||
let options_cbor: Option<cbor::Value> = options.map(|options| {
|
let options_cbor: Option<cbor::Value> = options.map(|options| {
|
||||||
let option_map: BTreeMap<_, _> = options
|
let options_map: BTreeMap<_, _> = options
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
|
.map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
|
||||||
.collect();
|
.collect();
|
||||||
cbor_map_btree!(option_map)
|
cbor_map_btree!(options_map)
|
||||||
|
});
|
||||||
|
|
||||||
|
let certifications_cbor: Option<cbor::Value> = certifications.map(|certifications| {
|
||||||
|
let certifications_map: BTreeMap<_, _> = certifications
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (cbor_text!(key), cbor_int!(value)))
|
||||||
|
.collect();
|
||||||
|
cbor_map_btree!(certifications_map)
|
||||||
});
|
});
|
||||||
|
|
||||||
cbor_map_options! {
|
cbor_map_options! {
|
||||||
@@ -185,6 +202,7 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
|
|||||||
0x0E => firmware_version,
|
0x0E => firmware_version,
|
||||||
0x0F => max_cred_blob_length,
|
0x0F => max_cred_blob_length,
|
||||||
0x10 => max_rp_ids_for_set_min_pin_length,
|
0x10 => max_rp_ids_for_set_min_pin_length,
|
||||||
|
0x13 => certifications_cbor,
|
||||||
0x14 => remaining_discoverable_credentials,
|
0x14 => remaining_discoverable_credentials,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,6 +213,8 @@ pub struct AuthenticatorClientPinResponse {
|
|||||||
pub key_agreement: Option<CoseKey>,
|
pub key_agreement: Option<CoseKey>,
|
||||||
pub pin_token: Option<Vec<u8>>,
|
pub pin_token: Option<Vec<u8>>,
|
||||||
pub retries: Option<u64>,
|
pub retries: Option<u64>,
|
||||||
|
pub power_cycle_state: Option<bool>,
|
||||||
|
// - 0x05: uvRetries missing as we don't support internal UV.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
||||||
@@ -203,12 +223,14 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
|||||||
key_agreement,
|
key_agreement,
|
||||||
pin_token,
|
pin_token,
|
||||||
retries,
|
retries,
|
||||||
|
power_cycle_state,
|
||||||
} = client_pin_response;
|
} = client_pin_response;
|
||||||
|
|
||||||
cbor_map_options! {
|
cbor_map_options! {
|
||||||
0x01 => key_agreement.map(cbor::Value::from),
|
0x01 => key_agreement.map(cbor::Value::from),
|
||||||
0x02 => pin_token,
|
0x02 => pin_token,
|
||||||
0x03 => retries,
|
0x03 => retries,
|
||||||
|
0x04 => power_cycle_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,6 +423,7 @@ mod test {
|
|||||||
firmware_version: None,
|
firmware_version: None,
|
||||||
max_cred_blob_length: None,
|
max_cred_blob_length: None,
|
||||||
max_rp_ids_for_set_min_pin_length: None,
|
max_rp_ids_for_set_min_pin_length: None,
|
||||||
|
certifications: None,
|
||||||
remaining_discoverable_credentials: None,
|
remaining_discoverable_credentials: None,
|
||||||
};
|
};
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
@@ -417,6 +440,8 @@ mod test {
|
|||||||
fn test_get_info_optionals_into_cbor() {
|
fn test_get_info_optionals_into_cbor() {
|
||||||
let mut options_map = BTreeMap::new();
|
let mut options_map = BTreeMap::new();
|
||||||
options_map.insert(String::from("rk"), true);
|
options_map.insert(String::from("rk"), true);
|
||||||
|
let mut certifications_map = BTreeMap::new();
|
||||||
|
certifications_map.insert(String::from("example-cert"), 1);
|
||||||
let get_info_response = AuthenticatorGetInfoResponse {
|
let get_info_response = AuthenticatorGetInfoResponse {
|
||||||
versions: vec!["FIDO_2_0".to_string()],
|
versions: vec!["FIDO_2_0".to_string()],
|
||||||
extensions: Some(vec!["extension".to_string()]),
|
extensions: Some(vec!["extension".to_string()]),
|
||||||
@@ -434,6 +459,7 @@ mod test {
|
|||||||
firmware_version: Some(0),
|
firmware_version: Some(0),
|
||||||
max_cred_blob_length: Some(1024),
|
max_cred_blob_length: Some(1024),
|
||||||
max_rp_ids_for_set_min_pin_length: Some(8),
|
max_rp_ids_for_set_min_pin_length: Some(8),
|
||||||
|
certifications: Some(certifications_map),
|
||||||
remaining_discoverable_credentials: Some(150),
|
remaining_discoverable_credentials: Some(150),
|
||||||
};
|
};
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
@@ -455,6 +481,7 @@ mod test {
|
|||||||
0x0E => 0,
|
0x0E => 0,
|
||||||
0x0F => 1024,
|
0x0F => 1024,
|
||||||
0x10 => 8,
|
0x10 => 8,
|
||||||
|
0x13 => cbor_map! {"example-cert" => 1},
|
||||||
0x14 => 150,
|
0x14 => 150,
|
||||||
};
|
};
|
||||||
assert_eq!(response_cbor, Some(expected_cbor));
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
@@ -462,15 +489,23 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_used_client_pin_into_cbor() {
|
fn test_used_client_pin_into_cbor() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pk = sk.genpk();
|
||||||
|
let cose_key = CoseKey::from(pk);
|
||||||
let client_pin_response = AuthenticatorClientPinResponse {
|
let client_pin_response = AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: Some(cose_key.clone()),
|
||||||
pin_token: Some(vec![70]),
|
pin_token: Some(vec![70]),
|
||||||
retries: None,
|
retries: Some(8),
|
||||||
|
power_cycle_state: Some(false),
|
||||||
};
|
};
|
||||||
let response_cbor: Option<cbor::Value> =
|
let response_cbor: Option<cbor::Value> =
|
||||||
ResponseData::AuthenticatorClientPin(Some(client_pin_response)).into();
|
ResponseData::AuthenticatorClientPin(Some(client_pin_response)).into();
|
||||||
let expected_cbor = cbor_map_options! {
|
let expected_cbor = cbor_map_options! {
|
||||||
|
0x01 => cbor::Value::from(cose_key),
|
||||||
0x02 => vec![70],
|
0x02 => vec![70],
|
||||||
|
0x03 => 8,
|
||||||
|
0x04 => false,
|
||||||
};
|
};
|
||||||
assert_eq!(response_cbor, Some(expected_cbor));
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user