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:
@@ -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 = []
|
||||
|
||||
242
libraries/persistent_store/src/concat.rs
Normal file
242
libraries/persistent_store/src/concat.rs
Normal 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())));
|
||||
}
|
||||
}
|
||||
@@ -363,6 +363,7 @@ extern crate alloc;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod buffer;
|
||||
pub mod concat;
|
||||
#[cfg(feature = "std")]
|
||||
mod driver;
|
||||
#[cfg(feature = "std")]
|
||||
|
||||
Reference in New Issue
Block a user