adds PIN protocol V2 to all commands (#295)

This commit is contained in:
kaczmarczyck
2021-03-18 17:29:32 +01:00
committed by GitHub
parent b1773d1cf3
commit aec1e0a409
8 changed files with 520 additions and 261 deletions

View File

@@ -90,14 +90,12 @@ fn check_and_store_new_pin(
}
#[cfg_attr(test, derive(IntoEnumIterator))]
// TODO remove when all variants are used
#[allow(dead_code)]
pub enum PinPermission {
// All variants should use integers with a single bit set.
MakeCredential = 0x01,
GetAssertion = 0x02,
CredentialManagement = 0x04,
BioEnrollment = 0x08,
_BioEnrollment = 0x08,
LargeBlobWrite = 0x10,
AuthenticatorConfiguration = 0x20,
}
@@ -414,18 +412,19 @@ impl ClientPin {
Ok(ResponseData::AuthenticatorClientPin(response))
}
/// Verifies the HMAC for the PIN protocol V1 pinUvAuthToken.
pub fn verify_pin_auth_token(
/// Verifies the HMAC for the pinUvAuthToken of the given version.
pub fn verify_pin_uv_auth_token(
&self,
hmac_contents: &[u8],
pin_uv_auth_param: &[u8],
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> Result<(), Ctap2StatusCode> {
verify_pin_uv_auth_token(
self.get_pin_protocol(PinUvAuthProtocol::V1)
self.get_pin_protocol(pin_uv_auth_protocol)
.get_pin_uv_auth_token(),
hmac_contents,
pin_uv_auth_param,
PinUvAuthProtocol::V1,
pin_uv_auth_protocol,
)
}
@@ -554,6 +553,7 @@ impl ClientPin {
#[cfg(test)]
mod test {
use super::super::pin_protocol::authenticate_pin_uv_auth_token;
use super::*;
use alloc::vec;
use crypto::rng256::ThreadRng256;
@@ -639,6 +639,48 @@ mod test {
(client_pin, params)
}
#[test]
fn test_mix_pin_protocols() {
let mut rng = ThreadRng256 {};
let client_pin = ClientPin::new(&mut rng);
let pin_protocol_v1 = client_pin.get_pin_protocol(PinUvAuthProtocol::V1);
let pin_protocol_v2 = client_pin.get_pin_protocol(PinUvAuthProtocol::V2);
let message = vec![0xAA; 16];
let shared_secret_v1 = pin_protocol_v1
.decapsulate(pin_protocol_v1.get_public_key(), PinUvAuthProtocol::V1)
.unwrap();
let shared_secret_v2 = pin_protocol_v2
.decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V2)
.unwrap();
let ciphertext = shared_secret_v1.encrypt(&mut rng, &message).unwrap();
let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
let ciphertext = shared_secret_v2.encrypt(&mut rng, &message).unwrap();
let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
let fake_secret_v1 = pin_protocol_v1
.decapsulate(pin_protocol_v2.get_public_key(), PinUvAuthProtocol::V1)
.unwrap();
let ciphertext = shared_secret_v1.encrypt(&mut rng, &message).unwrap();
let plaintext = fake_secret_v1.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
let ciphertext = fake_secret_v1.encrypt(&mut rng, &message).unwrap();
let plaintext = shared_secret_v1.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
let fake_secret_v2 = pin_protocol_v2
.decapsulate(pin_protocol_v1.get_public_key(), PinUvAuthProtocol::V2)
.unwrap();
let ciphertext = shared_secret_v2.encrypt(&mut rng, &message).unwrap();
let plaintext = fake_secret_v2.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
let ciphertext = fake_secret_v2.encrypt(&mut rng, &message).unwrap();
let plaintext = shared_secret_v2.decrypt(&ciphertext).unwrap();
assert_ne!(&message, &plaintext);
}
fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
@@ -1310,6 +1352,77 @@ mod test {
);
}
#[test]
fn test_verify_pin_uv_auth_token() {
let mut rng = ThreadRng256 {};
let client_pin = ClientPin::new(&mut rng);
let message = [0xAA];
let pin_uv_auth_token_v1 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V1)
.get_pin_uv_auth_token();
let pin_uv_auth_param_v1 =
authenticate_pin_uv_auth_token(&pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V1);
let pin_uv_auth_token_v2 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V2)
.get_pin_uv_auth_token();
let pin_uv_auth_param_v2 =
authenticate_pin_uv_auth_token(&pin_uv_auth_token_v2, &message, PinUvAuthProtocol::V2);
let pin_uv_auth_param_v1_from_v2_token =
authenticate_pin_uv_auth_token(&pin_uv_auth_token_v2, &message, PinUvAuthProtocol::V1);
let pin_uv_auth_param_v2_from_v1_token =
authenticate_pin_uv_auth_token(&pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V2);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v1,
PinUvAuthProtocol::V1
),
Ok(())
);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v2,
PinUvAuthProtocol::V2
),
Ok(())
);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v1,
PinUvAuthProtocol::V2
),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v2,
PinUvAuthProtocol::V1
),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v1_from_v2_token,
PinUvAuthProtocol::V1
),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
&pin_uv_auth_param_v2_from_v1_token,
PinUvAuthProtocol::V2
),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
}
#[test]
fn test_reset() {
let mut rng = ThreadRng256 {};

View File

@@ -155,7 +155,7 @@ pub struct AuthenticatorMakeCredentialParameters {
// Same for options, use defaults when not present.
pub options: MakeCredentialOptions,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
pub enterprise_attestation: Option<u64>,
}
@@ -213,7 +213,9 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
.unwrap_or_default();
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol
.map(PinUvAuthProtocol::try_from)
.transpose()?;
let enterprise_attestation = enterprise_attestation.map(extract_unsigned).transpose()?;
Ok(AuthenticatorMakeCredentialParameters {
@@ -241,7 +243,7 @@ pub struct AuthenticatorGetAssertionParameters {
// Same for options, use defaults when not present.
pub options: GetAssertionOptions,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
}
impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
@@ -288,7 +290,9 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
.unwrap_or_default();
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol
.map(PinUvAuthProtocol::try_from)
.transpose()?;
Ok(AuthenticatorGetAssertionParameters {
rp_id,
@@ -366,7 +370,7 @@ pub struct AuthenticatorLargeBlobsParameters {
pub offset: usize,
pub length: Option<usize>,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
}
impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
@@ -394,7 +398,9 @@ impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
.transpose()?
.map(|u| u as usize);
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol
.map(PinUvAuthProtocol::try_from)
.transpose()?;
if get.is_none() && set.is_none() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
@@ -439,7 +445,7 @@ pub struct AuthenticatorConfigParameters {
pub sub_command: ConfigSubCommand,
pub sub_command_params: Option<ConfigSubCommandParams>,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
}
impl TryFrom<cbor::Value> for AuthenticatorConfigParameters {
@@ -463,7 +469,9 @@ impl TryFrom<cbor::Value> for AuthenticatorConfigParameters {
_ => None,
};
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol
.map(PinUvAuthProtocol::try_from)
.transpose()?;
Ok(AuthenticatorConfigParameters {
sub_command,
@@ -507,8 +515,8 @@ impl TryFrom<cbor::Value> for AuthenticatorAttestationMaterial {
pub struct AuthenticatorCredentialManagementParameters {
pub sub_command: CredentialManagementSubCommand,
pub sub_command_params: Option<CredentialManagementSubCommandParameters>,
pub pin_uv_auth_protocol: Option<u64>,
pub pin_auth: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
pub pin_uv_auth_param: Option<Vec<u8>>,
}
impl TryFrom<cbor::Value> for AuthenticatorCredentialManagementParameters {
@@ -520,7 +528,7 @@ impl TryFrom<cbor::Value> for AuthenticatorCredentialManagementParameters {
0x01 => sub_command,
0x02 => sub_command_params,
0x03 => pin_uv_auth_protocol,
0x04 => pin_auth,
0x04 => pin_uv_auth_param,
} = extract_map(cbor_value)?;
}
@@ -528,14 +536,16 @@ impl TryFrom<cbor::Value> for AuthenticatorCredentialManagementParameters {
let sub_command_params = sub_command_params
.map(CredentialManagementSubCommandParameters::try_from)
.transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
let pin_auth = pin_auth.map(extract_byte_string).transpose()?;
let pin_uv_auth_protocol = pin_uv_auth_protocol
.map(PinUvAuthProtocol::try_from)
.transpose()?;
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
Ok(AuthenticatorCredentialManagementParameters {
sub_command,
sub_command_params,
pin_uv_auth_protocol,
pin_auth,
pin_uv_auth_param,
})
}
}
@@ -630,7 +640,7 @@ mod test {
extensions: MakeCredentialExtensions::default(),
options,
pin_uv_auth_param: Some(vec![0x12, 0x34]),
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
enterprise_attestation: Some(2),
};
@@ -677,7 +687,7 @@ mod test {
extensions: GetAssertionExtensions::default(),
options,
pin_uv_auth_param: Some(vec![0x12, 0x34]),
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
};
assert_eq!(
@@ -766,8 +776,8 @@ mod test {
let expected_cred_management_parameters = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsBegin,
sub_command_params: Some(params),
pin_uv_auth_protocol: Some(1),
pin_auth: Some(vec![0x9A; 16]),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param: Some(vec![0x9A; 16]),
};
assert_eq!(
@@ -821,7 +831,7 @@ mod test {
offset: 0,
length: Some(MIN_LARGE_BLOB_LEN),
pin_uv_auth_param: Some(vec![0xA9]),
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
};
assert_eq!(
returned_large_blobs_parameters,
@@ -843,7 +853,7 @@ mod test {
offset: 1,
length: None,
pin_uv_auth_param: Some(vec![0xA9]),
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
};
assert_eq!(
returned_large_blobs_parameters,

View File

@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::client_pin::ClientPin;
use super::client_pin::{ClientPin, PinPermission};
use super::command::AuthenticatorConfigParameters;
use super::customization::ENTERPRISE_ATTESTATION_MODE;
use super::data_formats::{ConfigSubCommand, ConfigSubCommandParams, SetMinPinLengthParams};
use super::response::ResponseData;
use super::status_code::Ctap2StatusCode;
use super::storage::PersistentStore;
use super::{check_pin_uv_auth_protocol, ENTERPRISE_ATTESTATION_MODE};
use alloc::vec;
/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig.
@@ -91,10 +91,10 @@ pub fn process_config(
_ => true,
} && persistent_store.has_always_uv()?;
if persistent_store.pin_hash()?.is_some() || enforce_uv {
// TODO(kaczmarczyck) The error code is specified inconsistently with other commands.
check_pin_uv_auth_protocol(pin_uv_auth_protocol)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let auth_param = pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
// Constants are taken from the specification, section 6.11, step 4.2.
let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, sub_command as u8]);
@@ -103,7 +103,12 @@ pub fn process_config(
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
}
client_pin.verify_pin_auth_token(&config_data, &auth_param)?;
client_pin.verify_pin_uv_auth_token(
&config_data,
&pin_uv_auth_param,
pin_uv_auth_protocol,
)?;
client_pin.has_permission(PinPermission::AuthenticatorConfiguration)?;
}
match sub_command {
@@ -127,6 +132,7 @@ mod test {
use super::*;
use crate::ctap::customization::ENFORCE_ALWAYS_UV;
use crate::ctap::data_formats::PinUvAuthProtocol;
use crate::ctap::pin_protocol::authenticate_pin_uv_auth_token;
use crypto::rng256::ThreadRng256;
#[test]
@@ -194,25 +200,24 @@ mod test {
}
}
#[test]
fn test_process_toggle_always_uv_with_pin() {
fn test_helper_process_toggle_always_uv_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
persistent_store.set_pin(&[0x88; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x99, 0xBA, 0x0A, 0x57, 0x9D, 0x95, 0x5A, 0x44, 0xE3, 0x77, 0xCF, 0x95, 0x51, 0x3F,
0xFD, 0xBE,
]);
let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]);
let pin_uv_auth_param =
authenticate_pin_uv_auth_token(&pin_uv_auth_token, &config_data, pin_uv_auth_protocol);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::ToggleAlwaysUv,
sub_command_params: None,
pin_uv_auth_param: pin_uv_auth_param.clone(),
pin_uv_auth_protocol: Some(1),
pin_uv_auth_param: Some(pin_uv_auth_param.clone()),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
if ENFORCE_ALWAYS_UV {
@@ -228,14 +233,24 @@ mod test {
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::ToggleAlwaysUv,
sub_command_params: None,
pin_uv_auth_param,
pin_uv_auth_protocol: Some(1),
pin_uv_auth_param: Some(pin_uv_auth_param),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert!(!persistent_store.has_always_uv().unwrap());
}
#[test]
fn test_process_toggle_always_uv_with_pin_v1() {
test_helper_process_toggle_always_uv_with_pin(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_toggle_always_uv_with_pin_v2() {
test_helper_process_toggle_always_uv_with_pin(PinUvAuthProtocol::V2);
}
fn create_min_pin_config_params(
min_pin_length: u8,
min_pin_length_rp_ids: Option<Vec<String>>,
@@ -251,7 +266,7 @@ mod test {
set_min_pin_length_params,
)),
pin_uv_auth_param: None,
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
}
}
@@ -276,22 +291,22 @@ mod test {
persistent_store.set_pin(&[0x88; 16], 8).unwrap();
let min_pin_length = 8;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
let pin_auth = vec![
let pin_uv_auth_param = vec![
0x5C, 0x69, 0x71, 0x29, 0xBD, 0xCC, 0x53, 0xE8, 0x3C, 0x97, 0x62, 0xDD, 0x90, 0x29,
0xB2, 0xDE,
];
config_params.pin_uv_auth_param = Some(pin_auth);
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
// Third, decreasing the minimum PIN length from 8 to 7 fails.
let mut config_params = create_min_pin_config_params(7, None);
let pin_auth = vec![
let pin_uv_auth_param = vec![
0xC5, 0xEA, 0xC1, 0x5E, 0x7F, 0x80, 0x70, 0x1A, 0x4E, 0xC4, 0xAD, 0x85, 0x35, 0xD8,
0xA7, 0x71,
];
config_params.pin_uv_auth_param = Some(pin_auth);
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(
config_response,
@@ -329,11 +344,11 @@ mod test {
persistent_store.set_pin(&[0x88; 16], 8).unwrap();
let mut config_params =
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
let pin_auth = vec![
let pin_uv_auth_param = vec![
0x40, 0x51, 0x2D, 0xAC, 0x2D, 0xE2, 0x15, 0x77, 0x5C, 0xF9, 0x5B, 0x62, 0x9A, 0x2D,
0xD6, 0xDA,
];
config_params.pin_uv_auth_param = Some(pin_auth.clone());
config_params.pin_uv_auth_param = Some(pin_uv_auth_param.clone());
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(persistent_store.min_pin_length(), Ok(min_pin_length));
@@ -346,7 +361,7 @@ mod test {
// One PIN auth shouldn't work for different lengths.
let mut config_params =
create_min_pin_config_params(9, Some(min_pin_length_rp_ids.clone()));
config_params.pin_uv_auth_param = Some(pin_auth.clone());
config_params.pin_uv_auth_param = Some(pin_uv_auth_param.clone());
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(
config_response,
@@ -364,7 +379,7 @@ mod test {
min_pin_length,
Some(vec!["counter.example.com".to_string()]),
);
config_params.pin_uv_auth_param = Some(pin_auth);
config_params.pin_uv_auth_param = Some(pin_uv_auth_param);
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(
config_response,
@@ -426,7 +441,7 @@ mod test {
set_min_pin_length_params,
)),
pin_uv_auth_param,
pin_uv_auth_protocol: Some(1),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
};
let config_response = process_config(&mut persistent_store, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));

View File

@@ -22,7 +22,7 @@ use super::data_formats::{
use super::response::{AuthenticatorCredentialManagementResponse, ResponseData};
use super::status_code::Ctap2StatusCode;
use super::storage::PersistentStore;
use super::{check_pin_uv_auth_protocol, StatefulCommand, StatefulPermission};
use super::{StatefulCommand, StatefulPermission};
use alloc::collections::BTreeSet;
use alloc::string::String;
use alloc::vec;
@@ -259,7 +259,7 @@ pub fn process_credential_management(
sub_command,
sub_command_params,
pin_uv_auth_protocol,
pin_auth,
pin_uv_auth_param,
} = cred_management_params;
match (sub_command, stateful_command_permission.get_command()) {
@@ -282,15 +282,21 @@ pub fn process_credential_management(
| CredentialManagementSubCommand::EnumerateCredentialsBegin
| CredentialManagementSubCommand::DeleteCredential
| CredentialManagementSubCommand::UpdateUserInformation => {
check_pin_uv_auth_protocol(pin_uv_auth_protocol)?;
let pin_auth = pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
let mut management_data = vec![sub_command as u8];
if let Some(sub_command_params) = sub_command_params.clone() {
if !cbor::write(sub_command_params.into(), &mut management_data) {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
}
client_pin.verify_pin_auth_token(&management_data, &pin_auth)?;
client_pin.verify_pin_uv_auth_token(
&management_data,
&pin_uv_auth_param,
pin_uv_auth_protocol,
)?;
// The RP ID permission is handled differently per subcommand below.
client_pin.has_permission(PinPermission::CredentialManagement)?;
}
@@ -352,6 +358,7 @@ pub fn process_credential_management(
#[cfg(test)]
mod test {
use super::super::data_formats::{PinUvAuthProtocol, PublicKeyCredentialType};
use super::super::pin_protocol::authenticate_pin_uv_auth_token;
use super::super::CtapState;
use super::*;
use crypto::rng256::{Rng256, ThreadRng256};
@@ -377,13 +384,12 @@ mod test {
}
}
#[test]
fn test_process_get_creds_metadata() {
fn test_helper_process_get_creds_metadata(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut rng = ThreadRng256 {};
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let credential_source = create_credential_source(&mut rng);
let user_immediately_present = |_| Ok(());
@@ -391,16 +397,18 @@ mod test {
ctap_state.client_pin = client_pin;
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
0xC5, 0xFB, 0x75, 0x55, 0x98, 0xB5, 0x19, 0x01, 0xB3, 0x31, 0x7D, 0xFE, 0x1D, 0xF5,
0xFB, 0x00,
]);
let management_data = vec![CredentialManagementSubCommand::GetCredsMetadata as u8];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&management_data,
pin_uv_auth_protocol,
);
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
sub_command_params: None,
pin_uv_auth_protocol: Some(1),
pin_auth: pin_auth.clone(),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
pin_uv_auth_param: Some(pin_uv_auth_param.clone()),
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -427,8 +435,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
sub_command_params: None,
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
pin_uv_auth_param: Some(pin_uv_auth_param),
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -449,6 +457,16 @@ mod test {
};
}
#[test]
fn test_process_get_creds_metadata_v1() {
test_helper_process_get_creds_metadata(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_get_creds_metadata_v2() {
test_helper_process_get_creds_metadata(PinUvAuthProtocol::V2);
}
#[test]
fn test_process_enumerate_rps_with_uv() {
let mut rng = ThreadRng256 {};
@@ -474,7 +492,7 @@ mod test {
.unwrap();
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
]);
@@ -482,8 +500,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateRpsBegin,
sub_command_params: None,
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -507,7 +525,7 @@ mod test {
sub_command: CredentialManagementSubCommand::EnumerateRpsGetNextRp,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_auth: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -532,7 +550,7 @@ mod test {
sub_command: CredentialManagementSubCommand::EnumerateRpsGetNextRp,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_auth: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -571,7 +589,7 @@ mod test {
}
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
]);
@@ -582,8 +600,8 @@ mod test {
let mut cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateRpsBegin,
sub_command_params: None,
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
for _ in 0..NUM_CREDENTIALS {
@@ -613,7 +631,7 @@ mod test {
sub_command: CredentialManagementSubCommand::EnumerateRpsGetNextRp,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_auth: None,
pin_uv_auth_param: None,
};
}
@@ -658,7 +676,7 @@ mod test {
.unwrap();
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
let pin_uv_auth_param = Some(vec![
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
0x2B, 0x68,
]);
@@ -673,8 +691,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsBegin,
sub_command_params: Some(sub_command_params),
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -697,7 +715,7 @@ mod test {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_auth: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -721,7 +739,7 @@ mod test {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_auth: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -756,7 +774,7 @@ mod test {
.unwrap();
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
let pin_uv_auth_param = Some(vec![
0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89,
0x9C, 0x64,
]);
@@ -774,8 +792,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::DeleteCredential,
sub_command_params: Some(sub_command_params.clone()),
pin_uv_auth_protocol: Some(1),
pin_auth: pin_auth.clone(),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param: pin_uv_auth_param.clone(),
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -792,8 +810,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::DeleteCredential,
sub_command_params: Some(sub_command_params),
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -828,7 +846,7 @@ mod test {
.unwrap();
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
let pin_uv_auth_param = Some(vec![
0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD,
0x9D, 0xB7,
]);
@@ -852,8 +870,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::UpdateUserInformation,
sub_command_params: Some(sub_command_params),
pin_uv_auth_protocol: Some(1),
pin_auth,
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
@@ -882,44 +900,7 @@ mod test {
}
#[test]
fn test_process_credential_management_invalid_pin_uv_auth_protocol() {
let mut rng = ThreadRng256 {};
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
ctap_state.client_pin = client_pin;
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_auth = Some(vec![
0xC5, 0xFB, 0x75, 0x55, 0x98, 0xB5, 0x19, 0x01, 0xB3, 0x31, 0x7D, 0xFE, 0x1D, 0xF5,
0xFB, 0x00,
]);
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
sub_command_params: None,
pin_uv_auth_protocol: Some(123456),
pin_auth,
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
cred_management_response,
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
}
#[test]
fn test_process_credential_management_invalid_pin_auth() {
fn test_process_credential_management_invalid_pin_uv_auth_param() {
let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
@@ -929,8 +910,8 @@ mod test {
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,
sub_command_params: None,
pin_uv_auth_protocol: Some(1),
pin_auth: Some(vec![0u8; 16]),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param: Some(vec![0u8; 16]),
};
let cred_management_response = process_credential_management(
&mut ctap_state.persistent_store,

View File

@@ -816,8 +816,8 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PinUvAuthProtocol {
V1,
V2,
V1 = 1,
V2 = 2,
}
impl TryFrom<cbor::Value> for PinUvAuthProtocol {

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::check_pin_uv_auth_protocol;
use super::client_pin::{ClientPin, PinPermission};
use super::command::AuthenticatorLargeBlobsParameters;
use super::response::{AuthenticatorLargeBlobsResponse, ResponseData};
@@ -91,17 +90,20 @@ impl LargeBlobs {
if persistent_store.pin_hash()?.is_some() || persistent_store.has_always_uv()? {
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
// TODO(kaczmarczyck) Error codes for PIN protocol differ across commands.
// Change to Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED for None?
check_pin_uv_auth_protocol(pin_uv_auth_protocol)?;
client_pin.has_permission(PinPermission::LargeBlobWrite)?;
let mut message = vec![0xFF; 32];
message.extend(&[0x0C, 0x00]);
let pin_uv_auth_protocol =
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
let mut large_blob_data = vec![0xFF; 32];
large_blob_data.extend(&[0x0C, 0x00]);
let mut offset_bytes = [0u8; 4];
LittleEndian::write_u32(&mut offset_bytes, offset as u32);
message.extend(&offset_bytes);
message.extend(&Sha256::hash(set.as_slice()));
client_pin.verify_pin_auth_token(&message, &pin_uv_auth_param)?;
large_blob_data.extend(&offset_bytes);
large_blob_data.extend(&Sha256::hash(set.as_slice()));
client_pin.verify_pin_uv_auth_token(
&large_blob_data,
&pin_uv_auth_param,
pin_uv_auth_protocol,
)?;
client_pin.has_permission(PinPermission::LargeBlobWrite)?;
}
if offset + set.len() > self.expected_length {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
@@ -136,6 +138,7 @@ impl LargeBlobs {
#[cfg(test)]
mod test {
use super::super::data_formats::PinUvAuthProtocol;
use super::super::pin_protocol::authenticate_pin_uv_auth_token;
use super::*;
use crypto::rng256::ThreadRng256;
@@ -358,14 +361,13 @@ mod test {
);
}
#[test]
fn test_process_command_commit_with_pin() {
fn test_helper_process_command_commit_with_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -374,18 +376,23 @@ mod test {
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x68, 0x0C, 0x3F, 0x6A, 0x62, 0x47, 0xE6, 0x7C, 0x23, 0x1F, 0x79, 0xE3, 0xDC, 0x6D,
0xC3, 0xDE,
]);
let mut large_blob_data = vec![0xFF; 32];
// Command constant and offset bytes.
large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]);
large_blob_data.extend(&Sha256::hash(&large_blob));
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&large_blob_data,
pin_uv_auth_protocol,
);
let large_blobs_params = AuthenticatorLargeBlobsParameters {
get: None,
set: Some(large_blob),
offset: 0,
length: Some(BLOB_LEN),
pin_uv_auth_param,
pin_uv_auth_protocol: Some(1),
pin_uv_auth_param: Some(pin_uv_auth_param),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let large_blobs_response =
large_blobs.process_command(&mut persistent_store, &mut client_pin, large_blobs_params);
@@ -394,4 +401,14 @@ mod test {
Ok(ResponseData::AuthenticatorLargeBlobs(None))
);
}
#[test]
fn test_process_command_commit_with_pin_v1() {
test_helper_process_command_commit_with_pin(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_command_commit_with_pin_v2() {
test_helper_process_command_commit_with_pin(PinUvAuthProtocol::V2);
}
}

View File

@@ -44,9 +44,9 @@ use self::customization::{
};
use self::data_formats::{
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
GetAssertionExtensions, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameter, PublicKeyCredentialSource, PublicKeyCredentialType,
PublicKeyCredentialUserEntity, SignatureAlgorithm,
GetAssertionExtensions, PackedAttestationStatement, PinUvAuthProtocol,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::large_blobs::{LargeBlobs, MAX_MSG_SIZE};
@@ -109,9 +109,6 @@ pub const U2F_VERSION_STRING: &str = "U2F_V2";
// TODO(#106) change to final string when ready
pub const FIDO2_1_VERSION_STRING: &str = "FIDO_2_1_PRE";
// This is the currently supported PIN protocol version.
const PIN_PROTOCOL_VERSION: u64 = 1;
// 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 {
@@ -119,16 +116,6 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
alg: SignatureAlgorithm::ES256,
};
// Checks the PIN protocol parameter against all supported versions.
pub fn check_pin_uv_auth_protocol(
pin_uv_auth_protocol: Option<u64>,
) -> Result<(), Ctap2StatusCode> {
match pin_uv_auth_protocol {
Some(PIN_PROTOCOL_VERSION) => Ok(()),
_ => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
}
}
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool.
@@ -526,7 +513,7 @@ where
fn pin_uv_auth_precheck(
&mut self,
pin_uv_auth_param: &Option<Vec<u8>>,
pin_uv_auth_protocol: Option<u64>,
pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
cid: ChannelID,
) -> Result<(), Ctap2StatusCode> {
if let Some(auth_param) = &pin_uv_auth_param {
@@ -539,11 +526,9 @@ where
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
}
}
check_pin_uv_auth_protocol(pin_uv_auth_protocol)
} else {
Ok(())
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
}
Ok(())
}
fn process_make_credential(
@@ -644,13 +629,16 @@ where
// User verification depends on the PIN auth inputs, which are checked here.
let ed_flag = if has_extension_output { ED_FLAG } else { 0 };
let flags = match pin_uv_auth_param {
Some(pin_auth) => {
Some(pin_uv_auth_param) => {
if self.persistent_store.pin_hash()?.is_none() {
// Specification is unclear, could be CTAP2_ERR_INVALID_OPTION.
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
self.client_pin
.verify_pin_auth_token(&client_data_hash, &pin_auth)?;
self.client_pin.verify_pin_uv_auth_token(
&client_data_hash,
&pin_uv_auth_param,
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
)?;
self.client_pin
.has_permission(PinPermission::MakeCredential)?;
self.client_pin.ensure_rp_id_permission(&rp_id)?;
@@ -932,13 +920,16 @@ where
// not support internal UV. User presence is requested as an option.
let has_uv = pin_uv_auth_param.is_some();
let mut flags = match pin_uv_auth_param {
Some(pin_auth) => {
Some(pin_uv_auth_param) => {
if self.persistent_store.pin_hash()?.is_none() {
// Specification is unclear, could be CTAP2_ERR_UNSUPPORTED_OPTION.
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
self.client_pin
.verify_pin_auth_token(&client_data_hash, &pin_auth)?;
self.client_pin.verify_pin_uv_auth_token(
&client_data_hash,
&pin_uv_auth_param,
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?,
)?;
self.client_pin
.has_permission(PinPermission::GetAssertion)?;
self.client_pin.ensure_rp_id_permission(&rp_id)?;
@@ -1082,7 +1073,11 @@ where
aaguid: self.persistent_store.aaguid()?,
options: Some(options_map),
max_msg_size: Some(MAX_MSG_SIZE as u64),
pin_protocols: Some(vec![PIN_PROTOCOL_VERSION]),
// The order implies preference. We favor the new V2.
pin_protocols: Some(vec![
PinUvAuthProtocol::V2 as u64,
PinUvAuthProtocol::V1 as u64,
]),
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
transports: Some(vec![AuthenticatorTransport::Usb]),
@@ -1220,12 +1215,14 @@ where
#[cfg(test)]
mod test {
use super::command::AuthenticatorAttestationMaterial;
use super::client_pin::PIN_TOKEN_LENGTH;
use super::command::{AuthenticatorAttestationMaterial, AuthenticatorClientPinParameters};
use super::data_formats::{
CoseKey, GetAssertionHmacSecretInput, GetAssertionOptions, MakeCredentialExtensions,
MakeCredentialOptions, PinUvAuthProtocol, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, GetAssertionOptions,
MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
};
use super::pin_protocol::{authenticate_pin_uv_auth_token, PinProtocol};
use super::*;
use cbor::{cbor_array, cbor_array_vec, cbor_map};
use crypto::rng256::ThreadRng256;
@@ -1316,7 +1313,7 @@ mod test {
"alwaysUv" => false,
},
0x05 => MAX_MSG_SIZE as u64,
0x06 => cbor_array_vec![vec![1]],
0x06 => cbor_array_vec![vec![2, 1]],
0x07 => MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
0x08 => CREDENTIAL_ID_SIZE as u64,
0x09 => cbor_array_vec![vec!["usb"]],
@@ -1755,6 +1752,52 @@ mod test {
assert_eq!(stored_credential.large_blob_key.unwrap(), large_blob_key);
}
fn test_helper_process_make_credential_with_pin_and_uv(
pin_uv_auth_protocol: PinUvAuthProtocol,
) {
let mut rng = ThreadRng256 {};
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
ctap_state.client_pin = client_pin;
ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap();
let client_data_hash = [0xCD];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&client_data_hash,
pin_uv_auth_protocol,
);
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.options.uv = true;
make_credential_params.pin_uv_auth_param = Some(pin_uv_auth_param);
make_credential_params.pin_uv_auth_protocol = Some(pin_uv_auth_protocol);
let make_credential_response =
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
check_make_response(
make_credential_response,
0x45,
&ctap_state.persistent_store.aaguid().unwrap(),
0x20,
&[],
);
}
#[test]
fn test_process_make_credential_with_pin_and_uv_v1() {
test_helper_process_make_credential_with_pin_and_uv(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_make_credential_with_pin_and_uv_v2() {
test_helper_process_make_credential_with_pin_and_uv(PinUvAuthProtocol::V2);
}
#[test]
fn test_non_resident_process_make_credential_with_pin() {
let mut rng = ThreadRng256 {};
@@ -1810,7 +1853,7 @@ mod test {
ctap_state.persistent_store.set_pin(&[0x88; 16], 4).unwrap();
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.pin_uv_auth_param = Some(vec![0xA4; 16]);
make_credential_params.pin_uv_auth_protocol = Some(1);
make_credential_params.pin_uv_auth_protocol = Some(PinUvAuthProtocol::V1);
let make_credential_response =
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
assert_eq!(
@@ -1951,10 +1994,62 @@ mod test {
check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None);
}
#[test]
fn test_process_get_assertion_hmac_secret() {
fn get_assertion_hmac_secret_params(
key_agreement_key: crypto::ecdh::SecKey,
key_agreement_response: ResponseData,
credential_id: Option<Vec<u8>>,
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> AuthenticatorGetAssertionParameters {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let platform_public_key = key_agreement_key.genpk();
let public_key = match key_agreement_response {
ResponseData::AuthenticatorClientPin(Some(client_pin_response)) => {
client_pin_response.key_agreement.unwrap()
}
_ => panic!("Invalid response type"),
};
let pin_protocol = PinProtocol::new_test(key_agreement_key, [0x91; 32]);
let shared_secret = pin_protocol
.decapsulate(public_key, pin_uv_auth_protocol)
.unwrap();
let salt = vec![0x01; 32];
let salt_enc = shared_secret.as_ref().encrypt(&mut rng, &salt).unwrap();
let salt_auth = shared_secret.authenticate(&salt_enc);
let hmac_secret_input = GetAssertionHmacSecretInput {
key_agreement: CoseKey::from(platform_public_key),
salt_enc,
salt_auth,
pin_uv_auth_protocol,
};
let get_extensions = GetAssertionExtensions {
hmac_secret: Some(hmac_secret_input),
..Default::default()
};
let credential_descriptor = credential_id.map(|key_id| PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id,
transports: None,
});
let allow_list = credential_descriptor.map(|c| vec![c]);
AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list,
extensions: get_extensions,
options: GetAssertionOptions {
up: true,
uv: false,
},
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
}
}
fn test_helper_process_get_assertion_hmac_secret(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut rng = ThreadRng256 {};
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
@@ -1979,51 +2074,50 @@ mod test {
_ => panic!("Invalid response type"),
};
let pk = sk.genpk();
let hmac_secret_input = GetAssertionHmacSecretInput {
key_agreement: CoseKey::from(pk),
salt_enc: vec![0x02; 32],
salt_auth: vec![0x03; 16],
pin_uv_auth_protocol: PinUvAuthProtocol::V1,
};
let get_extensions = GetAssertionExtensions {
hmac_secret: Some(hmac_secret_input),
..Default::default()
};
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,
key_id: credential_id,
transports: None,
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: Some(vec![cred_desc]),
extensions: get_extensions,
options: GetAssertionOptions {
up: false,
uv: false,
},
let client_pin_params = AuthenticatorClientPinParameters {
pin_uv_auth_protocol,
sub_command: ClientPinSubCommand::GetKeyAgreement,
key_agreement: None,
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
new_pin_enc: None,
pin_hash_enc: None,
permissions: None,
permissions_rp_id: None,
};
let key_agreement_response = ctap_state.client_pin.process_command(
ctap_state.rng,
&mut ctap_state.persistent_store,
client_pin_params,
);
let get_assertion_params = get_assertion_hmac_secret_params(
key_agreement_key,
key_agreement_response.unwrap(),
Some(credential_id),
pin_uv_auth_protocol,
);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_OPTION)
);
assert!(get_assertion_response.is_ok());
}
#[test]
fn test_resident_process_get_assertion_hmac_secret() {
fn test_process_get_assertion_hmac_secret_v1() {
test_helper_process_get_assertion_hmac_secret(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_get_assertion_hmac_secret_v2() {
test_helper_process_get_assertion_hmac_secret(PinUvAuthProtocol::V2);
}
fn test_helper_resident_process_get_assertion_hmac_secret(
pin_uv_auth_protocol: PinUvAuthProtocol,
) {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
@@ -2037,40 +2131,43 @@ mod test {
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
.is_ok());
let pk = sk.genpk();
let hmac_secret_input = GetAssertionHmacSecretInput {
key_agreement: CoseKey::from(pk),
salt_enc: vec![0x02; 32],
salt_auth: vec![0x03; 16],
pin_uv_auth_protocol: PinUvAuthProtocol::V1,
};
let get_extensions = GetAssertionExtensions {
hmac_secret: Some(hmac_secret_input),
..Default::default()
};
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
allow_list: None,
extensions: get_extensions,
options: GetAssertionOptions {
up: false,
uv: false,
},
let client_pin_params = AuthenticatorClientPinParameters {
pin_uv_auth_protocol,
sub_command: ClientPinSubCommand::GetKeyAgreement,
key_agreement: None,
pin_uv_auth_param: None,
pin_uv_auth_protocol: None,
new_pin_enc: None,
pin_hash_enc: None,
permissions: None,
permissions_rp_id: None,
};
let key_agreement_response = ctap_state.client_pin.process_command(
ctap_state.rng,
&mut ctap_state.persistent_store,
client_pin_params,
);
let get_assertion_params = get_assertion_hmac_secret_params(
key_agreement_key,
key_agreement_response.unwrap(),
None,
pin_uv_auth_protocol,
);
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
DUMMY_CHANNEL_ID,
DUMMY_CLOCK_VALUE,
);
assert!(get_assertion_response.is_ok());
}
assert_eq!(
get_assertion_response,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_OPTION)
);
#[test]
fn test_process_resident_get_assertion_hmac_secret_v1() {
test_helper_resident_process_get_assertion_hmac_secret(PinUvAuthProtocol::V1);
}
#[test]
fn test_resident_process_get_assertion_hmac_secret_v2() {
test_helper_resident_process_get_assertion_hmac_secret(PinUvAuthProtocol::V2);
}
#[test]
@@ -2315,13 +2412,14 @@ mod test {
assert_eq!(large_blob_key, vec![0x1C; 32]);
}
#[test]
fn test_process_get_next_assertion_two_credentials_with_uv() {
fn test_helper_process_get_next_assertion_two_credentials_with_uv(
pin_uv_auth_protocol: PinUvAuthProtocol,
) {
let mut rng = ThreadRng256 {};
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
let pin_uv_auth_token = [0x88; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
@@ -2352,22 +2450,24 @@ mod test {
// The PIN length is outside of the test scope and most likely incorrect.
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x6F, 0x52, 0x83, 0xBF, 0x1A, 0x91, 0xEE, 0x67, 0xE9, 0xD4, 0x4C, 0x80, 0x08, 0x79,
0x90, 0x8D,
]);
let client_data_hash = vec![0xCD];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
&client_data_hash,
pin_uv_auth_protocol,
);
let get_assertion_params = AuthenticatorGetAssertionParameters {
rp_id: String::from("example.com"),
client_data_hash: vec![0xCD],
client_data_hash,
allow_list: None,
extensions: GetAssertionExtensions::default(),
options: GetAssertionOptions {
up: false,
uv: true,
},
pin_uv_auth_param,
pin_uv_auth_protocol: Some(1),
pin_uv_auth_param: Some(pin_uv_auth_param),
pin_uv_auth_protocol: Some(pin_uv_auth_protocol),
};
let get_assertion_response = ctap_state.process_get_assertion(
get_assertion_params,
@@ -2404,6 +2504,16 @@ mod test {
);
}
#[test]
fn test_process_get_next_assertion_two_credentials_with_uv_v1() {
test_helper_process_get_next_assertion_two_credentials_with_uv(PinUvAuthProtocol::V1);
}
#[test]
fn test_process_get_next_assertion_two_credentials_with_uv_v2() {
test_helper_process_get_next_assertion_two_credentials_with_uv(PinUvAuthProtocol::V2);
}
#[test]
fn test_process_get_next_assertion_three_credentials_no_uv() {
let mut rng = ThreadRng256 {};

View File

@@ -94,6 +94,19 @@ impl PinProtocol {
}
}
/// Authenticates the pinUvAuthToken for the given PIN protocol.
#[cfg(test)]
pub fn authenticate_pin_uv_auth_token(
token: &[u8; PIN_TOKEN_LENGTH],
message: &[u8],
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> Vec<u8> {
match pin_uv_auth_protocol {
PinUvAuthProtocol::V1 => hmac_256::<Sha256>(token, message)[..16].to_vec(),
PinUvAuthProtocol::V2 => hmac_256::<Sha256>(token, message).to_vec(),
}
}
/// Verifies the pinUvAuthToken for the given PIN protocol.
pub fn verify_pin_uv_auth_token(
token: &[u8; PIN_TOKEN_LENGTH],