adds permissions and adapts clientPin 2.1 subcommands
This commit is contained in:
@@ -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 => {
|
||||||
|
|||||||
@@ -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.
|
#[cfg(not(feature = "with_ctap2_1"))]
|
||||||
key_agreement: None,
|
{
|
||||||
pin_token: Some(vec![]),
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND)
|
||||||
retries: None,
|
}
|
||||||
})
|
#[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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user