adds permissions and adapts clientPin 2.1 subcommands

This commit is contained in:
Fabian Kaczmarczyck
2020-07-02 19:11:49 +02:00
parent 26595db810
commit 216a6a0f6e
2 changed files with 163 additions and 20 deletions

View File

@@ -413,6 +413,9 @@ where
{ {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
} }
#[cfg(feature = "with_ctap2_1")]
self.pin_protocol_v1
.has_make_credential_permission(&rp_id)?;
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
} }
None => { None => {
@@ -591,6 +594,8 @@ where
{ {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
} }
#[cfg(feature = "with_ctap2_1")]
self.pin_protocol_v1.has_get_assertion_permission(&rp_id)?;
UV_FLAG UV_FLAG
} }
None => { None => {

View File

@@ -146,6 +146,10 @@ pub struct PinProtocolV1 {
key_agreement_key: crypto::ecdh::SecKey, key_agreement_key: crypto::ecdh::SecKey,
pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH],
consecutive_pin_mismatches: u8, consecutive_pin_mismatches: u8,
#[cfg(feature = "with_ctap2_1")]
permissions: u8,
#[cfg(feature = "with_ctap2_1")]
permissions_rp_id: Option<String>,
} }
impl PinProtocolV1 { impl PinProtocolV1 {
@@ -156,6 +160,10 @@ impl PinProtocolV1 {
key_agreement_key, key_agreement_key,
pin_uv_auth_token, pin_uv_auth_token,
consecutive_pin_mismatches: 0, consecutive_pin_mismatches: 0,
#[cfg(feature = "with_ctap2_1")]
permissions: 0,
#[cfg(feature = "with_ctap2_1")]
permissions_rp_id: None,
} }
} }
@@ -312,6 +320,12 @@ impl PinProtocolV1 {
pin_token.extend(item); pin_token.extend(item);
} }
#[cfg(feature = "with_ctap2_1")]
{
self.permissions = 0x03;
self.permissions_rp_id = None;
}
Ok(AuthenticatorClientPinResponse { Ok(AuthenticatorClientPinResponse {
key_agreement: None, key_agreement: None,
pin_token: Some(pin_token), pin_token: Some(pin_token),
@@ -324,22 +338,28 @@ impl PinProtocolV1 {
&self, &self,
_: CoseKey, _: CoseKey,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> { ) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
Ok(AuthenticatorClientPinResponse {
// User verifications is only supported through PIN currently. // User verifications is only supported through PIN currently.
key_agreement: None, #[cfg(not(feature = "with_ctap2_1"))]
pin_token: Some(vec![]), {
retries: None, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
}) }
#[cfg(feature = "with_ctap2_1")]
{
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND)
}
} }
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
fn process_get_uv_retries(&self) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> { fn process_get_uv_retries(&self) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
// User verifications is only supported through PIN currently. // User verifications is only supported through PIN currently.
Ok(AuthenticatorClientPinResponse { #[cfg(not(feature = "with_ctap2_1"))]
key_agreement: None, {
pin_token: None, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
retries: Some(0), }
}) #[cfg(feature = "with_ctap2_1")]
{
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_SUBCOMMAND)
}
} }
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
@@ -395,20 +415,23 @@ impl PinProtocolV1 {
key_agreement: CoseKey, key_agreement: CoseKey,
pin_hash_enc: Vec<u8>, pin_hash_enc: Vec<u8>,
permissions: u8, permissions: u8,
_permissions_rp_id: Option<String>, permissions_rp_id: Option<String>,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> { ) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
if permissions == 0 { if permissions == 0 {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
} }
// This check is not mentioned protocol steps, but mentioned in a side note.
if permissions & 0x03 != 0 && permissions_rp_id.is_none() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
// TODO(kaczmarczyck) split the implementation to omit the unnecessary token generation let response =
self.process_get_pin_token(rng, persistent_store, key_agreement, pin_hash_enc)?; self.process_get_pin_token(rng, persistent_store, key_agreement, pin_hash_enc)?;
// TODO
Ok(AuthenticatorClientPinResponse { self.permissions = permissions;
key_agreement: None, self.permissions_rp_id = permissions_rp_id;
pin_token: None,
retries: None, Ok(response)
})
} }
pub fn process( pub fn process(
@@ -539,6 +562,34 @@ impl PinProtocolV1 {
None => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION), None => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
} }
} }
#[cfg(feature = "with_ctap2_1")]
pub fn has_make_credential_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
self.has_permission(0x01, rp_id)
}
#[cfg(feature = "with_ctap2_1")]
pub fn has_get_assertion_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
self.has_permission(0x02, rp_id)
}
// TODO(kaczmarczyck) use permissons for new commands
#[cfg(feature = "with_ctap2_1")]
fn has_permission(&mut self, bitmask: u8, rp_id: &str) -> Result<(), Ctap2StatusCode> {
if let Some(permissions_rp_id) = &self.permissions_rp_id {
if rp_id != permissions_rp_id {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
} else {
self.permissions_rp_id = Some(String::from(rp_id));
}
if self.permissions & bitmask != 0 {
Ok(())
} else {
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
}
}
} }
#[cfg(test)] #[cfg(test)]
@@ -788,6 +839,71 @@ mod test {
); );
} }
#[cfg(feature = "with_ctap2_1")]
#[test]
fn test_process_get_pin_uv_auth_token_using_pin_with_permissions() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
set_standard_pin(&mut persistent_store);
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
let pk = pin_protocol_v1.key_agreement_key.genpk();
let shared_secret = pin_protocol_v1.key_agreement_key.exchange_x_sha256(&pk);
let key_agreement = CoseKey::from(pk);
let pin_hash_enc = encrypt_standard_pin_hash(&shared_secret);
assert!(pin_protocol_v1
.process_get_pin_uv_auth_token_using_pin_with_permissions(
&mut rng,
&mut persistent_store,
key_agreement.clone(),
pin_hash_enc.clone(),
0x03,
Some(String::from("example.com")),
)
.is_ok());
assert_eq!(pin_protocol_v1.permissions, 0x03);
assert_eq!(
pin_protocol_v1.permissions_rp_id,
Some(String::from("example.com"))
);
assert_eq!(
pin_protocol_v1.process_get_pin_uv_auth_token_using_pin_with_permissions(
&mut rng,
&mut persistent_store,
key_agreement.clone(),
pin_hash_enc.clone(),
0x00,
Some(String::from("example.com")),
),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
assert_eq!(
pin_protocol_v1.process_get_pin_uv_auth_token_using_pin_with_permissions(
&mut rng,
&mut persistent_store,
key_agreement.clone(),
pin_hash_enc.clone(),
0x03,
None,
),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
let pin_hash_enc = vec![0xEE; 16];
assert_eq!(
pin_protocol_v1.process_get_pin_uv_auth_token_using_pin_with_permissions(
&mut rng,
&mut persistent_store,
key_agreement,
pin_hash_enc,
0x03,
Some(String::from("example.com")),
),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
);
}
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
#[test] #[test]
fn test_process_set_min_pin_length() { fn test_process_set_min_pin_length() {
@@ -962,4 +1078,26 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
); );
} }
#[cfg(feature = "with_ctap2_1")]
#[test]
fn test_has_permission() {
let mut rng = ThreadRng256 {};
let mut pin_protocol_v1 = PinProtocolV1::new(&mut rng);
pin_protocol_v1.permissions = 0x03;
assert_eq!(pin_protocol_v1.has_permission(0x01, "example.com"), Ok(()));
assert_eq!(
pin_protocol_v1.permissions_rp_id,
Some(String::from("example.com"))
);
assert_eq!(pin_protocol_v1.has_permission(0x01, "example.com"), Ok(()));
assert_eq!(
pin_protocol_v1.has_permission(0x01, "counter-example.com"),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
assert_eq!(
pin_protocol_v1.has_permission(0x04, "example.com"),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
}
} }