diff --git a/src/ctap/client_pin.rs b/src/ctap/client_pin.rs index 12e049c..a580b1b 100644 --- a/src/ctap/client_pin.rs +++ b/src/ctap/client_pin.rs @@ -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 {}; diff --git a/src/ctap/command.rs b/src/ctap/command.rs index 748c33e..c0ecb2d 100644 --- a/src/ctap/command.rs +++ b/src/ctap/command.rs @@ -155,7 +155,7 @@ pub struct AuthenticatorMakeCredentialParameters { // Same for options, use defaults when not present. pub options: MakeCredentialOptions, pub pin_uv_auth_param: Option>, - pub pin_uv_auth_protocol: Option, + pub pin_uv_auth_protocol: Option, pub enterprise_attestation: Option, } @@ -213,7 +213,9 @@ impl TryFrom 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>, - pub pin_uv_auth_protocol: Option, + pub pin_uv_auth_protocol: Option, } impl TryFrom for AuthenticatorGetAssertionParameters { @@ -288,7 +290,9 @@ impl TryFrom 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, pub pin_uv_auth_param: Option>, - pub pin_uv_auth_protocol: Option, + pub pin_uv_auth_protocol: Option, } impl TryFrom for AuthenticatorLargeBlobsParameters { @@ -394,7 +398,9 @@ impl TryFrom 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, pub pin_uv_auth_param: Option>, - pub pin_uv_auth_protocol: Option, + pub pin_uv_auth_protocol: Option, } impl TryFrom for AuthenticatorConfigParameters { @@ -463,7 +469,9 @@ impl TryFrom 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 for AuthenticatorAttestationMaterial { pub struct AuthenticatorCredentialManagementParameters { pub sub_command: CredentialManagementSubCommand, pub sub_command_params: Option, - pub pin_uv_auth_protocol: Option, - pub pin_auth: Option>, + pub pin_uv_auth_protocol: Option, + pub pin_uv_auth_param: Option>, } impl TryFrom for AuthenticatorCredentialManagementParameters { @@ -520,7 +528,7 @@ impl TryFrom 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 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, diff --git a/src/ctap/config_command.rs b/src/ctap/config_command.rs index 3cbefd5..bdedc6b 100644 --- a/src/ctap/config_command.rs +++ b/src/ctap/config_command.rs @@ -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>, @@ -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)); diff --git a/src/ctap/credential_management.rs b/src/ctap/credential_management.rs index bbfb320..b81648a 100644 --- a/src/ctap/credential_management.rs +++ b/src/ctap/credential_management.rs @@ -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, diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index 138dd3a..84fe721 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -816,8 +816,8 @@ impl TryFrom for ecdh::PubKey { #[derive(Clone, Copy, Debug, PartialEq)] pub enum PinUvAuthProtocol { - V1, - V2, + V1 = 1, + V2 = 2, } impl TryFrom for PinUvAuthProtocol { diff --git a/src/ctap/large_blobs.rs b/src/ctap/large_blobs.rs index d366ce4..ffb98cb 100644 --- a/src/ctap/large_blobs.rs +++ b/src/ctap/large_blobs.rs @@ -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); + } } diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 8432f4e..83db5a9 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -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, -) -> 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>, - pin_uv_auth_protocol: Option, + pin_uv_auth_protocol: Option, 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>, + 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 {}; diff --git a/src/ctap/pin_protocol.rs b/src/ctap/pin_protocol.rs index 912f7aa..44ae53d 100644 --- a/src/ctap/pin_protocol.rs +++ b/src/ctap/pin_protocol.rs @@ -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 { + match pin_uv_auth_protocol { + PinUvAuthProtocol::V1 => hmac_256::(token, message)[..16].to_vec(), + PinUvAuthProtocol::V2 => hmac_256::(token, message).to_vec(), + } +} + /// Verifies the pinUvAuthToken for the given PIN protocol. pub fn verify_pin_uv_auth_token( token: &[u8; PIN_TOKEN_LENGTH],