adds PIN token state with timeouts (#296)
This commit is contained in:
@@ -20,6 +20,7 @@ use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret};
|
|||||||
use super::response::{AuthenticatorClientPinResponse, ResponseData};
|
use super::response::{AuthenticatorClientPinResponse, ResponseData};
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::PersistentStore;
|
use super::storage::PersistentStore;
|
||||||
|
use super::token_state::PinUvAuthTokenState;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::str;
|
use alloc::str;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
@@ -30,6 +31,7 @@ use crypto::sha256::Sha256;
|
|||||||
use crypto::Hash256;
|
use crypto::Hash256;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use enum_iterator::IntoEnumIterator;
|
use enum_iterator::IntoEnumIterator;
|
||||||
|
use libtock_drivers::timer::ClockValue;
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
/// The prefix length of the PIN hash that is stored and compared.
|
/// The prefix length of the PIN hash that is stored and compared.
|
||||||
@@ -104,8 +106,7 @@ pub struct ClientPin {
|
|||||||
pin_protocol_v1: PinProtocol,
|
pin_protocol_v1: PinProtocol,
|
||||||
pin_protocol_v2: PinProtocol,
|
pin_protocol_v2: PinProtocol,
|
||||||
consecutive_pin_mismatches: u8,
|
consecutive_pin_mismatches: u8,
|
||||||
permissions: u8,
|
pin_uv_auth_token_state: PinUvAuthTokenState,
|
||||||
permissions_rp_id: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientPin {
|
impl ClientPin {
|
||||||
@@ -114,8 +115,7 @@ impl ClientPin {
|
|||||||
pin_protocol_v1: PinProtocol::new(rng),
|
pin_protocol_v1: PinProtocol::new(rng),
|
||||||
pin_protocol_v2: PinProtocol::new(rng),
|
pin_protocol_v2: PinProtocol::new(rng),
|
||||||
consecutive_pin_mismatches: 0,
|
consecutive_pin_mismatches: 0,
|
||||||
permissions: 0,
|
pin_uv_auth_token_state: PinUvAuthTokenState::new(),
|
||||||
permissions_rp_id: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +290,7 @@ impl ClientPin {
|
|||||||
rng: &mut impl Rng256,
|
rng: &mut impl Rng256,
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
let AuthenticatorClientPinParameters {
|
let AuthenticatorClientPinParameters {
|
||||||
pin_uv_auth_protocol,
|
pin_uv_auth_protocol,
|
||||||
@@ -314,14 +315,17 @@ impl ClientPin {
|
|||||||
if persistent_store.has_force_pin_change()? {
|
if persistent_store.has_force_pin_change()? {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pin_token = shared_secret.encrypt(
|
let pin_token = shared_secret.encrypt(
|
||||||
rng,
|
rng,
|
||||||
self.get_pin_protocol(pin_uv_auth_protocol)
|
self.get_pin_protocol(pin_uv_auth_protocol)
|
||||||
.get_pin_uv_auth_token(),
|
.get_pin_uv_auth_token(),
|
||||||
)?;
|
)?;
|
||||||
self.permissions = 0x03;
|
|
||||||
self.permissions_rp_id = None;
|
self.pin_protocol_v1.reset_pin_uv_auth_token(rng);
|
||||||
|
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
|
||||||
|
self.pin_uv_auth_token_state
|
||||||
|
.begin_using_pin_uv_auth_token(now);
|
||||||
|
self.pin_uv_auth_token_state.set_default_permissions();
|
||||||
|
|
||||||
Ok(AuthenticatorClientPinResponse {
|
Ok(AuthenticatorClientPinResponse {
|
||||||
key_agreement: None,
|
key_agreement: None,
|
||||||
@@ -350,6 +354,7 @@ impl ClientPin {
|
|||||||
rng: &mut impl Rng256,
|
rng: &mut impl Rng256,
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
mut client_pin_params: AuthenticatorClientPinParameters,
|
mut client_pin_params: AuthenticatorClientPinParameters,
|
||||||
|
now: ClockValue,
|
||||||
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
|
||||||
let permissions = ok_or_missing(client_pin_params.permissions)?;
|
let permissions = ok_or_missing(client_pin_params.permissions)?;
|
||||||
// Mutating client_pin_params is just an optimization to move it into
|
// Mutating client_pin_params is just an optimization to move it into
|
||||||
@@ -364,10 +369,10 @@ impl ClientPin {
|
|||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self.process_get_pin_token(rng, persistent_store, client_pin_params)?;
|
let response = self.process_get_pin_token(rng, persistent_store, client_pin_params, now)?;
|
||||||
|
self.pin_uv_auth_token_state.set_permissions(permissions);
|
||||||
self.permissions = permissions;
|
self.pin_uv_auth_token_state
|
||||||
self.permissions_rp_id = permissions_rp_id;
|
.set_permissions_rp_id(permissions_rp_id);
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
@@ -378,6 +383,7 @@ impl ClientPin {
|
|||||||
rng: &mut impl Rng256,
|
rng: &mut impl Rng256,
|
||||||
persistent_store: &mut PersistentStore,
|
persistent_store: &mut PersistentStore,
|
||||||
client_pin_params: AuthenticatorClientPinParameters,
|
client_pin_params: AuthenticatorClientPinParameters,
|
||||||
|
now: ClockValue,
|
||||||
) -> Result<ResponseData, Ctap2StatusCode> {
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
let response = match client_pin_params.sub_command {
|
let response = match client_pin_params.sub_command {
|
||||||
ClientPinSubCommand::GetPinRetries => {
|
ClientPinSubCommand::GetPinRetries => {
|
||||||
@@ -395,7 +401,7 @@ impl ClientPin {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
ClientPinSubCommand::GetPinToken => {
|
ClientPinSubCommand::GetPinToken => {
|
||||||
Some(self.process_get_pin_token(rng, persistent_store, client_pin_params)?)
|
Some(self.process_get_pin_token(rng, persistent_store, client_pin_params, now)?)
|
||||||
}
|
}
|
||||||
ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some(
|
ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions => Some(
|
||||||
self.process_get_pin_uv_auth_token_using_uv_with_permissions(client_pin_params)?,
|
self.process_get_pin_uv_auth_token_using_uv_with_permissions(client_pin_params)?,
|
||||||
@@ -406,6 +412,7 @@ impl ClientPin {
|
|||||||
rng,
|
rng,
|
||||||
persistent_store,
|
persistent_store,
|
||||||
client_pin_params,
|
client_pin_params,
|
||||||
|
now,
|
||||||
)?,
|
)?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -419,6 +426,9 @@ impl ClientPin {
|
|||||||
pin_uv_auth_param: &[u8],
|
pin_uv_auth_param: &[u8],
|
||||||
pin_uv_auth_protocol: PinUvAuthProtocol,
|
pin_uv_auth_protocol: PinUvAuthProtocol,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
if !self.pin_uv_auth_token_state.is_in_use() {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
|
}
|
||||||
verify_pin_uv_auth_token(
|
verify_pin_uv_auth_token(
|
||||||
self.get_pin_protocol(pin_uv_auth_protocol)
|
self.get_pin_protocol(pin_uv_auth_protocol)
|
||||||
.get_pin_uv_auth_token(),
|
.get_pin_uv_auth_token(),
|
||||||
@@ -435,8 +445,7 @@ impl ClientPin {
|
|||||||
self.pin_protocol_v2.regenerate(rng);
|
self.pin_protocol_v2.regenerate(rng);
|
||||||
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
|
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
|
||||||
self.consecutive_pin_mismatches = 0;
|
self.consecutive_pin_mismatches = 0;
|
||||||
self.permissions = 0;
|
self.pin_uv_auth_token_state.stop_using_pin_uv_auth_token();
|
||||||
self.permissions_rp_id = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies, computes and encrypts the HMAC-secret outputs.
|
/// Verifies, computes and encrypts the HMAC-secret outputs.
|
||||||
@@ -476,30 +485,43 @@ impl ClientPin {
|
|||||||
shared_secret.encrypt(rng, &output)
|
shared_secret.encrypt(rng, &output)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the required command's token permission is granted.
|
/// Consumes flags and permissions related to the pinUvAuthToken.
|
||||||
pub fn has_permission(&self, permission: PinPermission) -> Result<(), Ctap2StatusCode> {
|
pub fn clear_token_flags(&mut self) {
|
||||||
// Relies on the fact that all permissions are represented by powers of two.
|
self.pin_uv_auth_token_state.clear_user_verified_flag();
|
||||||
if permission as u8 & self.permissions != 0 {
|
self.pin_uv_auth_token_state
|
||||||
|
.clear_pin_uv_auth_token_permissions_except_lbw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the running timers, triggers timeout events.
|
||||||
|
pub fn update_timeouts(&mut self, now: ClockValue) {
|
||||||
|
self.pin_uv_auth_token_state
|
||||||
|
.pin_uv_auth_token_usage_timer_observer(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if user verification is cached for use of the pinUvAuthToken.
|
||||||
|
pub fn check_user_verified_flag(&mut self) -> Result<(), Ctap2StatusCode> {
|
||||||
|
if self.pin_uv_auth_token_state.get_user_verified_flag_value() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the required command's token permission is granted.
|
||||||
|
pub fn has_permission(&self, permission: PinPermission) -> Result<(), Ctap2StatusCode> {
|
||||||
|
self.pin_uv_auth_token_state.has_permission(permission)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if no RP ID is associated with the token permission.
|
/// Check if no RP ID is associated with the token permission.
|
||||||
pub fn has_no_rp_id_permission(&self) -> Result<(), Ctap2StatusCode> {
|
pub fn has_no_rp_id_permission(&self) -> Result<(), Ctap2StatusCode> {
|
||||||
if self.permissions_rp_id.is_some() {
|
self.pin_uv_auth_token_state.has_no_permissions_rp_id()
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if no or the passed RP ID is associated with the token permission.
|
/// Check if no or the passed RP ID is associated with the token permission.
|
||||||
pub fn has_no_or_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
pub fn has_no_or_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
||||||
match &self.permissions_rp_id {
|
self.pin_uv_auth_token_state
|
||||||
Some(p) if rp_id != p => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
.has_no_permissions_rp_id()
|
||||||
_ => Ok(()),
|
.or_else(|_| self.pin_uv_auth_token_state.has_permissions_rp_id(rp_id))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if no RP ID is associated with the token permission, or it matches the hash.
|
/// Check if no RP ID is associated with the token permission, or it matches the hash.
|
||||||
@@ -507,26 +529,28 @@ impl ClientPin {
|
|||||||
&self,
|
&self,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
match &self.permissions_rp_id {
|
self.pin_uv_auth_token_state
|
||||||
Some(p) if rp_id_hash != Sha256::hash(p.as_bytes()) => {
|
.has_no_permissions_rp_id()
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
.or_else(|_| {
|
||||||
}
|
self.pin_uv_auth_token_state
|
||||||
_ => Ok(()),
|
.has_permissions_rp_id_hash(rp_id_hash)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the passed RP ID is associated with the token permission.
|
/// Check if the passed RP ID is associated with the token permission.
|
||||||
///
|
///
|
||||||
/// If no RP ID is associated, associate the passed RP ID as a side effect.
|
/// If no RP ID is associated, associate the passed RP ID as a side effect.
|
||||||
pub fn ensure_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
pub fn ensure_rp_id_permission(&mut self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
||||||
match &self.permissions_rp_id {
|
if self
|
||||||
Some(p) if rp_id != p => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
.pin_uv_auth_token_state
|
||||||
None => {
|
.has_no_permissions_rp_id()
|
||||||
self.permissions_rp_id = Some(String::from(rp_id));
|
.is_ok()
|
||||||
Ok(())
|
{
|
||||||
}
|
self.pin_uv_auth_token_state
|
||||||
_ => Ok(()),
|
.set_permissions_rp_id(Some(String::from(rp_id)));
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
self.pin_uv_auth_token_state.has_permissions_rp_id(rp_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -541,12 +565,16 @@ impl ClientPin {
|
|||||||
PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(&mut rng)),
|
PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(&mut rng)),
|
||||||
PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(&mut rng), key_agreement_key),
|
PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(&mut rng), key_agreement_key),
|
||||||
};
|
};
|
||||||
|
let mut pin_uv_auth_token_state = PinUvAuthTokenState::new();
|
||||||
|
pin_uv_auth_token_state.set_permissions(0xFF);
|
||||||
|
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||||
|
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
||||||
|
pin_uv_auth_token_state.begin_using_pin_uv_auth_token(DUMMY_CLOCK_VALUE);
|
||||||
ClientPin {
|
ClientPin {
|
||||||
pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token),
|
pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token),
|
||||||
pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token),
|
pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token),
|
||||||
consecutive_pin_mismatches: 0,
|
consecutive_pin_mismatches: 0,
|
||||||
permissions: 0xFF,
|
pin_uv_auth_token_state,
|
||||||
permissions_rp_id: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,6 +585,10 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
use libtock_drivers::timer::Duration;
|
||||||
|
|
||||||
|
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||||
|
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
||||||
|
|
||||||
/// Stores a PIN hash corresponding to the dummy PIN "1234".
|
/// Stores a PIN hash corresponding to the dummy PIN "1234".
|
||||||
fn set_standard_pin(persistent_store: &mut PersistentStore) {
|
fn set_standard_pin(persistent_store: &mut PersistentStore) {
|
||||||
@@ -782,7 +814,7 @@ mod test {
|
|||||||
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
retries: Some(persistent_store.pin_retries().unwrap() as u64),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -810,7 +842,7 @@ mod test {
|
|||||||
retries: None,
|
retries: None,
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
Ok(ResponseData::AuthenticatorClientPin(expected_response))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -831,7 +863,7 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Ok(ResponseData::AuthenticatorClientPin(None))
|
Ok(ResponseData::AuthenticatorClientPin(None))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -865,14 +897,24 @@ mod test {
|
|||||||
let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data);
|
let pin_uv_auth_param = shared_secret.authenticate(&auth_param_data);
|
||||||
params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params.clone()),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
params.clone(),
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Ok(ResponseData::AuthenticatorClientPin(None))
|
Ok(ResponseData::AuthenticatorClientPin(None))
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, bad_params),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
bad_params,
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -880,7 +922,7 @@ mod test {
|
|||||||
persistent_store.decr_pin_retries().unwrap();
|
persistent_store.decr_pin_retries().unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -905,13 +947,41 @@ mod test {
|
|||||||
set_standard_pin(&mut persistent_store);
|
set_standard_pin(&mut persistent_store);
|
||||||
|
|
||||||
assert!(client_pin
|
assert!(client_pin
|
||||||
.process_command(&mut rng, &mut persistent_store, params.clone())
|
.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
params.clone(),
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::MakeCredential),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::GetAssertion),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_no_permissions_rp_id(),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
|
||||||
let mut bad_params = params;
|
let mut bad_params = params;
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, bad_params),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
bad_params,
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -937,7 +1007,7 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -964,32 +1034,65 @@ mod test {
|
|||||||
set_standard_pin(&mut persistent_store);
|
set_standard_pin(&mut persistent_store);
|
||||||
|
|
||||||
assert!(client_pin
|
assert!(client_pin
|
||||||
.process_command(&mut rng, &mut persistent_store, params.clone())
|
.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
params.clone(),
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert_eq!(client_pin.permissions, 0x03);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.permissions_rp_id,
|
client_pin
|
||||||
Some(String::from("example.com"))
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::MakeCredential),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::GetAssertion),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permissions_rp_id("example.com"),
|
||||||
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.permissions = Some(0x00);
|
bad_params.permissions = Some(0x00);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, bad_params),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
bad_params,
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params.clone();
|
let mut bad_params = params.clone();
|
||||||
bad_params.permissions_rp_id = None;
|
bad_params.permissions_rp_id = None;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, bad_params),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
bad_params,
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bad_params = params;
|
let mut bad_params = params;
|
||||||
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
bad_params.pin_hash_enc = Some(vec![0xEE; 16]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, bad_params),
|
client_pin.process_command(
|
||||||
|
&mut rng,
|
||||||
|
&mut persistent_store,
|
||||||
|
bad_params,
|
||||||
|
DUMMY_CLOCK_VALUE
|
||||||
|
),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1017,7 +1120,7 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
assert_eq!(persistent_store.force_pin_change(), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.process_command(&mut rng, &mut persistent_store, params),
|
client_pin.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1275,14 +1378,21 @@ mod test {
|
|||||||
fn test_has_permission() {
|
fn test_has_permission() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut client_pin = ClientPin::new(&mut rng);
|
let mut client_pin = ClientPin::new(&mut rng);
|
||||||
client_pin.permissions = 0x7F;
|
client_pin.pin_uv_auth_token_state.set_permissions(0x7F);
|
||||||
for permission in PinPermission::into_enum_iter() {
|
|
||||||
assert_eq!(client_pin.has_permission(permission), Ok(()));
|
|
||||||
}
|
|
||||||
client_pin.permissions = 0x00;
|
|
||||||
for permission in PinPermission::into_enum_iter() {
|
for permission in PinPermission::into_enum_iter() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.has_permission(permission),
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(permission),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
client_pin.pin_uv_auth_token_state.set_permissions(0x00);
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(permission),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1293,8 +1403,9 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut client_pin = ClientPin::new(&mut rng);
|
let mut client_pin = ClientPin::new(&mut rng);
|
||||||
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
|
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
|
||||||
assert_eq!(client_pin.permissions_rp_id, None);
|
client_pin
|
||||||
client_pin.permissions_rp_id = Some("example.com".to_string());
|
.pin_uv_auth_token_state
|
||||||
|
.set_permissions_rp_id(Some("example.com".to_string()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.has_no_rp_id_permission(),
|
client_pin.has_no_rp_id_permission(),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
@@ -1306,8 +1417,9 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut client_pin = ClientPin::new(&mut rng);
|
let mut client_pin = ClientPin::new(&mut rng);
|
||||||
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
|
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
|
||||||
assert_eq!(client_pin.permissions_rp_id, None);
|
client_pin
|
||||||
client_pin.permissions_rp_id = Some("example.com".to_string());
|
.pin_uv_auth_token_state
|
||||||
|
.set_permissions_rp_id(Some("example.com".to_string()));
|
||||||
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
|
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.has_no_or_rp_id_permission("another.example.com"),
|
client_pin.has_no_or_rp_id_permission("another.example.com"),
|
||||||
@@ -1324,8 +1436,9 @@ mod test {
|
|||||||
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
assert_eq!(client_pin.permissions_rp_id, None);
|
client_pin
|
||||||
client_pin.permissions_rp_id = Some("example.com".to_string());
|
.pin_uv_auth_token_state
|
||||||
|
.set_permissions_rp_id(Some("example.com".to_string()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1342,12 +1455,14 @@ mod test {
|
|||||||
let mut client_pin = ClientPin::new(&mut rng);
|
let mut client_pin = ClientPin::new(&mut rng);
|
||||||
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
|
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.permissions_rp_id,
|
client_pin
|
||||||
Some(String::from("example.com"))
|
.pin_uv_auth_token_state
|
||||||
|
.has_permissions_rp_id("example.com"),
|
||||||
|
Ok(())
|
||||||
);
|
);
|
||||||
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
|
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_pin.ensure_rp_id_permission("counter-example.com"),
|
client_pin.ensure_rp_id_permission("another.example.com"),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1355,8 +1470,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_verify_pin_uv_auth_token() {
|
fn test_verify_pin_uv_auth_token() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let client_pin = ClientPin::new(&mut rng);
|
let mut client_pin = ClientPin::new(&mut rng);
|
||||||
let message = [0xAA];
|
let message = [0xAA];
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.begin_using_pin_uv_auth_token(DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
let pin_uv_auth_token_v1 = client_pin
|
let pin_uv_auth_token_v1 = client_pin
|
||||||
.get_pin_protocol(PinUvAuthProtocol::V1)
|
.get_pin_protocol(PinUvAuthProtocol::V1)
|
||||||
@@ -1423,6 +1541,28 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_pin_uv_auth_token_not_in_use() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client_pin.verify_pin_uv_auth_token(
|
||||||
|
&message,
|
||||||
|
&pin_uv_auth_param_v1,
|
||||||
|
PinUvAuthProtocol::V1
|
||||||
|
),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reset() {
|
fn test_reset() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1431,8 +1571,10 @@ mod test {
|
|||||||
let public_key_v2 = client_pin.pin_protocol_v2.get_public_key();
|
let public_key_v2 = client_pin.pin_protocol_v2.get_public_key();
|
||||||
let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token();
|
let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token();
|
||||||
let token_v2 = *client_pin.pin_protocol_v2.get_pin_uv_auth_token();
|
let token_v2 = *client_pin.pin_protocol_v2.get_pin_uv_auth_token();
|
||||||
client_pin.permissions = 0xFF;
|
client_pin.pin_uv_auth_token_state.set_permissions(0xFF);
|
||||||
client_pin.permissions_rp_id = Some(String::from("example.com"));
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.set_permissions_rp_id(Some(String::from("example.com")));
|
||||||
client_pin.reset(&mut rng);
|
client_pin.reset(&mut rng);
|
||||||
assert_ne!(public_key_v1, client_pin.pin_protocol_v1.get_public_key());
|
assert_ne!(public_key_v1, client_pin.pin_protocol_v1.get_public_key());
|
||||||
assert_ne!(public_key_v2, client_pin.pin_protocol_v2.get_public_key());
|
assert_ne!(public_key_v2, client_pin.pin_protocol_v2.get_public_key());
|
||||||
@@ -1452,4 +1594,94 @@ mod test {
|
|||||||
}
|
}
|
||||||
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
|
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_timeouts() {
|
||||||
|
let (mut client_pin, mut params) = create_client_pin_and_parameters(
|
||||||
|
PinUvAuthProtocol::V2,
|
||||||
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||||
|
);
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
set_standard_pin(&mut persistent_store);
|
||||||
|
params.permissions = Some(0xFF);
|
||||||
|
|
||||||
|
assert!(client_pin
|
||||||
|
.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE)
|
||||||
|
.is_ok());
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(permission),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permissions_rp_id("example.com"),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
|
||||||
|
let timeout = DUMMY_CLOCK_VALUE.wrapping_add(Duration::from_ms(30001));
|
||||||
|
client_pin.update_timeouts(timeout);
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(permission),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permissions_rp_id("example.com"),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_token_flags() {
|
||||||
|
let (mut client_pin, mut params) = create_client_pin_and_parameters(
|
||||||
|
PinUvAuthProtocol::V2,
|
||||||
|
ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions,
|
||||||
|
);
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
set_standard_pin(&mut persistent_store);
|
||||||
|
params.permissions = Some(0xFF);
|
||||||
|
|
||||||
|
assert!(client_pin
|
||||||
|
.process_command(&mut rng, &mut persistent_store, params, DUMMY_CLOCK_VALUE)
|
||||||
|
.is_ok());
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(permission),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(client_pin.check_user_verified_flag(), Ok(()));
|
||||||
|
|
||||||
|
client_pin.clear_token_flags();
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::CredentialManagement),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin
|
||||||
|
.pin_uv_auth_token_state
|
||||||
|
.has_permission(PinPermission::LargeBlobWrite),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
client_pin.check_user_verified_flag(),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ impl Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct AuthenticatorMakeCredentialParameters {
|
pub struct AuthenticatorMakeCredentialParameters {
|
||||||
pub client_data_hash: Vec<u8>,
|
pub client_data_hash: Vec<u8>,
|
||||||
pub rp: PublicKeyCredentialRpEntity,
|
pub rp: PublicKeyCredentialRpEntity,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
|
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct PublicKeyCredentialParameter {
|
pub struct PublicKeyCredentialParameter {
|
||||||
pub cred_type: PublicKeyCredentialType,
|
pub cred_type: PublicKeyCredentialType,
|
||||||
pub alg: SignatureAlgorithm,
|
pub alg: SignatureAlgorithm,
|
||||||
@@ -387,7 +387,7 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Even though options are optional, we can use the default if not present.
|
// Even though options are optional, we can use the default if not present.
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct MakeCredentialOptions {
|
pub struct MakeCredentialOptions {
|
||||||
pub rk: bool,
|
pub rk: bool,
|
||||||
pub uv: bool,
|
pub uv: bool,
|
||||||
@@ -484,7 +484,7 @@ impl From<PackedAttestationStatement> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum SignatureAlgorithm {
|
pub enum SignatureAlgorithm {
|
||||||
ES256 = ES256_ALGORITHM as isize,
|
ES256 = ES256_ALGORITHM as isize,
|
||||||
// This is the default for all numbers not covered above.
|
// This is the default for all numbers not covered above.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub mod response;
|
|||||||
pub mod status_code;
|
pub mod status_code;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod timed_permission;
|
mod timed_permission;
|
||||||
|
mod token_state;
|
||||||
|
|
||||||
use self::client_pin::{ClientPin, PinPermission};
|
use self::client_pin::{ClientPin, PinPermission};
|
||||||
use self::command::{
|
use self::command::{
|
||||||
@@ -301,11 +302,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_command_permission(&mut self, now: ClockValue) {
|
pub fn update_timeouts(&mut self, now: ClockValue) {
|
||||||
// Ignore the result, just update.
|
// Ignore the result, just update.
|
||||||
let _ = self
|
let _ = self
|
||||||
.stateful_command_permission
|
.stateful_command_permission
|
||||||
.check_command_permission(now);
|
.check_command_permission(now);
|
||||||
|
self.client_pin.update_timeouts(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn increment_global_signature_counter(&mut self) -> Result<(), Ctap2StatusCode> {
|
pub fn increment_global_signature_counter(&mut self) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -465,6 +467,7 @@ where
|
|||||||
self.rng,
|
self.rng,
|
||||||
&mut self.persistent_store,
|
&mut self.persistent_store,
|
||||||
params,
|
params,
|
||||||
|
now,
|
||||||
),
|
),
|
||||||
Command::AuthenticatorReset => self.process_reset(cid, now),
|
Command::AuthenticatorReset => self.process_reset(cid, now),
|
||||||
Command::AuthenticatorCredentialManagement(params) => {
|
Command::AuthenticatorCredentialManagement(params) => {
|
||||||
@@ -641,6 +644,10 @@ where
|
|||||||
)?;
|
)?;
|
||||||
self.client_pin
|
self.client_pin
|
||||||
.has_permission(PinPermission::MakeCredential)?;
|
.has_permission(PinPermission::MakeCredential)?;
|
||||||
|
self.client_pin.check_user_verified_flag()?;
|
||||||
|
// Checking for the correct permissions_rp_id is specified earlier.
|
||||||
|
// Error codes are identical though, so the implementation can be identical with
|
||||||
|
// GetAssertion.
|
||||||
self.client_pin.ensure_rp_id_permission(&rp_id)?;
|
self.client_pin.ensure_rp_id_permission(&rp_id)?;
|
||||||
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
||||||
}
|
}
|
||||||
@@ -660,6 +667,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
(self.check_user_presence)(cid)?;
|
(self.check_user_presence)(cid)?;
|
||||||
|
self.client_pin.clear_token_flags();
|
||||||
|
|
||||||
let sk = crypto::ecdsa::SecKey::gensk(self.rng);
|
let sk = crypto::ecdsa::SecKey::gensk(self.rng);
|
||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
@@ -932,6 +940,10 @@ where
|
|||||||
)?;
|
)?;
|
||||||
self.client_pin
|
self.client_pin
|
||||||
.has_permission(PinPermission::GetAssertion)?;
|
.has_permission(PinPermission::GetAssertion)?;
|
||||||
|
// Checking for the UV flag is specified earlier for GetAssertion.
|
||||||
|
// Error codes are identical though, so the implementation can be identical with
|
||||||
|
// MakeCredential.
|
||||||
|
self.client_pin.check_user_verified_flag()?;
|
||||||
self.client_pin.ensure_rp_id_permission(&rp_id)?;
|
self.client_pin.ensure_rp_id_permission(&rp_id)?;
|
||||||
UV_FLAG
|
UV_FLAG
|
||||||
}
|
}
|
||||||
@@ -987,6 +999,7 @@ where
|
|||||||
// For CTAP 2.1, it was moved to a later protocol step.
|
// For CTAP 2.1, it was moved to a later protocol step.
|
||||||
if options.up {
|
if options.up {
|
||||||
(self.check_user_presence)(cid)?;
|
(self.check_user_presence)(cid)?;
|
||||||
|
self.client_pin.clear_token_flags();
|
||||||
}
|
}
|
||||||
|
|
||||||
let credential = credential.ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?;
|
let credential = credential.ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?;
|
||||||
@@ -1777,7 +1790,7 @@ mod test {
|
|||||||
make_credential_params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
make_credential_params.pin_uv_auth_param = Some(pin_uv_auth_param);
|
||||||
make_credential_params.pin_uv_auth_protocol = Some(pin_uv_auth_protocol);
|
make_credential_params.pin_uv_auth_protocol = Some(pin_uv_auth_protocol);
|
||||||
let make_credential_response =
|
let make_credential_response =
|
||||||
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
ctap_state.process_make_credential(make_credential_params.clone(), DUMMY_CHANNEL_ID);
|
||||||
|
|
||||||
check_make_response(
|
check_make_response(
|
||||||
make_credential_response,
|
make_credential_response,
|
||||||
@@ -1786,6 +1799,13 @@ mod test {
|
|||||||
0x20,
|
0x20,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let make_credential_response =
|
||||||
|
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||||
|
assert_eq!(
|
||||||
|
make_credential_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2088,6 +2108,7 @@ mod test {
|
|||||||
ctap_state.rng,
|
ctap_state.rng,
|
||||||
&mut ctap_state.persistent_store,
|
&mut ctap_state.persistent_store,
|
||||||
client_pin_params,
|
client_pin_params,
|
||||||
|
DUMMY_CLOCK_VALUE,
|
||||||
);
|
);
|
||||||
let get_assertion_params = get_assertion_hmac_secret_params(
|
let get_assertion_params = get_assertion_hmac_secret_params(
|
||||||
key_agreement_key,
|
key_agreement_key,
|
||||||
@@ -2145,6 +2166,7 @@ mod test {
|
|||||||
ctap_state.rng,
|
ctap_state.rng,
|
||||||
&mut ctap_state.persistent_store,
|
&mut ctap_state.persistent_store,
|
||||||
client_pin_params,
|
client_pin_params,
|
||||||
|
DUMMY_CLOCK_VALUE,
|
||||||
);
|
);
|
||||||
let get_assertion_params = get_assertion_hmac_secret_params(
|
let get_assertion_params = get_assertion_hmac_secret_params(
|
||||||
key_agreement_key,
|
key_agreement_key,
|
||||||
@@ -2423,7 +2445,6 @@ mod test {
|
|||||||
|
|
||||||
let user_immediately_present = |_| Ok(());
|
let user_immediately_present = |_| Ok(());
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
ctap_state.client_pin = client_pin;
|
|
||||||
|
|
||||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||||
let user1 = PublicKeyCredentialUserEntity {
|
let user1 = PublicKeyCredentialUserEntity {
|
||||||
@@ -2448,6 +2469,7 @@ mod test {
|
|||||||
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
|
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
|
ctap_state.client_pin = client_pin;
|
||||||
// The PIN length is outside of the test scope and most likely incorrect.
|
// The PIN length is outside of the test scope and most likely incorrect.
|
||||||
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
|
ctap_state.persistent_store.set_pin(&[0u8; 16], 4).unwrap();
|
||||||
let client_data_hash = vec![0xCD];
|
let client_data_hash = vec![0xCD];
|
||||||
|
|||||||
277
src/ctap/token_state.rs
Normal file
277
src/ctap/token_state.rs
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
// Copyright 2021 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::ctap::client_pin::PinPermission;
|
||||||
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
|
use crate::ctap::timed_permission::TimedPermission;
|
||||||
|
use alloc::string::String;
|
||||||
|
use crypto::sha256::Sha256;
|
||||||
|
use crypto::Hash256;
|
||||||
|
use libtock_drivers::timer::{ClockValue, Duration};
|
||||||
|
|
||||||
|
/// Timeout for auth tokens.
|
||||||
|
///
|
||||||
|
/// This usage time limit is correct for USB, BLE, and internal.
|
||||||
|
/// NFC only allows 19.8 seconds.
|
||||||
|
/// TODO(#15) multiplex over transports, add NFC
|
||||||
|
const INITIAL_USAGE_TIME_LIMIT: Duration<isize> = Duration::from_ms(30000);
|
||||||
|
|
||||||
|
/// Implements pinUvAuthToken state from section 6.5.2.1.
|
||||||
|
///
|
||||||
|
/// The userPresent flag is omitted as the only way to set it to true is
|
||||||
|
/// built-in user verification. Therefore, we never cache user presence.
|
||||||
|
///
|
||||||
|
/// This implementation does not use a rolling timer.
|
||||||
|
pub struct PinUvAuthTokenState {
|
||||||
|
// Relies on the fact that all permissions are represented by powers of two.
|
||||||
|
permissions_set: u8,
|
||||||
|
permissions_rp_id: Option<String>,
|
||||||
|
usage_timer: TimedPermission,
|
||||||
|
user_verified: bool,
|
||||||
|
in_use: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinUvAuthTokenState {
|
||||||
|
/// Creates a pinUvAuthToken state without permissions.
|
||||||
|
pub fn new() -> PinUvAuthTokenState {
|
||||||
|
PinUvAuthTokenState {
|
||||||
|
permissions_set: 0,
|
||||||
|
permissions_rp_id: None,
|
||||||
|
usage_timer: TimedPermission::waiting(),
|
||||||
|
user_verified: false,
|
||||||
|
in_use: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the pinUvAuthToken is active.
|
||||||
|
pub fn is_in_use(&self) -> bool {
|
||||||
|
self.in_use
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the permission is granted.
|
||||||
|
pub fn has_permission(&self, permission: PinPermission) -> Result<(), Ctap2StatusCode> {
|
||||||
|
if permission as u8 & self.permissions_set != 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there is no associated permissions RPID.
|
||||||
|
pub fn has_no_permissions_rp_id(&self) -> Result<(), Ctap2StatusCode> {
|
||||||
|
if self.permissions_rp_id.is_some() {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the permissions RPID is associated.
|
||||||
|
pub fn has_permissions_rp_id(&self, rp_id: &str) -> Result<(), Ctap2StatusCode> {
|
||||||
|
match &self.permissions_rp_id {
|
||||||
|
Some(p) if rp_id == p => Ok(()),
|
||||||
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the permissions RPID's association matches the hash.
|
||||||
|
pub fn has_permissions_rp_id_hash(&self, rp_id_hash: &[u8]) -> Result<(), Ctap2StatusCode> {
|
||||||
|
match &self.permissions_rp_id {
|
||||||
|
Some(p) if rp_id_hash == Sha256::hash(p.as_bytes()) => Ok(()),
|
||||||
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the permissions, represented as bits in a byte.
|
||||||
|
pub fn set_permissions(&mut self, permissions: u8) {
|
||||||
|
self.permissions_set = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the permissions RPID.
|
||||||
|
pub fn set_permissions_rp_id(&mut self, permissions_rp_id: Option<String>) {
|
||||||
|
self.permissions_rp_id = permissions_rp_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the default permissions.
|
||||||
|
///
|
||||||
|
/// Allows MakeCredential and GetAssertion, without specifying a RP ID.
|
||||||
|
pub fn set_default_permissions(&mut self) {
|
||||||
|
self.set_permissions(0x03);
|
||||||
|
self.set_permissions_rp_id(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the timer for pinUvAuthToken usage.
|
||||||
|
pub fn begin_using_pin_uv_auth_token(&mut self, now: ClockValue) {
|
||||||
|
self.user_verified = true;
|
||||||
|
self.usage_timer = TimedPermission::granted(now, INITIAL_USAGE_TIME_LIMIT);
|
||||||
|
self.in_use = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the usage timer, and disables the pinUvAuthToken on timeout.
|
||||||
|
pub fn pin_uv_auth_token_usage_timer_observer(&mut self, now: ClockValue) {
|
||||||
|
if !self.in_use {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.usage_timer = self.usage_timer.check_expiration(now);
|
||||||
|
if !self.usage_timer.is_granted(now) {
|
||||||
|
self.stop_using_pin_uv_auth_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the user is verified.
|
||||||
|
pub fn get_user_verified_flag_value(&self) -> bool {
|
||||||
|
self.in_use && self.user_verified
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the user verification.
|
||||||
|
pub fn clear_user_verified_flag(&mut self) {
|
||||||
|
self.user_verified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all permissions except Large Blob Write.
|
||||||
|
pub fn clear_pin_uv_auth_token_permissions_except_lbw(&mut self) {
|
||||||
|
self.permissions_set &= PinPermission::LargeBlobWrite as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets to the initial state.
|
||||||
|
pub fn stop_using_pin_uv_auth_token(&mut self) {
|
||||||
|
self.permissions_rp_id = None;
|
||||||
|
self.permissions_set = 0;
|
||||||
|
self.usage_timer = TimedPermission::waiting();
|
||||||
|
self.user_verified = false;
|
||||||
|
self.in_use = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use enum_iterator::IntoEnumIterator;
|
||||||
|
|
||||||
|
const CLOCK_FREQUENCY_HZ: usize = 32768;
|
||||||
|
const START_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
|
||||||
|
const SMALL_DURATION: Duration<isize> = Duration::from_ms(100);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_observer() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
let mut now = START_CLOCK_VALUE;
|
||||||
|
token_state.begin_using_pin_uv_auth_token(now);
|
||||||
|
assert!(token_state.is_in_use());
|
||||||
|
now = now.wrapping_add(SMALL_DURATION);
|
||||||
|
token_state.pin_uv_auth_token_usage_timer_observer(now);
|
||||||
|
assert!(token_state.is_in_use());
|
||||||
|
now = now.wrapping_add(INITIAL_USAGE_TIME_LIMIT);
|
||||||
|
token_state.pin_uv_auth_token_usage_timer_observer(now);
|
||||||
|
assert!(!token_state.is_in_use());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stop() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
token_state.begin_using_pin_uv_auth_token(START_CLOCK_VALUE);
|
||||||
|
assert!(token_state.is_in_use());
|
||||||
|
token_state.stop_using_pin_uv_auth_token();
|
||||||
|
assert!(!token_state.is_in_use());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permissions() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
token_state.set_permissions(0xFF);
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(token_state.has_permission(permission), Ok(()));
|
||||||
|
}
|
||||||
|
token_state.clear_pin_uv_auth_token_permissions_except_lbw();
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permission(PinPermission::CredentialManagement),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permission(PinPermission::LargeBlobWrite),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
token_state.stop_using_pin_uv_auth_token();
|
||||||
|
for permission in PinPermission::into_enum_iter() {
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permission(permission),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permissions_rp_id_none() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
let example_hash = Sha256::hash(b"example.com");
|
||||||
|
token_state.set_permissions_rp_id(None);
|
||||||
|
assert_eq!(token_state.has_no_permissions_rp_id(), Ok(()));
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id("example.com"),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id_hash(&example_hash),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permissions_rp_id_some() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
let example_hash = Sha256::hash(b"example.com");
|
||||||
|
token_state.set_permissions_rp_id(Some(String::from("example.com")));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_no_permissions_rp_id(),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(token_state.has_permissions_rp_id("example.com"), Ok(()));
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id("another.example.com"),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id_hash(&example_hash),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id_hash(&[0x1D; 32]),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
|
||||||
|
token_state.stop_using_pin_uv_auth_token();
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id("example.com"),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
token_state.has_permissions_rp_id_hash(&example_hash),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_verified_flag() {
|
||||||
|
let mut token_state = PinUvAuthTokenState::new();
|
||||||
|
assert!(!token_state.get_user_verified_flag_value());
|
||||||
|
token_state.begin_using_pin_uv_auth_token(START_CLOCK_VALUE);
|
||||||
|
assert!(token_state.get_user_verified_flag_value());
|
||||||
|
token_state.clear_user_verified_flag();
|
||||||
|
assert!(!token_state.get_user_verified_flag_value());
|
||||||
|
token_state.begin_using_pin_uv_auth_token(START_CLOCK_VALUE);
|
||||||
|
assert!(token_state.get_user_verified_flag_value());
|
||||||
|
token_state.stop_using_pin_uv_auth_token();
|
||||||
|
assert!(!token_state.get_user_verified_flag_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,8 +120,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These calls are making sure that even for long inactivity, wrapping clock values
|
// These calls are making sure that even for long inactivity, wrapping clock values
|
||||||
// never randomly wink or grant user presence for U2F.
|
// don't cause problems with timers.
|
||||||
ctap_state.update_command_permission(now);
|
ctap_state.update_timeouts(now);
|
||||||
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
|
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
|
||||||
|
|
||||||
if has_packet {
|
if has_packet {
|
||||||
|
|||||||
Reference in New Issue
Block a user