Add basic setup for multi-PIN (#530)

* Add basic setup for multi-PIN

- Reserve the storage keys for maximum of 8 user slots.
- Modify the storage functions to take a slot_id parameter.
- Add the slot_count() customization.
- Assume slot_id as a parameter when needed except these places:
  - Entrance functions of command processing that directly takes the
    command parameter structure. slot_id is set as 0, and will be
    parsed from the parameters when we enable the feature.
  - MakeCredential/GetAssertion/AuthenticatorConfig will take the
    slot_id from active token state when we enable the feature,
    resulting in an `Option<usize>`. Below code will act on the option
    value correctly. When the feature isn't enabled, we're always
    referring to the only PIN slot so set slot_id as Some(0).
  - GetInfo returns verdict of whether PIN is supported and enabled, and
    whether PIN needs to be forced changed. There will be new fields to
    represent those values when the feature is enabled, and the old
    fields will not be populated. So when the feature isn't enabled, we
    can treat slot_id as 0.

Not covered in this commit:
- Unittests for other slots. The existing tests all pass and I plan to
  add unittests for multi-slot case after the codebase allows enabling
  the feature.
- Persisting and checking the slot_id in credentials. This is planned to
  come in the next commit.

* Fix storage and some other style

* Add support for concatenated values

* Switch some storage entries back to multi-entry

* Set bumpalo version for fuzzing (#532)

* maximum working bumpalo version

* explicit comment to explain version locking

* removes incorrect comment

* moves serde version lock to dev dependencies

* removes serde dependencies

* reverts serde removal in crypto library

* Make PIN_PROPERTIES use concatenated storage entry

* Fix bumpalo issue

* Use concatenated storage entry for force_pin_change too

* Fix cargofmt

Co-authored-by: Julien Cretin <cretin@google.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
This commit is contained in:
hcyang
2022-08-23 23:01:13 +08:00
committed by GitHub
parent 4442998b64
commit 078e565ac1
15 changed files with 814 additions and 232 deletions

View File

@@ -20,8 +20,6 @@ persistent_store = { path = "libraries/persistent_store" }
byteorder = { version = "1", default-features = false }
arrayref = "0.3.6"
subtle = { version = "2.2", default-features = false, features = ["nightly"] }
# This import explicitly locks the version.
serde_json = { version = "=1.0.69", default-features = false, features = ["alloc"] }
embedded-time = "0.12.1"
arbitrary = { version = "0.4.7", features = ["derive"], optional = true }
rand = { version = "0.8.4", optional = true }

View File

@@ -11,8 +11,6 @@ cargo-fuzz = true
[dependencies]
libfuzzer-sys = { version = "0.3" }
fuzz_helper = { path = "fuzz_helper" }
# This import explicitly locks the version.
serde_json = { version = "=1.0.69" }
# Prevent this from interfering with workspaces
[workspace]

View File

@@ -21,6 +21,11 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "=1.0.69", optional = true }
regex = { version = "1", optional = true }
# We explicitly lock the version of those transitive dependencies because their
# Cargo.toml don't parse with the nightly compiler used by Tock. Remove this
# whole block once CTAP is a library.
bumpalo = "=3.8.0" # transitive dependency of ring
[features]
std = ["hex", "ring", "rng256/std", "untrusted", "serde", "serde_json", "regex"]
with_ctap1 = []

View File

@@ -0,0 +1,242 @@
// Copyright 2022 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.
//! Support for concatenated entries.
//!
//! This module permits to store multiple indexed values under the same key by concatenation. Such
//! values must be at most 255 bytes and there can't be more than 255 such values under the same
//! key (they are indexed with a `u8`).
//!
//! The rationale for using those particular constraints is that we want the number of bits to store
//! the index and the number of bits to store the length to fit in an integer number of bytes
//! (because the values are an integer number of bytes). Using only one byte is too restrictive
//! (e.g. 8 values of at most 31 bytes or 16 values of at most 15 bytes). Using 2 bytes is plenty of
//! space, so using one byte for each field makes parsing simpler and faster.
//!
//! The format is thus `(index:u8 length:u8 payload:[u8; length])*`. The concatenation is not
//! particularly sorted.
use crate::{Storage, Store, StoreError, StoreResult};
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::ops::Range;
/// Reads a value from a concatenated entry.
pub fn read(store: &Store<impl Storage>, key: usize, index: u8) -> StoreResult<Option<Vec<u8>>> {
let values = match store.find(key)? {
None => return Ok(None),
Some(x) => x,
};
Ok(find(&values, index)?.map(|range| values[range].to_vec()))
}
/// Writes a value to a concatenated entry.
pub fn write(
store: &mut Store<impl Storage>,
key: usize,
index: u8,
value: &[u8],
) -> StoreResult<()> {
if value.len() > 255 {
return Err(StoreError::InvalidArgument);
}
let mut values = store.find(key)?.unwrap_or(vec![]);
match find(&values, index)? {
None => {
values.push(index);
values.push(value.len() as u8);
values.extend_from_slice(value);
}
Some(mut range) => {
values[range.start - 1] = value.len() as u8;
match range.len().cmp(&value.len()) {
Ordering::Less => {
let diff = value.len() - range.len();
values.resize(values.len() + diff, 0);
values[range.end..].rotate_right(diff);
range.end += diff;
}
Ordering::Equal => (),
Ordering::Greater => {
let diff = range.len() - value.len();
range.end -= diff;
values[range.end..].rotate_left(diff);
values.truncate(values.len() - diff);
}
}
values[range].copy_from_slice(value);
}
}
store.insert(key, &values)
}
/// Deletes the value from a concatenated entry.
pub fn delete(store: &mut Store<impl Storage>, key: usize, index: u8) -> StoreResult<()> {
let mut values = match store.find(key)? {
None => return Ok(()),
Some(x) => x,
};
let mut range = match find(&values, index)? {
None => return Ok(()),
Some(x) => x,
};
range.start -= 2;
values[range.start..].rotate_left(range.len());
values.truncate(values.len() - range.len());
store.insert(key, &values)
}
fn find(values: &[u8], index: u8) -> StoreResult<Option<Range<usize>>> {
let mut pos = 0;
while pos < values.len() {
if pos == values.len() - 1 {
return Err(StoreError::InvalidStorage);
}
let len = values[pos + 1] as usize;
if len > values.len() - 2 || pos > values.len() - 2 - len {
return Err(StoreError::InvalidStorage);
}
if index == values[pos] {
return Ok(Some(pos + 2..pos + 2 + len));
}
pos += 2 + len;
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::MINIMAL;
#[test]
fn read_empty_entry() {
let store = MINIMAL.new_store();
assert_eq!(read(&store, 0, 0), Ok(None));
assert_eq!(read(&store, 0, 1), Ok(None));
}
#[test]
fn read_missing_value() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(read(&store, 0, 1), Ok(None));
}
#[test]
fn read_existing_value() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(read(&store, 0, 0), Ok(Some(b"foo".to_vec())));
assert_eq!(read(&store, 0, 2), Ok(Some(b"hello".to_vec())));
}
#[test]
fn read_invalid_entry_too_long() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x08hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(read(&store, 0, 1), Err(StoreError::InvalidStorage));
}
#[test]
fn read_invalid_entry_too_short() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(read(&store, 0, 1), Err(StoreError::InvalidStorage));
}
#[test]
fn write_empty_entry() {
let mut store = MINIMAL.new_store();
assert_eq!(write(&mut store, 0, 0, b"foo"), Ok(()));
assert_eq!(store.find(0), Ok(Some(b"\x00\x03foo".to_vec())));
}
#[test]
fn write_missing_value() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(write(&mut store, 0, 1, b"bar"), Ok(()));
assert_eq!(store.find(0), Ok(Some(b"\x00\x03foo\x01\x03bar".to_vec())));
}
#[test]
fn write_existing_value_same_size() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(write(&mut store, 0, 0, b"bar"), Ok(()));
assert_eq!(
store.find(0),
Ok(Some(b"\x00\x03bar\x02\x05hello".to_vec()))
);
}
#[test]
fn write_existing_value_longer() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(write(&mut store, 0, 0, b"barrage"), Ok(()));
assert_eq!(
store.find(0),
Ok(Some(b"\x00\x07barrage\x02\x05hello".to_vec()))
);
}
#[test]
fn write_existing_value_shorter() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x08football\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(write(&mut store, 0, 0, b"bar"), Ok(()));
assert_eq!(
store.find(0),
Ok(Some(b"\x00\x03bar\x02\x05hello".to_vec()))
);
}
#[test]
fn delete_empty_entry() {
let mut store = MINIMAL.new_store();
assert_eq!(delete(&mut store, 0, 0), Ok(()));
assert_eq!(delete(&mut store, 0, 1), Ok(()));
}
#[test]
fn delete_missing_value() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(delete(&mut store, 0, 1), Ok(()));
assert_eq!(
store.find(0),
Ok(Some(b"\x00\x03foo\x02\x05hello".to_vec()))
);
}
#[test]
fn delete_existing_value() {
let mut store = MINIMAL.new_store();
let value = b"\x00\x03foo\x02\x05hello".to_vec();
store.insert(0, &value).unwrap();
assert_eq!(delete(&mut store, 0, 0), Ok(()));
assert_eq!(store.find(0), Ok(Some(b"\x02\x05hello".to_vec())));
}
}

