Skip to content

Commit

Permalink
LargeBlob trait supports buffering choice
Browse files Browse the repository at this point in the history
Also makes LargeBlob a proper stateful command that loses state when
interleaved.
  • Loading branch information
kaczmarczyck committed Apr 5, 2024
1 parent 3faea59 commit 87be49e
Show file tree
Hide file tree
Showing 5 changed files with 422 additions and 180 deletions.
53 changes: 37 additions & 16 deletions libraries/opensk/src/api/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,48 @@ pub trait Persist {
self.insert(keys::MIN_PIN_LENGTH_RP_IDS, min_pin_length_rp_ids_bytes)
}

// TODO rework LargeBlob
// Problem 1: Env should be allowed to choose whether to buffer in memory or persist
// Otherwise small RAM devices have limited large blog size.
// Problem 2: LargeBlob is a stateful command, but doesn't use the safeguard and infrastructure
// of Stateful command. It has to be migrated there.
// While doing that, check if PinUvAuthToken timers and StatefulCommand timeouts are working
// together correctly.
/// Prepares writing a new large blob.
///
/// Returns a buffer that is returned to other API calls for potential usage.
fn init_large_blob(&mut self, expected_length: usize) -> CtapResult<Vec<u8>> {
Ok(Vec::with_capacity(expected_length))
}

/// Writes a large blob chunk to the buffer.
///
/// This can be the passed in buffer, or a custom solution.
fn write_large_blob_chunk(
&mut self,
offset: usize,
chunk: &mut Vec<u8>,
buffer: &mut Vec<u8>,
) -> CtapResult<()> {
if buffer.len() != offset {
// This should be caught on CTAP level.
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
buffer.append(chunk);
Ok(())
}

/// Reads the byte vector stored as the serialized large blobs array.
///
/// If too few bytes exist at that offset, return the maximum number
/// available. This includes cases of offset being beyond the stored array.
///
/// If no large blob is committed to the store, get responds as if an empty
/// CBOR array (0x80) was written, together with the 16 byte prefix of its
/// SHA256, to a total length of 17 byte (which is the shortest legitimate
/// large blob entry possible).
fn get_large_blob_array(
/// The buffer is passed in when writing is in process.
fn get_large_blob(
&self,
mut offset: usize,
byte_count: usize,
buffer: Option<&Vec<u8>>,
) -> CtapResult<Option<Vec<u8>>> {
if let Some(buffer) = buffer {
let start = cmp::min(offset, buffer.len());
let end = offset.saturating_add(byte_count);
let end = cmp::min(end, buffer.len());
return Ok(Some(buffer[start..end].to_vec()));
}
let mut result = Vec::with_capacity(byte_count);
for key in keys::LARGE_BLOB_SHARDS {
if offset >= VALUE_LENGTH {
Expand All @@ -276,12 +297,12 @@ pub trait Persist {
}

/// Sets a byte vector as the serialized large blobs array.
fn commit_large_blob_array(&mut self, large_blob_array: &[u8]) -> CtapResult<()> {
debug_assert!(large_blob_array.len() <= keys::LARGE_BLOB_SHARDS.len() * VALUE_LENGTH);
fn commit_large_blob_array(&mut self, buffer: &Vec<u8>) -> CtapResult<()> {
debug_assert!(buffer.len() <= keys::LARGE_BLOB_SHARDS.len() * VALUE_LENGTH);
let mut offset = 0;
for key in keys::LARGE_BLOB_SHARDS {
let cur_len = cmp::min(large_blob_array.len().saturating_sub(offset), VALUE_LENGTH);
let slice = &large_blob_array[offset..][..cur_len];
let cur_len = cmp::min(buffer.len().saturating_sub(offset), VALUE_LENGTH);
let slice = &buffer[offset..][..cur_len];
if slice.is_empty() {
self.remove(key)?;
} else {
Expand Down
34 changes: 4 additions & 30 deletions libraries/opensk/src/ctap/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ use core::convert::TryFrom;
use sk_cbor as cbor;
use sk_cbor::destructure_cbor_map;

// This constant is a consequence of the structure of messages.
const MIN_LARGE_BLOB_LEN: usize = 17;

// CTAP specification (version 20190130) section 6.1
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
Expand Down Expand Up @@ -399,27 +396,15 @@ impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
.map(PinUvAuthProtocol::try_from)
.transpose()?;

if get.is_none() && set.is_none() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
if get.is_some() && set.is_some() {
if get.is_some() == set.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
if get.is_some()
&& (length.is_some() || pin_uv_auth_param.is_some() || pin_uv_auth_protocol.is_some())
{
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
if set.is_some() && offset == 0 {
match length {
None => return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
Some(len) if len < MIN_LARGE_BLOB_LEN => {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}
Some(_) => (),
}
}
if set.is_some() && offset != 0 && length.is_some() {
if set.is_some() && ((offset == 0) != length.is_some()) {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}

Expand Down Expand Up @@ -761,6 +746,8 @@ mod test {

#[test]
fn test_from_cbor_large_blobs_parameters() {
const MIN_LARGE_BLOB_LEN: usize = 17;

// successful get
let cbor_value = cbor_map! {
0x01 => 2,
Expand Down Expand Up @@ -875,19 +862,6 @@ mod test {
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);

// failing with length smaller than minimum
let cbor_value = cbor_map! {
0x02 => vec! [0x5E],
0x03 => 0,
0x04 => MIN_LARGE_BLOB_LEN as u64 - 1,
0x05 => vec! [0xA9],
0x06 => 1,
};
assert_eq!(
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);

// failing with non-zero offset and length present
let cbor_value = cbor_map! {
0x02 => vec! [0x5E],
Expand Down
Loading

0 comments on commit 87be49e

Please sign in to comment.