From 63aef3bd7645cd4b7eea9a6e2f6b342f20367c2c Mon Sep 17 00:00:00 2001 From: Fabian Kaczmarczyck Date: Thu, 18 Jun 2020 16:10:08 +0200 Subject: [PATCH] new client pin subcommands --- src/ctap/command.rs | 73 +++++++++++++++++++++++++++++++++++ src/ctap/data_formats.rs | 20 ++++++++-- src/ctap/mod.rs | 83 ++++++++++++++++++++++++++++++++++------ 3 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/ctap/command.rs b/src/ctap/command.rs index ef1e6ed..9b966fe 100644 --- a/src/ctap/command.rs +++ b/src/ctap/command.rs @@ -278,6 +278,14 @@ pub struct AuthenticatorClientPinParameters { pub pin_auth: Option>, pub new_pin_enc: Option>, pub pin_hash_enc: Option>, + #[cfg(feature = "with_ctap2_1")] + pub min_pin_length: Option, + #[cfg(feature = "with_ctap2_1")] + pub min_pin_length_rp_ids: Option>, + #[cfg(feature = "with_ctap2_1")] + pub permissions: Option, + #[cfg(feature = "with_ctap2_1")] + pub permissions_rp_id: Option, } impl TryFrom for AuthenticatorClientPinParameters { @@ -305,6 +313,39 @@ impl TryFrom for AuthenticatorClientPinParameters { let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?; let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?; + // TODO(kaczmarczyck) merge with new map destructuring (and use hex!) + #[cfg(feature = "with_ctap2_1")] + let min_pin_length = param_map + .remove(&cbor_unsigned!(7)) + .map(extract_unsigned) + .transpose()?; + + #[cfg(feature = "with_ctap2_1")] + let min_pin_length_rp_ids = match param_map.remove(&cbor_unsigned!(8)) { + Some(entry) => Some( + extract_array(entry)? + .into_iter() + .map(extract_text_string) + .collect::, Ctap2StatusCode>>()?, + ), + None => None, + }; + + #[cfg(feature = "with_ctap2_1")] + // We expect a bit field of 8 bits, and drop everything else. + // This means we ignore extensions in future versions. + let permissions = param_map + .remove(&cbor_unsigned!(9)) + .map(extract_unsigned) + .transpose()? + .map(|p| p as u8); + + #[cfg(feature = "with_ctap2_1")] + let permissions_rp_id = param_map + .remove(&cbor_unsigned!(10)) + .map(extract_text_string) + .transpose()?; + Ok(AuthenticatorClientPinParameters { pin_protocol, sub_command, @@ -312,6 +353,14 @@ impl TryFrom for AuthenticatorClientPinParameters { pin_auth, new_pin_enc, pin_hash_enc, + #[cfg(feature = "with_ctap2_1")] + min_pin_length, + #[cfg(feature = "with_ctap2_1")] + min_pin_length_rp_ids, + #[cfg(feature = "with_ctap2_1")] + permissions, + #[cfg(feature = "with_ctap2_1")] + permissions_rp_id, }) } } @@ -434,6 +483,9 @@ mod test { #[test] fn test_from_cbor_client_pin_parameters() { + // TODO(kaczmarczyck) inline the #cfg when the ICE is resolved: + // https://github.com/rust-lang/rust/issues/73663 + #[cfg(not(feature = "with_ctap2_1"))] let cbor_value = cbor_map! { 1 => 1, 2 => ClientPinSubCommand::GetPinRetries, @@ -442,6 +494,19 @@ mod test { 5 => vec! [0xCC], 6 => vec! [0xDD], }; + #[cfg(feature = "with_ctap2_1")] + let cbor_value = cbor_map! { + 1 => 1, + 2 => ClientPinSubCommand::GetPinRetries, + 3 => cbor_map!{}, + 4 => vec! [0xBB], + 5 => vec! [0xCC], + 6 => vec! [0xDD], + 7 => 4, + 8 => cbor_array!["example.com"], + 9 => 0x03, + 10 => "example.com", + }; let returned_pin_protocol_parameters = AuthenticatorClientPinParameters::try_from(cbor_value).unwrap(); @@ -452,6 +517,14 @@ mod test { pin_auth: Some(vec![0xBB]), new_pin_enc: Some(vec![0xCC]), pin_hash_enc: Some(vec![0xDD]), + #[cfg(feature = "with_ctap2_1")] + min_pin_length: Some(4), + #[cfg(feature = "with_ctap2_1")] + min_pin_length_rp_ids: Some(vec!["example.com".to_string()]), + #[cfg(feature = "with_ctap2_1")] + permissions: Some(0x03), + #[cfg(feature = "with_ctap2_1")] + permissions_rp_id: Some("example.com".to_string()), }; assert_eq!( diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs index ed54389..fdff6e1 100644 --- a/src/ctap/data_formats.rs +++ b/src/ctap/data_formats.rs @@ -690,9 +690,15 @@ pub enum ClientPinSubCommand { GetKeyAgreement = 0x02, SetPin = 0x03, ChangePin = 0x04, - GetPinUvAuthTokenUsingPin = 0x05, - GetPinUvAuthTokenUsingUv = 0x06, + GetPinToken = 0x05, + #[cfg(feature = "with_ctap2_1")] + GetPinUvAuthTokenUsingUvWithPermissions = 0x06, + #[cfg(feature = "with_ctap2_1")] GetUvRetries = 0x07, + #[cfg(feature = "with_ctap2_1")] + SetMinPinLength = 0x08, + #[cfg(feature = "with_ctap2_1")] + GetPinUvAuthTokenUsingPinWithPermissions = 0x09, } impl From for cbor::Value { @@ -712,10 +718,16 @@ impl TryFrom for ClientPinSubCommand { 0x02 => Ok(GetKeyAgreement), 0x03 => Ok(SetPin), 0x04 => Ok(ChangePin), - 0x05 => Ok(GetPinUvAuthTokenUsingPin), - 0x06 => Ok(GetPinUvAuthTokenUsingUv), + 0x05 => Ok(GetPinToken), + #[cfg(feature = "with_ctap2_1")] + 0x06 => Ok(GetPinUvAuthTokenUsingUvWithPermissions), + #[cfg(feature = "with_ctap2_1")] 0x07 => Ok(GetUvRetries), #[cfg(feature = "with_ctap2_1")] + 0x08 => Ok(SetMinPinLength), + #[cfg(feature = "with_ctap2_1")] + 0x09 => Ok(GetPinUvAuthTokenUsingPinWithPermissions), + #[cfg(feature = "with_ctap2_1")] _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND), #[cfg(not(feature = "with_ctap2_1"))] _ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER), diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 027e91f..e5bb6f2 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -849,6 +849,7 @@ where } } if pin.len() < 4 || pin.len() == PIN_PADDED_LENGTH { + // TODO(kaczmarczyck) check 4 code point minimum instead return false; } let mut pin_hash = [0; 16]; @@ -973,7 +974,7 @@ where Ok(()) } - fn process_get_pin_uv_auth_token_using_pin( + fn process_get_pin_token( &mut self, key_agreement: CoseKey, pin_hash_enc: Vec, @@ -1007,7 +1008,8 @@ where }) } - fn process_get_pin_uv_auth_token_using_uv( + #[cfg(feature = "with_ctap2_1")] + fn process_get_pin_uv_auth_token_using_uv_with_permissions( &self, _: CoseKey, ) -> Result { @@ -1019,6 +1021,7 @@ where }) } + #[cfg(feature = "with_ctap2_1")] fn process_get_uv_retries(&self) -> Result { // User verifications is only supported through PIN currently. Ok(AuthenticatorClientPinResponse { @@ -1028,6 +1031,37 @@ where }) } + #[cfg(feature = "with_ctap2_1")] + fn process_set_min_pin_length( + &mut self, + _min_pin_length: u64, + _min_pin_length_rp_ids: Vec, + _pin_auth: Vec, + ) -> Result { + // TODO + Ok(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: None, + retries: Some(0), + }) + } + + #[cfg(feature = "with_ctap2_1")] + fn process_get_pin_uv_auth_token_using_pin_with_permissions( + &mut self, + _key_agreement: CoseKey, + _pin_hash_enc: Vec, + _permissions: u8, + _permissions_rp_id: String, + ) -> Result { + // TODO + Ok(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: None, + retries: Some(0), + }) + } + fn process_client_pin( &mut self, client_pin_params: AuthenticatorClientPinParameters, @@ -1039,6 +1073,14 @@ where pin_auth, new_pin_enc, pin_hash_enc, + #[cfg(feature = "with_ctap2_1")] + min_pin_length, + #[cfg(feature = "with_ctap2_1")] + min_pin_length_rp_ids, + #[cfg(feature = "with_ctap2_1")] + permissions, + #[cfg(feature = "with_ctap2_1")] + permissions_rp_id, } = client_pin_params; if pin_protocol != 1 { @@ -1065,18 +1107,37 @@ where )?; None } - ClientPinSubCommand::GetPinUvAuthTokenUsingPin => { - Some(self.process_get_pin_uv_auth_token_using_pin( + ClientPinSubCommand::GetPinToken => Some(self.process_get_pin_token( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?), + #[cfg(feature = "with_ctap2_1")] + ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some( + self.process_get_pin_uv_auth_token_using_uv_with_permissions( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?, + ), + #[cfg(feature = "with_ctap2_1")] + ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries()?), + #[cfg(feature = "with_ctap2_1")] + ClientPinSubCommand::SetMinPinLength => { + self.process_set_min_pin_length( + min_pin_length.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + min_pin_length_rp_ids.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?; + None + } + #[cfg(feature = "with_ctap2_1")] + ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => { + self.process_get_pin_uv_auth_token_using_pin_with_permissions( key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, - )?) + permissions.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + permissions_rp_id.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?; + None } - ClientPinSubCommand::GetPinUvAuthTokenUsingUv => { - Some(self.process_get_pin_uv_auth_token_using_uv( - key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, - )?) - } - ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries()?), }; Ok(ResponseData::AuthenticatorClientPin(response)) }