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

@@ -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")]