View File

@@ -363,6 +363,7 @@ extern crate alloc;
#[cfg(feature = "std")]
mod buffer;
pub mod concat;
#[cfg(feature = "std")]
mod driver;
#[cfg(feature = "std")]

View File

@@ -241,6 +241,21 @@ pub trait Customization {
/// With P=20 and K=150, we have I=2M which is enough for 500 increments per day
/// for 10 years.
fn max_supported_resident_keys(&self) -> usize;
/// Sets the slot count of the multi-PIN feature.
///
/// # Invariant
///
/// - The slot count may not:
/// - make the storage entries that concatenate data of each slots
/// become larger than the storage page size,
/// - go over u8, as we only reserve 1 byte for the array index for
/// concatenated entries, or
/// - exceed the number of keys we reserve for the storage entries
/// that use unique keys for each slot.
///
/// The upper bound of this is currently 8.
fn slot_count(&self) -> usize;
}
#[derive(Clone)]
@@ -260,6 +275,7 @@ pub struct CustomizationImpl {
pub max_large_blob_array_size: usize,
pub max_rp_ids_length: usize,
pub max_supported_resident_keys: usize,
pub slot_count: usize,
}
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
@@ -278,6 +294,7 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
max_large_blob_array_size: 2048,
max_rp_ids_length: 8,
max_supported_resident_keys: 150,
slot_count: 1,
};
impl Customization for CustomizationImpl {
@@ -351,6 +368,10 @@ impl Customization for CustomizationImpl {
fn max_supported_resident_keys(&self) -> usize {
self.max_supported_resident_keys
}
fn slot_count(&self) -> usize {
self.slot_count
}
}
#[cfg(feature = "std")]
@@ -423,6 +444,11 @@ pub fn is_valid(customization: &impl Customization) -> bool {
return false;
}
// Slot count should be at most 8.
if customization.slot_count() > 8 {
return false;
}
true
}

View File

