Skip to content

Commit

Permalink
Merge 96e52c2 into 4442998
Browse files Browse the repository at this point in the history
  • Loading branch information
hcyang-google committed Aug 23, 2022
2 parents 4442998 + 96e52c2 commit 979059c
Show file tree
Hide file tree
Showing 15 changed files with 814 additions and 232 deletions.
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 0 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
5 changes: 5 additions & 0 deletions libraries/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 242 additions & 0 deletions libraries/persistent_store/src/concat.rs
Original file line number Diff line number Diff line change
@@ -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())));
}
}
1 change: 1 addition & 0 deletions libraries/persistent_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ extern crate alloc;

#[cfg(feature = "std")]
mod buffer;
pub mod concat;
#[cfg(feature = "std")]
mod driver;
#[cfg(feature = "std")]
Expand Down
26 changes: 26 additions & 0 deletions src/api/customization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 979059c

Please sign in to comment.