@@ -21,12 +21,13 @@ use super::pin_protocol::{verify_pin_uv_auth_token, PinProtocol, SharedSecret};
use super::response::{AuthenticatorClientPinResponse, ResponseData};
use super::status_code::Ctap2StatusCode;
use super::token_state::PinUvAuthTokenState;
use crate::api::customization::Customization;
use crate::ctap::storage;
use crate::env::Env;
use alloc::boxed::Box;
use alloc::str;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{str, vec};
use crypto::hmac::hmac_256;
use crypto::sha256::Sha256;
use crypto::Hash256;
@@ -78,6 +79,7 @@ fn decrypt_pin(
/// truncated for persistent storage.
fn check_and_store_new_pin(
env: &mut impl Env,
slot_id: usize,
shared_secret: &dyn SharedSecret,
new_pin_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
@@ -90,7 +92,7 @@ fn check_and_store_new_pin(
let mut pin_hash = [0u8; PIN_AUTH_LENGTH];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]);
// The PIN length is always < PIN_PADDED_LENGTH < 256.
storage::set_pin(env, &pin_hash, pin_length as u8)?;
storage::set_pin(env, slot_id, &pin_hash, pin_length as u8)?;
Ok(())
}
@@ -108,16 +110,16 @@ pub enum PinPermission {
pub struct ClientPin {
pin_protocol_v1: PinProtocol,
pin_protocol_v2: PinProtocol,
consecutive_pin_mismatches: u8,
consecutive_pin_mismatches: Vec<u8>,
pin_uv_auth_token_state: PinUvAuthTokenState,
}
impl ClientPin {
pub fn new(rng: &mut impl Rng256) -> ClientPin {
pub fn new(env: &mut impl Env) -> ClientPin {
ClientPin {
pin_protocol_v1: PinProtocol::new(rng),
pin_protocol_v2: PinProtocol::new(rng),
consecutive_pin_mismatches: 0,
pin_protocol_v1: PinProtocol::new(env.rng()),
pin_protocol_v2: PinProtocol::new(env.rng()),
consecutive_pin_mismatches: vec![0; env.customization().slot_count()],
pin_uv_auth_token_state: PinUvAuthTokenState::new(),
}
}
@@ -159,16 +161,21 @@ impl ClientPin {
fn verify_pin_hash_enc(
&mut self,
env: &mut impl Env,
slot_id: usize,
pin_uv_auth_protocol: PinUvAuthProtocol,
shared_secret: &dyn SharedSecret,
pin_hash_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
match storage::pin_hash(env)? {
// To prevent out of bounds access in code below.
if slot_id >= self.consecutive_pin_mismatches.len() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
match storage::pin_hash(env, slot_id)? {
Some(pin_hash) => {
if self.consecutive_pin_mismatches >= 3 {
if self.consecutive_pin_mismatches[slot_id] >= 3 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
}
storage::decr_pin_retries(env)?;
storage::decr_pin_retries(env, slot_id)?;
let pin_hash_dec = shared_secret
.decrypt(&pin_hash_enc)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
@@ -176,11 +183,11 @@ impl ClientPin {
if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) {
self.get_mut_pin_protocol(pin_uv_auth_protocol)
.regenerate(env.rng());
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
self.consecutive_pin_mismatches += 1;
if self.consecutive_pin_mismatches >= 3 {
self.consecutive_pin_mismatches[slot_id] += 1;
if self.consecutive_pin_mismatches[slot_id] >= 3 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
}
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -189,20 +196,26 @@ impl ClientPin {
// This status code is not explicitly mentioned in the specification.
None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED),
}
storage::reset_pin_retries(env)?;
self.consecutive_pin_mismatches = 0;
storage::reset_pin_retries(env, slot_id)?;
self.consecutive_pin_mismatches[slot_id] = 0;
Ok(())
}
fn process_get_pin_retries(
&self,
env: &mut impl Env,
_client_pin_params: AuthenticatorClientPinParameters,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
if slot_id >= self.consecutive_pin_mismatches.len() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(env)? as u64),
power_cycle_state: Some(self.consecutive_pin_mismatches >= 3),
retries: Some(storage::pin_retries(env, slot_id)? as u64),
power_cycle_state: Some(self.consecutive_pin_mismatches[slot_id] >= 3),
})
}
@@ -234,18 +247,20 @@ impl ClientPin {
new_pin_enc,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
let new_pin_enc = ok_or_missing(new_pin_enc)?;
if storage::pin_hash(env)?.is_some() {
if storage::pin_hash(env, slot_id)?.is_some() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?;
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
storage::reset_pin_retries(env)?;
check_and_store_new_pin(env, slot_id, shared_secret.as_ref(), new_pin_enc)?;
storage::reset_pin_retries(env, slot_id)?;
Ok(())
}
@@ -262,12 +277,14 @@ impl ClientPin {
pin_hash_enc,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
let new_pin_enc = ok_or_missing(new_pin_enc)?;
let pin_hash_enc = ok_or_missing(pin_hash_enc)?;
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
@@ -276,12 +293,13 @@ impl ClientPin {
shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?;
self.verify_pin_hash_enc(
env,
slot_id,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc,
)?;
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
check_and_store_new_pin(env, slot_id, shared_secret.as_ref(), new_pin_enc)?;
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
Ok(())
@@ -301,28 +319,32 @@ impl ClientPin {
permissions_rp_id,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_hash_enc = ok_or_missing(pin_hash_enc)?;
if permissions.is_some() || permissions_rp_id.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
self.verify_pin_hash_enc(
env,
slot_id,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc,
)?;
if storage::has_force_pin_change(env)? {
if storage::has_force_pin_change(env, slot_id)? {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
}
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
// TODO: pass the slot_id parameter and store it in the state too.
self.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(now);
self.pin_uv_auth_token_state.set_default_permissions();
@@ -391,7 +413,9 @@ impl ClientPin {
now: CtapInstant,
) -> Result<ResponseData, Ctap2StatusCode> {
let response = match client_pin_params.sub_command {
ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries(env)?),
ClientPinSubCommand::GetPinRetries => {
Some(self.process_get_pin_retries(env, client_pin_params)?)
}
ClientPinSubCommand::GetKeyAgreement => {
Some(self.process_get_key_agreement(client_pin_params)?)
}
@@ -446,7 +470,9 @@ impl ClientPin {
self.pin_protocol_v1.reset_pin_uv_auth_token(rng);
self.pin_protocol_v2.regenerate(rng);
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
self.consecutive_pin_mismatches = 0;
for v in &mut self.consecutive_pin_mismatches {
*v = 0;
}
self.pin_uv_auth_token_state.stop_using_pin_uv_auth_token();
}
@@ -557,11 +583,11 @@ impl ClientPin {
#[cfg(test)]
pub fn new_test(
env: &mut impl Env,
key_agreement_key: crypto::ecdh::SecKey,
pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH],
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> ClientPin {
let mut env = crate::env::test::TestEnv::new();
let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol {
PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(env.rng())),
PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(env.rng()), key_agreement_key),
@@ -572,7 +598,7 @@ impl ClientPin {
ClientPin {
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),
consecutive_pin_mismatches: 0,
consecutive_pin_mismatches: vec![0; env.customization().slot_count()],
pin_uv_auth_token_state,
}
}
@@ -592,7 +618,7 @@ mod test {
pin[..4].copy_from_slice(b"1234");
let mut pin_hash = [0u8; 16];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
storage::set_pin(env, &pin_hash, 4).unwrap();
storage::set_pin(env, 0, &pin_hash, 4).unwrap();
}
/// Fails on PINs bigger than 64 bytes.
@@ -618,8 +644,12 @@ mod test {
let pk = key_agreement_key.genpk();
let key_agreement = CoseKey::from(pk);
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 client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let shared_secret = client_pin
.get_pin_protocol(pin_uv_auth_protocol)
.decapsulate(key_agreement, pin_uv_auth_protocol)
@@ -677,7 +707,7 @@ mod test {
#[test]
fn test_mix_pin_protocols() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::new(&mut env);
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];
@@ -718,7 +748,7 @@ mod test {
fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol);
let shared_secret = pin_protocol
.decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol)
@@ -728,7 +758,7 @@ mod test {
0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36,
0xC4, 0x12,
];
storage::set_pin(&mut env, &pin_hash, 4).unwrap();
storage::set_pin(&mut env, 0, &pin_hash, 4).unwrap();
let pin_hash_enc = shared_secret
.as_ref()
@@ -737,6 +767,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -748,6 +779,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -759,22 +791,24 @@ mod test {
.as_ref()
.encrypt(env.rng(), &pin_hash)
.unwrap();
client_pin.consecutive_pin_mismatches = 3;
client_pin.consecutive_pin_mismatches[0] = 3;
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED)
);
client_pin.consecutive_pin_mismatches = 0;
client_pin.consecutive_pin_mismatches[0] = 0;
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1];
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -786,6 +820,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -813,7 +848,7 @@ mod test {
let expected_response = Some(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(&mut env).unwrap() as u64),
retries: Some(storage::pin_retries(&mut env, 0).unwrap() as u64),
power_cycle_state: Some(false),
});
assert_eq!(
@@ -821,11 +856,11 @@ mod test {
Ok(ResponseData::AuthenticatorClientPin(expected_response))
);
client_pin.consecutive_pin_mismatches = 3;
client_pin.consecutive_pin_mismatches[0] = 3;
let expected_response = Some(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(&mut env).unwrap() as u64),
retries: Some(storage::pin_retries(&mut env, 0).unwrap() as u64),
power_cycle_state: Some(true),
});
assert_eq!(
@@ -921,8 +956,8 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
while storage::pin_retries(&mut env).unwrap() > 0 {
storage::decr_pin_retries(&mut env).unwrap();
while storage::pin_retries(&mut env, 0).unwrap() > 0 {
storage::decr_pin_retries(&mut env, 0).unwrap();
}
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
@@ -1015,7 +1050,7 @@ mod test {
let mut env = TestEnv::new();
set_standard_pin(&mut env);
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(storage::force_pin_change(&mut env, 0), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
@@ -1125,7 +1160,7 @@ mod test {
let mut env = TestEnv::new();
set_standard_pin(&mut env);
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(storage::force_pin_change(&mut env, 0), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
@@ -1217,17 +1252,17 @@ mod test {
),
];
for (pin, result) in test_cases {
let old_pin_hash = storage::pin_hash(&mut env).unwrap();
let old_pin_hash = storage::pin_hash(&mut env, 0).unwrap();
let new_pin_enc = encrypt_pin(shared_secret.as_ref(), pin);
assert_eq!(
check_and_store_new_pin(&mut env, shared_secret.as_ref(), new_pin_enc),
check_and_store_new_pin(&mut env, 0, shared_secret.as_ref(), new_pin_enc),
result
);
if result.is_ok() {
assert_ne!(old_pin_hash, storage::pin_hash(&mut env).unwrap());
assert_ne!(old_pin_hash, storage::pin_hash(&mut env, 0).unwrap());
} else {
assert_eq!(old_pin_hash, storage::pin_hash(&mut env).unwrap());
assert_eq!(old_pin_hash, storage::pin_hash(&mut env, 0).unwrap());
}
}
}
@@ -1383,7 +1418,7 @@ mod test {
#[test]
fn test_has_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
client_pin.pin_uv_auth_token_state.set_permissions(0x7F);
for permission in PinPermission::into_enum_iter() {
assert_eq!(
@@ -1407,7 +1442,7 @@ mod test {
#[test]
fn test_has_no_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1421,7 +1456,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1436,7 +1471,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_hash_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let rp_id_hash = Sha256::hash(b"example.com");
assert_eq!(
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
@@ -1458,7 +1493,7 @@ mod test {
#[test]
fn test_ensure_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
assert_eq!(
client_pin
@@ -1476,7 +1511,7 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let message = [0xAA];
client_pin
.pin_uv_auth_token_state
@@ -1550,7 +1585,7 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token_not_in_use() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::new(&mut env);
let message = [0xAA];
let pin_uv_auth_token_v1 = client_pin
@@ -1572,7 +1607,7 @@ mod test {
#[test]
fn test_reset() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let public_key_v1 = client_pin.pin_protocol_v1.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();

View File

@@ -55,15 +55,21 @@ fn process_set_min_pin_length(
if new_min_pin_length < store_min_pin_length {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
}
let mut force_change_pin = force_change_pin.unwrap_or(false);
if force_change_pin && storage::pin_hash(env)?.is_none() {
let force_change_pin = force_change_pin.unwrap_or(false);
if force_change_pin && !storage::has_pin(env)? {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
if let Some(old_length) = storage::pin_code_point_length(env)? {
force_change_pin |= new_min_pin_length > old_length;
}
if force_change_pin {
storage::force_pin_change(env)?;
for slot_id in 0..env.customization().slot_count() {
if storage::pin_hash(env, slot_id)?.is_none() {
continue;
}
let mut force_change_pin = force_change_pin;
if let Some(old_length) = storage::pin_code_point_length(env, slot_id)? {
force_change_pin |= new_min_pin_length > old_length;
}
if force_change_pin {
storage::force_pin_change(env, slot_id)?;
}
}
storage::set_min_pin_length(env, new_min_pin_length)?;
if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
@@ -85,9 +91,11 @@ pub fn process_config(
pin_uv_auth_protocol,
} = params;
// TODO: Get the slot id from active token state when multi-PIN is enabled.
let slot_id = Some(0);
let enforce_uv =
!matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) && storage::has_always_uv(env)?;
if storage::pin_hash(env)?.is_some() || enforce_uv {
if (slot_id.is_some() && storage::pin_hash(env, slot_id.unwrap())?.is_some()) || enforce_uv {
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
@@ -133,8 +141,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::EnableEnterpriseAttestation,
@@ -160,8 +172,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::ToggleAlwaysUv,
@@ -195,9 +211,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]);
@@ -265,8 +285,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, increase minimum PIN length from 4 to 6 without PIN auth.
let min_pin_length = 6;
@@ -277,7 +301,7 @@ mod test {
// Second, increase minimum PIN length from 6 to 8 with PIN auth.
// The stored PIN or its length don't matter since we control the token.
storage::set_pin(&mut env, &[0x88; 16], 8).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 8).unwrap();
let min_pin_length = 8;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
let pin_uv_auth_param = vec![
@@ -309,8 +333,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, set RP IDs without PIN auth.
let min_pin_length = 6;
@@ -329,7 +357,7 @@ mod test {
let min_pin_length = 8;
let min_pin_length_rp_ids = vec!["another.example.com".to_string()];
// The stored PIN or its length don't matter since we control the token.
storage::set_pin(&mut env, &[0x88; 16], 8).unwrap();
storage::set_pin(&mut env, 0, &[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_uv_auth_param = vec![
@@ -385,10 +413,14 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
// Increase min PIN, force PIN change.
let min_pin_length = 6;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
@@ -400,7 +432,7 @@ mod test {
let config_response = process_config(&mut env, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length));
assert_eq!(storage::has_force_pin_change(&mut env), Ok(true));
assert_eq!(storage::has_force_pin_change(&mut env, 0), Ok(true));
}
#[test]
@@ -408,10 +440,14 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xE3, 0x74, 0xF4, 0x27, 0xBE, 0x7D, 0x40, 0xB5, 0x71, 0xB6, 0xB4, 0x1A, 0xD2, 0xC1,
0x53, 0xD7,
@@ -431,7 +467,7 @@ mod test {
};
let config_response = process_config(&mut env, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(storage::has_force_pin_change(&mut env), Ok(true));
assert_eq!(storage::has_force_pin_change(&mut env, 0), Ok(true));
}
#[test]
@@ -439,8 +475,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::VendorPrototype,

View File

@@ -392,14 +392,18 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let management_data = vec![CredentialManagementSubCommand::GetCredsMetadata as u8];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
@@ -474,8 +478,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.rp_id = "another.example.com".to_string();
@@ -486,7 +494,7 @@ mod test {
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
@@ -568,8 +576,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
@@ -582,7 +594,7 @@ mod test {
storage::store_credential(&mut env, credential).unwrap();
}
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
@@ -649,8 +661,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.user_handle = vec![0x02];
@@ -664,7 +680,7 @@ mod test {
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
0x2B, 0x68,
@@ -751,8 +767,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
@@ -761,7 +781,7 @@ mod test {
storage::store_credential(&mut env, credential_source).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89,
0x9C, 0x64,
@@ -821,8 +841,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
@@ -831,7 +855,7 @@ mod test {
storage::store_credential(&mut env, credential_source).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD,
0x9D, 0xB7,
@@ -889,7 +913,7 @@ mod test {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,

View File

@@ -320,11 +320,12 @@ impl Ctap1Command {
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
}
ctap_state
.increment_global_signature_counter(env)
.increment_global_signature_counter(env, 0)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
let mut signature_data = ctap_state
.generate_auth_data(
env,
0,
&application,
Ctap1Command::USER_PRESENCE_INDICATOR_BYTE,
)
@@ -651,7 +652,7 @@ mod test {
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0))
.unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
let global_signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_signature_counter(
&mut env,
array_ref!(response, 1, 4),
@@ -684,7 +685,7 @@ mod test {
)
.unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
let global_signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_signature_counter(
&mut env,
array_ref!(response, 1, 4),

View File

@@ -87,7 +87,9 @@ impl LargeBlobs {
if offset != self.expected_next_offset {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ);
}
if storage::pin_hash(env)?.is_some() || storage::has_always_uv(env)? {
// TODO: Get the slot id from active token state when multi-PIN is enabled.
let slot_id = 0;
if storage::pin_hash(env, slot_id)?.is_some() || storage::has_always_uv(env)? {
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
@@ -147,8 +149,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blob = vec![
@@ -178,8 +184,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -240,8 +250,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -286,8 +300,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -332,8 +350,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blobs_params = AuthenticatorLargeBlobsParameters {
@@ -355,8 +377,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -383,8 +409,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let mut client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -392,7 +422,7 @@ mod test {
let mut large_blob = vec![0x1B; DATA_LEN];
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let mut large_blob_data = vec![0xFF; 32];
// Command constant and offset bytes.
large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]);

View File

@@ -551,7 +551,7 @@ pub struct CtapState {
impl CtapState {
pub fn new(env: &mut impl Env, now: CtapInstant) -> Self {
storage::init(env).ok().unwrap();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::new(env);
CtapState {
client_pin,
#[cfg(feature = "with_ctap1")]
@@ -569,10 +569,11 @@ impl CtapState {
pub fn increment_global_signature_counter(
&mut self,
env: &mut impl Env,
slot_id: usize,
) -> Result<(), Ctap2StatusCode> {
if env.customization().use_signature_counter() {
let increment = env.rng().gen_uniform_u32x8()[0] % 8 + 1;
storage::incr_global_signature_counter(env, increment)?;
storage::incr_global_signature_counter(env, slot_id, increment)?;
}
Ok(())
}
@@ -722,6 +723,7 @@ impl CtapState {
fn pin_uv_auth_precheck(
&mut self,
env: &mut impl Env,
slot_id: Option<usize>,
pin_uv_auth_param: &Option<Vec<u8>>,
pin_uv_auth_protocol: Option<PinUvAuthProtocol>,
channel: Channel,
@@ -730,7 +732,7 @@ impl CtapState {
// This case was added in FIDO 2.1.
if auth_param.is_empty() {
check_user_presence(env, channel)?;
if storage::pin_hash(env)?.is_none() {
if slot_id.is_none() || storage::pin_hash(env, slot_id.unwrap())?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
} else {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -776,7 +778,16 @@ impl CtapState {
enterprise_attestation,
} = make_credential_params;
self.pin_uv_auth_precheck(env, &pin_uv_auth_param, pin_uv_auth_protocol, channel)?;
// TODO: Get the slot id from active token state when multi-PIN is enabled.
let slot_id = Some(0);
self.pin_uv_auth_precheck(
env,
slot_id,
&pin_uv_auth_param,
pin_uv_auth_protocol,
channel,
)?;
// When more algorithms are supported, iterate and pick the first match.
let cred_param = get_preferred_cred_param(&pub_key_cred_params)
@@ -813,7 +824,7 @@ impl CtapState {
let mut flags = match pin_uv_auth_param {
Some(pin_uv_auth_param) => {
// This case is not mentioned in CTAP2.1, so we keep 2.0 logic.
if storage::pin_hash(env)?.is_none() {
if slot_id.is_some() && storage::pin_hash(env, slot_id.unwrap())?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
self.client_pin.verify_pin_uv_auth_token(
@@ -838,13 +849,19 @@ impl CtapState {
return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED);
}
// Corresponds to makeCredUvNotRqd set to true.
if options.rk && storage::pin_hash(env)?.is_some() {
if options.rk
&& slot_id.is_some()
&& storage::pin_hash(env, slot_id.unwrap())?.is_some()
{
return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED);
}
0x00
}
};
flags |= UP_FLAG | AT_FLAG;
// By this point, we should have already returned the suitable errors in all situations that
// slot_id is none.
let slot_id = slot_id.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
if let Some(exclude_list) = exclude_list {
@@ -901,6 +918,7 @@ impl CtapState {
// We decide on the algorithm early, but delay key creation since it takes time.
// We rather do that later so all intermediate checks may return faster.
let private_key = PrivateKey::new(env, algorithm);
// TODO: persist slot_id in the credential.
let credential_id = if options.rk {
let random_id = env.rng().gen_uniform_u8x32().to_vec();
let credential_source = PublicKeyCredentialSource {
@@ -937,7 +955,7 @@ impl CtapState {
)?
};
let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
let mut auth_data = self.generate_auth_data(env, slot_id, &rp_id_hash, flags)?;
auth_data.extend(&storage::aaguid(env)?);
// The length is fixed to 0x20 or 0x80 and fits one byte.
if credential_id.len() > 0xFF {
@@ -1157,7 +1175,16 @@ impl CtapState {
pin_uv_auth_protocol,
} = get_assertion_params;
self.pin_uv_auth_precheck(env, &pin_uv_auth_param, pin_uv_auth_protocol, channel)?;
// TODO: Get the slot id from active token state when multi-PIN is enabled.
let slot_id = Some(0);
self.pin_uv_auth_precheck(
env,
slot_id,
&pin_uv_auth_param,
pin_uv_auth_protocol,
channel,
)?;
if extensions.hmac_secret.is_some() && !options.up {
// The extension is actually supported, but we need user presence.
@@ -1170,7 +1197,7 @@ impl CtapState {
let mut flags = match pin_uv_auth_param {
Some(pin_uv_auth_param) => {
// This case is not mentioned in CTAP2.1, so we keep 2.0 logic.
if storage::pin_hash(env)?.is_none() {
if slot_id.is_some() && storage::pin_hash(env, slot_id.unwrap())?.is_none() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
self.client_pin.verify_pin_uv_auth_token(
@@ -1203,8 +1230,12 @@ impl CtapState {
if extensions.hmac_secret.is_some() || extensions.cred_blob {
flags |= ED_FLAG;
}
// By this point, we should have already returned the suitable errors in all situations that
// slot_id is none.
let slot_id = slot_id.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
// TODO: check slot_id in the credential matches.
let (credential, next_credential_keys) = if let Some(allow_list) = allow_list {
(
self.get_any_credential_from_allow_list(
@@ -1249,11 +1280,11 @@ impl CtapState {
self.client_pin.clear_token_flags();
}
self.increment_global_signature_counter(env)?;
self.increment_global_signature_counter(env, slot_id)?;
let assertion_input = AssertionInput {
client_data_hash,
auth_data: self.generate_auth_data(env, &rp_id_hash, flags)?,
auth_data: self.generate_auth_data(env, slot_id, &rp_id_hash, flags)?,
extensions,
has_uv,
};
@@ -1310,7 +1341,13 @@ impl CtapState {
(String::from("alwaysUv"), has_always_uv),
(String::from("credMgmt"), true),
(String::from("authnrCfg"), true),
(String::from("clientPin"), storage::pin_hash(env)?.is_some()),
// TODO: Return in another field the actual slot names that have a PIN set when
// multi-PIN feature is enabled, and omit this field. As this field is only filled
// in when multi-PIN feature is disabled, just check PIN at slot 0.
(
String::from("clientPin"),
storage::pin_hash(env, 0)?.is_some(),
),
(String::from("largeBlobs"), true),
(String::from("pinUvAuthToken"), true),
(String::from("setMinPINLength"), true),
@@ -1345,7 +1382,10 @@ impl CtapState {
max_serialized_large_blob_array: Some(
env.customization().max_large_blob_array_size() as u64,
),
force_pin_change: Some(storage::has_force_pin_change(env)?),
// TODO: Return in another field the actual slot names that need to force change
// the PIN when multi-PIN feature is enabled, and omit this field. As this field
// is only filled in when multi-PIN feature is disabled, just check PIN at slot 0.
force_pin_change: Some(storage::has_force_pin_change(env, 0)?),
min_pin_length: storage::min_pin_length(env)?,
firmware_version: None,
max_cred_blob_length: Some(env.customization().max_cred_blob_length() as u64),
@@ -1503,6 +1543,7 @@ impl CtapState {
pub fn generate_auth_data(
&self,
env: &mut impl Env,
slot_id: usize,
rp_id_hash: &[u8],
flag_byte: u8,
) -> Result<Vec<u8>, Ctap2StatusCode> {
@@ -1514,7 +1555,7 @@ impl CtapState {
let mut signature_counter = [0u8; 4];
BigEndian::write_u32(
&mut signature_counter,
storage::global_signature_counter(env)?,
storage::global_signature_counter(env, slot_id)?,
);
auth_data.extend(&signature_counter);
Ok(auth_data)
@@ -2111,12 +2152,16 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.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 client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let client_data_hash = [0xCD];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
@@ -2164,7 +2209,7 @@ mod test {
fn test_non_resident_process_make_credential_with_pin() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.options.rk = false;
@@ -2184,7 +2229,7 @@ mod test {
fn test_resident_process_make_credential_with_pin() {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let make_credential_params = create_minimal_make_credential_parameters();
let make_credential_response =
@@ -2209,7 +2254,7 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[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(PinUvAuthProtocol::V1);
@@ -2466,7 +2511,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None);
}
@@ -2709,7 +2754,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_assertion_response(get_assertion_response, vec![0x1D], signature_counter, None);
let credential = PublicKeyCredentialSource {
@@ -2878,7 +2923,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB,
];
@@ -2947,7 +2992,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
let expected_extension_cbor = [
0xA1, 0x68, 0x63, 0x72, 0x65, 0x64, 0x42, 0x6C, 0x6F, 0x62, 0x41, 0xCB,
];
@@ -3020,8 +3065,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x88; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
@@ -3050,7 +3099,7 @@ mod test {
ctap_state.client_pin = client_pin;
// The PIN length is outside of the test scope and most likely incorrect.
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let client_data_hash = vec![0xCD];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
@@ -3076,7 +3125,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_assertion_response_with_user(
get_assertion_response,
Some(user2),
@@ -3161,7 +3210,7 @@ mod test {
DUMMY_CHANNEL,
CtapInstant::new(0),
);
let signature_counter = storage::global_signature_counter(&mut env).unwrap();
let signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_assertion_response(
get_assertion_response,
vec![0x03],
@@ -3336,13 +3385,13 @@ mod test {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
let mut last_counter = storage::global_signature_counter(&mut env).unwrap();
let mut last_counter = storage::global_signature_counter(&mut env, 0).unwrap();
assert!(last_counter > 0);
for _ in 0..100 {
assert!(ctap_state
.increment_global_signature_counter(&mut env)
.increment_global_signature_counter(&mut env, 0)
.is_ok());
let next_counter = storage::global_signature_counter(&mut env).unwrap();
let next_counter = storage::global_signature_counter(&mut env, 0).unwrap();
assert!(next_counter > last_counter);
last_counter = next_counter;
}
@@ -3749,8 +3798,12 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let private_key = PrivateKey::new_ecdsa(&mut env);
let credential_source = PublicKeyCredentialSource {
@@ -3777,7 +3830,7 @@ mod test {
storage::store_credential(&mut env, credential).unwrap();
}
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,

View File

@@ -30,7 +30,7 @@ use alloc::vec::Vec;
use arrayref::array_ref;
use core::cmp;
use core::convert::TryInto;
use persistent_store::{fragment, StoreUpdate};
use persistent_store::{concat, fragment};
use rng256::Rng256;
use sk_cbor::cbor_array_vec;
@@ -237,9 +237,34 @@ pub fn new_creation_order(env: &mut impl Env) -> Result<u64, Ctap2StatusCode> {
Ok(max.unwrap_or(0).wrapping_add(1))
}
fn check_and_get_key_for_slot(
env: &mut impl Env,
slot_id: usize,
first_key: usize,
key_array: core::ops::Range<usize>,
) -> Result<usize, Ctap2StatusCode> {
if slot_id >= env.customization().slot_count() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(if slot_id == 0 {
first_key
} else {
key_array.start + slot_id - 1
})
}
/// Returns the global signature counter.
pub fn global_signature_counter(env: &mut impl Env) -> Result<u32, Ctap2StatusCode> {
match env.store().find(key::GLOBAL_SIGNATURE_COUNTER)? {
pub fn global_signature_counter(
env: &mut impl Env,
slot_id: usize,
) -> Result<u32, Ctap2StatusCode> {
let key = check_and_get_key_for_slot(
env,
slot_id,
key::FIRST_GLOBAL_SIGNATURE_COUNTER,
key::GLOBAL_SIGNATURE_COUNTER,
)?;
match env.store().find(key)? {
None => Ok(INITIAL_SIGNATURE_COUNTER),
Some(value) if value.len() == 4 => Ok(u32::from_ne_bytes(*array_ref!(&value, 0, 4))),
Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
@@ -249,13 +274,19 @@ pub fn global_signature_counter(env: &mut impl Env) -> Result<u32, Ctap2StatusCo
/// Increments the global signature counter.
pub fn incr_global_signature_counter(
env: &mut impl Env,
slot_id: usize,
increment: u32,
) -> Result<(), Ctap2StatusCode> {
let old_value = global_signature_counter(env)?;
let key = check_and_get_key_for_slot(
env,
slot_id,
key::FIRST_GLOBAL_SIGNATURE_COUNTER,
key::GLOBAL_SIGNATURE_COUNTER,
)?;
let old_value = global_signature_counter(env, slot_id)?;
// In hopes that servers handle the wrapping gracefully.
let new_value = old_value.wrapping_add(increment);
env.store()
.insert(key::GLOBAL_SIGNATURE_COUNTER, &new_value.to_ne_bytes())?;
env.store().insert(key, &new_value.to_ne_bytes())?;
Ok(())
}
@@ -273,9 +304,22 @@ pub fn cred_random_secret(env: &mut impl Env, has_uv: bool) -> Result<[u8; 32],
}
/// Reads the PIN properties and wraps them into PinProperties.
fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2StatusCode> {
let pin_properties = match env.store().find(key::PIN_PROPERTIES)? {
None => return Ok(None),
fn pin_properties(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<PinProperties>, Ctap2StatusCode> {
let pin_properties = match concat::read(env.store(), key::PIN_PROPERTIES, slot_id as u8)? {
None => {
// Backward compatibility: old implementation where there is only 1 PIN slot
// uses the entry with key `FIRST_PIN_PROPERTIES`.
if slot_id != 0 {
return Ok(None);
}
match env.store().find(key::FIRST_PIN_PROPERTIES)? {
None => return Ok(None),
Some(pin_properties) => pin_properties,
}
}
Some(pin_properties) => pin_properties,
};
const PROPERTIES_LENGTH: usize = PIN_AUTH_LENGTH + 1;
@@ -288,14 +332,30 @@ fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2Stat
}
}
/// Returns if PIN is set for at least one slot.
pub fn has_pin(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
for slot_id in 0..env.customization().slot_count() {
if pin_hash(env, slot_id)?.is_some() {
return Ok(true);
}
}
Ok(false)
}
/// Returns the PIN hash if defined.
pub fn pin_hash(env: &mut impl Env) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
Ok(pin_properties(env)?.map(|p| p.hash))
pub fn pin_hash(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
Ok(pin_properties(env, slot_id)?.map(|p| p.hash))
}
/// Returns the length of the currently set PIN if defined.
pub fn pin_code_point_length(env: &mut impl Env) -> Result<Option<u8>, Ctap2StatusCode> {
Ok(pin_properties(env)?.map(|p| p.code_point_length))
pub fn pin_code_point_length(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<u8>, Ctap2StatusCode> {
Ok(pin_properties(env, slot_id)?.map(|p| p.code_point_length))
}
/// Sets the PIN hash and length.
@@ -303,26 +363,31 @@ pub fn pin_code_point_length(env: &mut impl Env) -> Result<Option<u8>, Ctap2Stat
/// If it was already defined, it is updated.
pub fn set_pin(
env: &mut impl Env,
slot_id: usize,
pin_hash: &[u8; PIN_AUTH_LENGTH],
pin_code_point_length: u8,
) -> Result<(), Ctap2StatusCode> {
concat::delete(env.store(), key::FORCE_PIN_CHANGE, slot_id as u8)?;
if slot_id == 0 {
// Backward compatibility: data might be stored in this entry for slot 0 as well.
env.store().remove(key::FIRST_FORCE_PIN_CHANGE)?;
}
let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH];
pin_properties[0] = pin_code_point_length;
pin_properties[1..].clone_from_slice(pin_hash);
Ok(env.store().transaction(&[
StoreUpdate::Insert {
key: key::PIN_PROPERTIES,
value: &pin_properties[..],
},
StoreUpdate::Remove {
key: key::FORCE_PIN_CHANGE,
},
])?)
concat::write(
env.store(),
key::PIN_PROPERTIES,
slot_id as u8,
&pin_properties[..],
)?;
Ok(())
}
/// Returns the number of remaining PIN retries.
pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> {
match env.store().find(key::PIN_RETRIES)? {
pub fn pin_retries(env: &mut impl Env, slot_id: usize) -> Result<u8, Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
match env.store().find(key)? {
None => Ok(env.customization().max_pin_retries()),
Some(value) if value.len() == 1 => Ok(value[0]),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
@@ -330,18 +395,20 @@ pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> {
}
/// Decrements the number of remaining PIN retries.
pub fn decr_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
let old_value = pin_retries(env)?;
pub fn decr_pin_retries(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
let old_value = pin_retries(env, slot_id)?;
let new_value = old_value.saturating_sub(1);
if new_value != old_value {
env.store().insert(key::PIN_RETRIES, &[new_value])?;
env.store().insert(key, &[new_value])?;
}
Ok(())
}
/// Resets the number of remaining PIN retries.
pub fn reset_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
Ok(env.store().remove(key::PIN_RETRIES)?)
pub fn reset_pin_retries(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
Ok(env.store().remove(key)?)
}
/// Returns the minimum PIN length.
@@ -467,17 +534,34 @@ pub fn reset(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
}
/// Returns whether the PIN needs to be changed before its next usage.
pub fn has_force_pin_change(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
match env.store().find(key::FORCE_PIN_CHANGE)? {
None => Ok(false),
pub fn has_force_pin_change(env: &mut impl Env, slot_id: usize) -> Result<bool, Ctap2StatusCode> {
match concat::read(env.store(), key::FORCE_PIN_CHANGE, slot_id as u8)? {
None => {
if slot_id != 0 {
Ok(false)
} else {
// Backward compatibility: old implementation where there is only 1 PIN slot
// uses the entry with key `FIRST_FORCE_PIN_CHANGE`.
match env.store().find(key::FIRST_FORCE_PIN_CHANGE)? {
None => Ok(false),
Some(value) if value.is_empty() => Ok(true),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
}
Some(value) if value.is_empty() => Ok(true),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
/// Marks the PIN as outdated with respect to the new PIN policy.
pub fn force_pin_change(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
Ok(env.store().insert(key::FORCE_PIN_CHANGE, &[])?)
pub fn force_pin_change(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
Ok(concat::write(
env.store(),
key::FORCE_PIN_CHANGE,
slot_id as u8,
&[],
)?)
}
/// Returns whether enterprise attestation is enabled.
@@ -899,8 +983,8 @@ mod test {
let mut env = TestEnv::new();
// Pin hash is initially not set.
assert!(pin_hash(&mut env).unwrap().is_none());
assert!(pin_code_point_length(&mut env).unwrap().is_none());
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
// Setting the pin sets the pin hash.
let random_data = env.rng().gen_uniform_u8x32();
@@ -909,17 +993,23 @@ mod test {
let pin_hash_2 = *array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH);
let pin_length_1 = 4;
let pin_length_2 = 63;
set_pin(&mut env, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_1));
assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_1));
set_pin(&mut env, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_2));
assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_2));
set_pin(&mut env, 0, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_1));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_1)
);
set_pin(&mut env, 0, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_2));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_2)
);
// Resetting the storage resets the pin hash.
reset(&mut env).unwrap();
assert!(pin_hash(&mut env).unwrap().is_none());
assert!(pin_code_point_length(&mut env).unwrap().is_none());
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
}
#[test]
@@ -928,24 +1018,24 @@ mod test {
// The pin retries is initially at the maximum.
assert_eq!(
pin_retries(&mut env),
pin_retries(&mut env, 0),
Ok(env.customization().max_pin_retries())
);
// Decrementing the pin retries decrements the pin retries.
for retries in (0..env.customization().max_pin_retries()).rev() {
decr_pin_retries(&mut env).unwrap();
assert_eq!(pin_retries(&mut env), Ok(retries));
decr_pin_retries(&mut env, 0).unwrap();
assert_eq!(pin_retries(&mut env, 0), Ok(retries));
}
// Decrementing the pin retries after zero does not modify the pin retries.
decr_pin_retries(&mut env).unwrap();
assert_eq!(pin_retries(&mut env), Ok(0));
decr_pin_retries(&mut env, 0).unwrap();
assert_eq!(pin_retries(&mut env, 0), Ok(0));
// Resetting the pin retries resets the pin retries.
reset_pin_retries(&mut env).unwrap();
reset_pin_retries(&mut env, 0).unwrap();
assert_eq!(
pin_retries(&mut env),
pin_retries(&mut env, 0),
Ok(env.customization().max_pin_retries())
);
}
@@ -1088,11 +1178,17 @@ mod test {
let mut env = TestEnv::new();
let mut counter_value = 1;
assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value);
assert_eq!(
global_signature_counter(&mut env, 0).unwrap(),
counter_value
);
for increment in 1..10 {
assert!(incr_global_signature_counter(&mut env, increment).is_ok());
assert!(incr_global_signature_counter(&mut env, 0, increment).is_ok());
counter_value += increment;
assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value);
assert_eq!(
global_signature_counter(&mut env, 0).unwrap(),
counter_value
);
}
}
@@ -1100,11 +1196,11 @@ mod test {
fn test_force_pin_change() {
let mut env = TestEnv::new();
assert!(!has_force_pin_change(&mut env).unwrap());
assert_eq!(force_pin_change(&mut env), Ok(()));
assert!(has_force_pin_change(&mut env).unwrap());
assert_eq!(set_pin(&mut env, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env).unwrap());
assert!(!has_force_pin_change(&mut env, 0).unwrap());
assert_eq!(force_pin_change(&mut env, 0), Ok(()));
assert!(has_force_pin_change(&mut env, 0).unwrap());
assert_eq!(set_pin(&mut env, 0, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env, 0).unwrap());
}
#[test]

View File

@@ -73,6 +73,32 @@ make_partition! {
// - When adding a (non-persistent) key below this message, make sure its value is bigger or
// equal than NUM_PERSISTENT_KEYS.
// Start of key arrays for multi-PIN feature: these fields are separated for each slots, so
// a unique key is needed for each slot. However, we reuse the existing fields and rename them
// to `FIRST_{KEY_NAME}` so the upgrade is backward compatible.
// Depending on `Customization::slot_count()`, only a prefix of those keys is used.
/// Whether the PIN needs to be changed each slot.
///
/// The PIN needs to be changed if the slot exists and its data is empty.
FORCE_PIN_CHANGE = 984;
/// The number of PIN retries for each slot, except the first.
PIN_RETRIES = 985..992;
/// The PIN hash and length for each slot.
///
/// If a slot is absent, there is no PIN set for that slot. The first byte represents
/// the length, the following are an array with the hash.
PIN_PROPERTIES = 992;
/// The global signature counters for each slot, except the first.
///
/// If the entry is absent, the counter is 0.
GLOBAL_SIGNATURE_COUNTER = 993..1000;
// End of key arrays for multi-PIN feature.
/// Reserved for future credential-related objects.
///
/// In particular, additional credentials could be added there by reducing the lower bound of
@@ -97,8 +123,10 @@ make_partition! {
/// If this entry exists and is empty, enterprise attestation is enabled.
ENTERPRISE_ATTESTATION = 2039;
/// If this entry exists and is empty, the PIN needs to be changed.
FORCE_PIN_CHANGE = 2040;
/// Whether the PIN needs to be changed for the first slot.
///
/// The PIN needs to be changed if this entry exists and is empty.
FIRST_FORCE_PIN_CHANGE = 2040;
/// The secret of the CredRandom feature.
CRED_RANDOM_SECRET = 2041;
@@ -111,24 +139,22 @@ make_partition! {
/// If the entry is absent, the minimum PIN length is `Customization::default_min_pin_length()`.
MIN_PIN_LENGTH = 2043;
/// The number of PIN retries.
/// The number of PIN retries for the first slot.
///
/// If the entry is absent, the number of PIN retries is `Customization::max_pin_retries()`.
PIN_RETRIES = 2044;
FIRST_PIN_RETRIES = 2044;
/// The PIN hash and length.
/// The PIN hash and length for the first slot.
///
/// If the entry is absent, there is no PIN set. The first byte represents
/// the length, the following are an array with the hash.
PIN_PROPERTIES = 2045;
FIRST_PIN_PROPERTIES = 2045;
/// Reserved for the key store implementation of the environment.
_RESERVED_KEY_STORE = 2046;
/// The global signature counter.
///
/// If the entry is absent, the counter is 0.
GLOBAL_SIGNATURE_COUNTER = 2047;
/// The global signature counter for the first slot.
FIRST_GLOBAL_SIGNATURE_COUNTER = 2047;
}
#[cfg(test)]

View File

@@ -33,6 +33,7 @@ pub struct TestCustomization {
max_large_blob_array_size: usize,
max_rp_ids_length: usize,
max_supported_resident_keys: usize,
slot_count: usize,
}
impl TestCustomization {
@@ -112,6 +113,10 @@ impl Customization for TestCustomization {
fn max_supported_resident_keys(&self) -> usize {
self.max_supported_resident_keys
}
fn slot_count(&self) -> usize {
self.slot_count
}
}
impl From<CustomizationImpl> for TestCustomization {
@@ -132,6 +137,7 @@ impl From<CustomizationImpl> for TestCustomization {
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
slot_count,
} = c;
let default_min_pin_length_rp_ids = default_min_pin_length_rp_ids
@@ -160,6 +166,7 @@ impl From<CustomizationImpl> for TestCustomization {
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
slot_count,
}
}
}