@@ -24,6 +24,7 @@ use alloc::vec::Vec;
|
|||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
|
||||||
/// Represents a sequence of keys.
|
/// Represents a sequence of keys.
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
pub trait Keys {
|
pub trait Keys {
|
||||||
/// Returns the number of keys.
|
/// Returns the number of keys.
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use super::data_formats::{
|
|||||||
};
|
};
|
||||||
use super::key_material;
|
use super::key_material;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
|
use super::storage::MAX_LARGE_BLOB_ARRAY_SIZE;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
@@ -33,6 +34,9 @@ use core::convert::TryFrom;
|
|||||||
// You might also want to set the max credential size in process_get_info then.
|
// You might also want to set the max credential size in process_get_info then.
|
||||||
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
|
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
|
||||||
|
|
||||||
|
// This constant is a consequence of the structure of messages.
|
||||||
|
const MIN_LARGE_BLOB_LEN: usize = 17;
|
||||||
|
|
||||||
// CTAP specification (version 20190130) section 6.1
|
// CTAP specification (version 20190130) section 6.1
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
@@ -44,8 +48,8 @@ pub enum Command {
|
|||||||
AuthenticatorGetNextAssertion,
|
AuthenticatorGetNextAssertion,
|
||||||
AuthenticatorCredentialManagement(AuthenticatorCredentialManagementParameters),
|
AuthenticatorCredentialManagement(AuthenticatorCredentialManagementParameters),
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
|
AuthenticatorLargeBlobs(AuthenticatorLargeBlobsParameters),
|
||||||
AuthenticatorConfig(AuthenticatorConfigParameters),
|
AuthenticatorConfig(AuthenticatorConfigParameters),
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
|
|
||||||
// Vendor specific commands
|
// Vendor specific commands
|
||||||
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
||||||
}
|
}
|
||||||
@@ -56,8 +60,6 @@ impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this `allow(dead_code)` once the constants are used.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl Command {
|
impl Command {
|
||||||
const AUTHENTICATOR_MAKE_CREDENTIAL: u8 = 0x01;
|
const AUTHENTICATOR_MAKE_CREDENTIAL: u8 = 0x01;
|
||||||
const AUTHENTICATOR_GET_ASSERTION: u8 = 0x02;
|
const AUTHENTICATOR_GET_ASSERTION: u8 = 0x02;
|
||||||
@@ -65,8 +67,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
||||||
const AUTHENTICATOR_RESET: u8 = 0x07;
|
const AUTHENTICATOR_RESET: u8 = 0x07;
|
||||||
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
||||||
// TODO(kaczmarczyck) use or remove those constants
|
// Implement Bio Enrollment when your hardware supports biometrics.
|
||||||
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
const _AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
||||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0x0A;
|
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0x0A;
|
||||||
const AUTHENTICATOR_SELECTION: u8 = 0x0B;
|
const AUTHENTICATOR_SELECTION: u8 = 0x0B;
|
||||||
const AUTHENTICATOR_LARGE_BLOBS: u8 = 0x0C;
|
const AUTHENTICATOR_LARGE_BLOBS: u8 = 0x0C;
|
||||||
@@ -123,6 +125,12 @@ impl Command {
|
|||||||
// Parameters are ignored.
|
// Parameters are ignored.
|
||||||
Ok(Command::AuthenticatorSelection)
|
Ok(Command::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
Command::AUTHENTICATOR_LARGE_BLOBS => {
|
||||||
|
let decoded_cbor = cbor::read(&bytes[1..])?;
|
||||||
|
Ok(Command::AuthenticatorLargeBlobs(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(decoded_cbor)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
Command::AUTHENTICATOR_CONFIG => {
|
Command::AUTHENTICATOR_CONFIG => {
|
||||||
let decoded_cbor = cbor::read(&bytes[1..])?;
|
let decoded_cbor = cbor::read(&bytes[1..])?;
|
||||||
Ok(Command::AuthenticatorConfig(
|
Ok(Command::AuthenticatorConfig(
|
||||||
@@ -351,6 +359,81 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
|
pub struct AuthenticatorLargeBlobsParameters {
|
||||||
|
pub get: Option<usize>,
|
||||||
|
pub set: Option<Vec<u8>>,
|
||||||
|
pub offset: usize,
|
||||||
|
pub length: Option<usize>,
|
||||||
|
pub pin_uv_auth_param: Option<Vec<u8>>,
|
||||||
|
pub pin_uv_auth_protocol: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorLargeBlobsParameters {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => get,
|
||||||
|
2 => set,
|
||||||
|
3 => offset,
|
||||||
|
4 => length,
|
||||||
|
5 => pin_uv_auth_param,
|
||||||
|
6 => pin_uv_auth_protocol,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// careful: some missing parameters here are CTAP1_ERR_INVALID_PARAMETER
|
||||||
|
let get = get.map(extract_unsigned).transpose()?.map(|u| u as usize);
|
||||||
|
let set = set.map(extract_byte_string).transpose()?;
|
||||||
|
let offset =
|
||||||
|
extract_unsigned(offset.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?)? as usize;
|
||||||
|
let length = length
|
||||||
|
.map(extract_unsigned)
|
||||||
|
.transpose()?
|
||||||
|
.map(|u| u as usize);
|
||||||
|
let pin_uv_auth_param = pin_uv_auth_param.map(extract_byte_string).transpose()?;
|
||||||
|
let pin_uv_auth_protocol = pin_uv_auth_protocol.map(extract_unsigned).transpose()?;
|
||||||
|
|
||||||
|
if get.is_none() && set.is_none() {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
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 > MAX_LARGE_BLOB_ARRAY_SIZE => {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_LARGE_BLOB_STORAGE_FULL)
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AuthenticatorLargeBlobsParameters {
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
pin_uv_auth_param,
|
||||||
|
pin_uv_auth_protocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
pub struct AuthenticatorConfigParameters {
|
pub struct AuthenticatorConfigParameters {
|
||||||
pub sub_command: ConfigSubCommand,
|
pub sub_command: ConfigSubCommand,
|
||||||
@@ -698,6 +781,149 @@ mod test {
|
|||||||
assert_eq!(command, Ok(Command::AuthenticatorSelection));
|
assert_eq!(command, Ok(Command::AuthenticatorSelection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_cbor_large_blobs_parameters() {
|
||||||
|
// successful get
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => 2,
|
||||||
|
3 => 4,
|
||||||
|
};
|
||||||
|
let returned_large_blobs_parameters =
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap();
|
||||||
|
let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: Some(2),
|
||||||
|
set: None,
|
||||||
|
offset: 4,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
returned_large_blobs_parameters,
|
||||||
|
expected_large_blobs_parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
// successful first set
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 0,
|
||||||
|
4 => MIN_LARGE_BLOB_LEN as u64,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
let returned_large_blobs_parameters =
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap();
|
||||||
|
let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(vec![0x5E]),
|
||||||
|
offset: 0,
|
||||||
|
length: Some(MIN_LARGE_BLOB_LEN),
|
||||||
|
pin_uv_auth_param: Some(vec![0xA9]),
|
||||||
|
pin_uv_auth_protocol: Some(1),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
returned_large_blobs_parameters,
|
||||||
|
expected_large_blobs_parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
// successful next set
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 1,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
let returned_large_blobs_parameters =
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value).unwrap();
|
||||||
|
let expected_large_blobs_parameters = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(vec![0x5E]),
|
||||||
|
offset: 1,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: Some(vec![0xA9]),
|
||||||
|
pin_uv_auth_protocol: Some(1),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
returned_large_blobs_parameters,
|
||||||
|
expected_large_blobs_parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
// failing with neither get nor set
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
3 => 4,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// failing with get and set
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => 2,
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 4,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// failing with get and length
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => 2,
|
||||||
|
3 => 4,
|
||||||
|
4 => MIN_LARGE_BLOB_LEN as u64,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// failing with zero offset and no length present
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 0,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// failing with length smaller than minimum
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 0,
|
||||||
|
4 => MIN_LARGE_BLOB_LEN as u64 - 1,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 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! {
|
||||||
|
2 => vec! [0x5E],
|
||||||
|
3 => 4,
|
||||||
|
4 => MIN_LARGE_BLOB_LEN as u64,
|
||||||
|
5 => vec! [0xA9],
|
||||||
|
6 => 1,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorLargeBlobsParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vendor_configure() {
|
fn test_vendor_configure() {
|
||||||
// Incomplete command
|
// Incomplete command
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ fn enumerate_credentials_response(
|
|||||||
public_key: Some(public_key),
|
public_key: Some(public_key),
|
||||||
total_credentials,
|
total_credentials,
|
||||||
cred_protect: cred_protect_policy,
|
cred_protect: cred_protect_policy,
|
||||||
// TODO(kaczmarczyck) add when largeBlobKey is implemented
|
// TODO(kaczmarczyck) add when largeBlobKey extension is implemented
|
||||||
large_blob_key: None,
|
large_blob_key: None,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|||||||
422
src/ctap/large_blobs.rs
Normal file
422
src/ctap/large_blobs.rs
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
// Copyright 2020-2021 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.
|
||||||
|
|
||||||
|
use super::check_pin_uv_auth_protocol;
|
||||||
|
use super::command::AuthenticatorLargeBlobsParameters;
|
||||||
|
use super::pin_protocol_v1::{PinPermission, PinProtocolV1};
|
||||||
|
use super::response::{AuthenticatorLargeBlobsResponse, ResponseData};
|
||||||
|
use super::status_code::Ctap2StatusCode;
|
||||||
|
use super::storage::PersistentStore;
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use crypto::sha256::Sha256;
|
||||||
|
use crypto::Hash256;
|
||||||
|
|
||||||
|
/// This is maximum message size supported by the authenticator. 1024 is the default.
|
||||||
|
/// Increasing this values can speed up commands with longer responses, but lead to
|
||||||
|
/// packets dropping or unexpected failures.
|
||||||
|
pub const MAX_MSG_SIZE: usize = 1024;
|
||||||
|
/// The length of the truncated hash that as appended to the large blob data.
|
||||||
|
const TRUNCATED_HASH_LEN: usize = 16;
|
||||||
|
|
||||||
|
pub struct LargeBlobs {
|
||||||
|
buffer: Vec<u8>,
|
||||||
|
expected_length: usize,
|
||||||
|
expected_next_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the logic for the AuthenticatorLargeBlobs command and keeps its state.
|
||||||
|
impl LargeBlobs {
|
||||||
|
pub fn new() -> LargeBlobs {
|
||||||
|
LargeBlobs {
|
||||||
|
buffer: Vec::new(),
|
||||||
|
expected_length: 0,
|
||||||
|
expected_next_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process the large blob command.
|
||||||
|
pub fn process_command(
|
||||||
|
&mut self,
|
||||||
|
persistent_store: &mut PersistentStore,
|
||||||
|
pin_protocol_v1: &mut PinProtocolV1,
|
||||||
|
large_blobs_params: AuthenticatorLargeBlobsParameters,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
let AuthenticatorLargeBlobsParameters {
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
pin_uv_auth_param,
|
||||||
|
pin_uv_auth_protocol,
|
||||||
|
} = large_blobs_params;
|
||||||
|
|
||||||
|
const MAX_FRAGMENT_LENGTH: usize = MAX_MSG_SIZE - 64;
|
||||||
|
|
||||||
|
if let Some(get) = get {
|
||||||
|
if get > MAX_FRAGMENT_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
|
||||||
|
}
|
||||||
|
let config = persistent_store.get_large_blob_array(offset, get)?;
|
||||||
|
return Ok(ResponseData::AuthenticatorLargeBlobs(Some(
|
||||||
|
AuthenticatorLargeBlobsResponse { config },
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut set) = set {
|
||||||
|
if set.len() > MAX_FRAGMENT_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_LENGTH);
|
||||||
|
}
|
||||||
|
if offset == 0 {
|
||||||
|
// Checks for offset and length are already done in command.
|
||||||
|
self.expected_length =
|
||||||
|
length.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
|
||||||
|
self.expected_next_offset = 0;
|
||||||
|
}
|
||||||
|
if offset != self.expected_next_offset {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ);
|
||||||
|
}
|
||||||
|
if persistent_store.pin_hash()?.is_some() {
|
||||||
|
let pin_uv_auth_param =
|
||||||
|
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
|
||||||
|
// TODO(kaczmarczyck) Error codes for PIN protocol differ across commands.
|
||||||
|
// Change to Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED for None?
|
||||||
|
check_pin_uv_auth_protocol(pin_uv_auth_protocol)?;
|
||||||
|
pin_protocol_v1.has_permission(PinPermission::LargeBlobWrite)?;
|
||||||
|
let mut message = vec![0xFF; 32];
|
||||||
|
message.extend(&[0x0C, 0x00]);
|
||||||
|
let mut offset_bytes = [0u8; 4];
|
||||||
|
LittleEndian::write_u32(&mut offset_bytes, offset as u32);
|
||||||
|
message.extend(&offset_bytes);
|
||||||
|
message.extend(&Sha256::hash(set.as_slice()));
|
||||||
|
if !pin_protocol_v1.verify_pin_auth_token(&message, &pin_uv_auth_param) {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if offset + set.len() > self.expected_length {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
if offset == 0 {
|
||||||
|
self.buffer = Vec::with_capacity(self.expected_length);
|
||||||
|
}
|
||||||
|
self.buffer.append(&mut set);
|
||||||
|
self.expected_next_offset = self.buffer.len();
|
||||||
|
if self.expected_next_offset == self.expected_length {
|
||||||
|
self.expected_length = 0;
|
||||||
|
self.expected_next_offset = 0;
|
||||||
|
// Must be a positive number.
|
||||||
|
let buffer_hash_index = self.buffer.len() - TRUNCATED_HASH_LEN;
|
||||||
|
if Sha256::hash(&self.buffer[..buffer_hash_index])[..TRUNCATED_HASH_LEN]
|
||||||
|
!= self.buffer[buffer_hash_index..]
|
||||||
|
{
|
||||||
|
self.buffer = Vec::new();
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
|
}
|
||||||
|
persistent_store.commit_large_blob_array(&self.buffer)?;
|
||||||
|
self.buffer = Vec::new();
|
||||||
|
}
|
||||||
|
return Ok(ResponseData::AuthenticatorLargeBlobs(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be unreachable, since the command has either get or set.
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_get_empty() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
let large_blob = vec![
|
||||||
|
0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5,
|
||||||
|
0x7A, 0x6D, 0x3C,
|
||||||
|
];
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: Some(large_blob.len()),
|
||||||
|
set: None,
|
||||||
|
offset: 0,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
match large_blobs_response.unwrap() {
|
||||||
|
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
||||||
|
assert_eq!(response.config, large_blob);
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid response type"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_commit_and_get() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
const BLOB_LEN: usize = 200;
|
||||||
|
const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN;
|
||||||
|
let mut large_blob = vec![0x1B; DATA_LEN];
|
||||||
|
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[..BLOB_LEN / 2].to_vec()),
|
||||||
|
offset: 0,
|
||||||
|
length: Some(BLOB_LEN),
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[BLOB_LEN / 2..].to_vec()),
|
||||||
|
offset: BLOB_LEN / 2,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: Some(BLOB_LEN),
|
||||||
|
set: None,
|
||||||
|
offset: 0,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
match large_blobs_response.unwrap() {
|
||||||
|
ResponseData::AuthenticatorLargeBlobs(Some(response)) => {
|
||||||
|
assert_eq!(response.config, large_blob);
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid response type"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_commit_unexpected_offset() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
const BLOB_LEN: usize = 200;
|
||||||
|
const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN;
|
||||||
|
let mut large_blob = vec![0x1B; DATA_LEN];
|
||||||
|
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[..BLOB_LEN / 2].to_vec()),
|
||||||
|
offset: 0,
|
||||||
|
length: Some(BLOB_LEN),
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[BLOB_LEN / 2..].to_vec()),
|
||||||
|
// The offset is 1 too big.
|
||||||
|
offset: BLOB_LEN / 2 + 1,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_commit_unexpected_length() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
const BLOB_LEN: usize = 200;
|
||||||
|
const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN;
|
||||||
|
let mut large_blob = vec![0x1B; DATA_LEN];
|
||||||
|
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[..BLOB_LEN / 2].to_vec()),
|
||||||
|
offset: 0,
|
||||||
|
// The length is 1 too small.
|
||||||
|
length: Some(BLOB_LEN - 1),
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob[BLOB_LEN / 2..].to_vec()),
|
||||||
|
offset: BLOB_LEN / 2,
|
||||||
|
length: None,
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_commit_unexpected_hash() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
const BLOB_LEN: usize = 20;
|
||||||
|
// This blob does not have an appropriate hash.
|
||||||
|
let large_blob = vec![0x1B; BLOB_LEN];
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob.to_vec()),
|
||||||
|
offset: 0,
|
||||||
|
length: Some(BLOB_LEN),
|
||||||
|
pin_uv_auth_param: None,
|
||||||
|
pin_uv_auth_protocol: None,
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_command_commit_with_pin() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
let key_agreement_key = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||||
|
let pin_uv_auth_token = [0x55; 32];
|
||||||
|
let mut pin_protocol_v1 = PinProtocolV1::new_test(key_agreement_key, pin_uv_auth_token);
|
||||||
|
let mut large_blobs = LargeBlobs::new();
|
||||||
|
|
||||||
|
const BLOB_LEN: usize = 20;
|
||||||
|
const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN;
|
||||||
|
let mut large_blob = vec![0x1B; DATA_LEN];
|
||||||
|
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
|
||||||
|
|
||||||
|
persistent_store.set_pin(&[0u8; 16], 4).unwrap();
|
||||||
|
let pin_uv_auth_param = Some(vec![
|
||||||
|
0x68, 0x0C, 0x3F, 0x6A, 0x62, 0x47, 0xE6, 0x7C, 0x23, 0x1F, 0x79, 0xE3, 0xDC, 0x6D,
|
||||||
|
0xC3, 0xDE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let large_blobs_params = AuthenticatorLargeBlobsParameters {
|
||||||
|
get: None,
|
||||||
|
set: Some(large_blob),
|
||||||
|
offset: 0,
|
||||||
|
length: Some(BLOB_LEN),
|
||||||
|
pin_uv_auth_param,
|
||||||
|
pin_uv_auth_protocol: Some(1),
|
||||||
|
};
|
||||||
|
let large_blobs_response = large_blobs.process_command(
|
||||||
|
&mut persistent_store,
|
||||||
|
&mut pin_protocol_v1,
|
||||||
|
large_blobs_params,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
large_blobs_response,
|
||||||
|
Ok(ResponseData::AuthenticatorLargeBlobs(None))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ mod ctap1;
|
|||||||
pub mod data_formats;
|
pub mod data_formats;
|
||||||
pub mod hid;
|
pub mod hid;
|
||||||
mod key_material;
|
mod key_material;
|
||||||
|
mod large_blobs;
|
||||||
mod pin_protocol_v1;
|
mod pin_protocol_v1;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
pub mod status_code;
|
pub mod status_code;
|
||||||
@@ -41,6 +42,7 @@ use self::data_formats::{
|
|||||||
SignatureAlgorithm,
|
SignatureAlgorithm,
|
||||||
};
|
};
|
||||||
use self::hid::ChannelID;
|
use self::hid::ChannelID;
|
||||||
|
use self::large_blobs::{LargeBlobs, MAX_MSG_SIZE};
|
||||||
use self::pin_protocol_v1::{PinPermission, PinProtocolV1};
|
use self::pin_protocol_v1::{PinPermission, PinProtocolV1};
|
||||||
use self::response::{
|
use self::response::{
|
||||||
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
||||||
@@ -293,6 +295,7 @@ pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(
|
|||||||
pub u2f_up_state: U2fUserPresenceState,
|
pub u2f_up_state: U2fUserPresenceState,
|
||||||
// The state initializes to Reset and its timeout, and never goes back to Reset.
|
// The state initializes to Reset and its timeout, and never goes back to Reset.
|
||||||
stateful_command_permission: StatefulPermission,
|
stateful_command_permission: StatefulPermission,
|
||||||
|
large_blobs: LargeBlobs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence>
|
||||||
@@ -318,6 +321,7 @@ where
|
|||||||
Duration::from_ms(TOUCH_TIMEOUT_MS),
|
Duration::from_ms(TOUCH_TIMEOUT_MS),
|
||||||
),
|
),
|
||||||
stateful_command_permission: StatefulPermission::new_reset(now),
|
stateful_command_permission: StatefulPermission::new_reset(now),
|
||||||
|
large_blobs: LargeBlobs::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,12 +488,16 @@ where
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Command::AuthenticatorSelection => self.process_selection(cid),
|
Command::AuthenticatorSelection => self.process_selection(cid),
|
||||||
|
Command::AuthenticatorLargeBlobs(params) => self.large_blobs.process_command(
|
||||||
|
&mut self.persistent_store,
|
||||||
|
&mut self.pin_protocol_v1,
|
||||||
|
params,
|
||||||
|
),
|
||||||
Command::AuthenticatorConfig(params) => process_config(
|
Command::AuthenticatorConfig(params) => process_config(
|
||||||
&mut self.persistent_store,
|
&mut self.persistent_store,
|
||||||
&mut self.pin_protocol_v1,
|
&mut self.pin_protocol_v1,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
|
||||||
// Vendor specific commands
|
// Vendor specific commands
|
||||||
Command::AuthenticatorVendorConfigure(params) => {
|
Command::AuthenticatorVendorConfigure(params) => {
|
||||||
self.process_vendor_configure(params, cid)
|
self.process_vendor_configure(params, cid)
|
||||||
@@ -1026,7 +1034,7 @@ where
|
|||||||
]),
|
]),
|
||||||
aaguid: self.persistent_store.aaguid()?,
|
aaguid: self.persistent_store.aaguid()?,
|
||||||
options: Some(options_map),
|
options: Some(options_map),
|
||||||
max_msg_size: Some(1024),
|
max_msg_size: Some(MAX_MSG_SIZE as u64),
|
||||||
pin_protocols: Some(vec![PIN_PROTOCOL_VERSION]),
|
pin_protocols: Some(vec![PIN_PROTOCOL_VERSION]),
|
||||||
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
||||||
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
||||||
@@ -1259,7 +1267,7 @@ mod test {
|
|||||||
"setMinPINLength" => true,
|
"setMinPINLength" => true,
|
||||||
"forcePINChange" => false,
|
"forcePINChange" => false,
|
||||||
},
|
},
|
||||||
0x05 => 1024,
|
0x05 => MAX_MSG_SIZE as u64,
|
||||||
0x06 => cbor_array_vec![vec![1]],
|
0x06 => cbor_array_vec![vec![1]],
|
||||||
0x07 => MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
0x07 => MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
||||||
0x08 => CREDENTIAL_ID_SIZE as u64,
|
0x08 => CREDENTIAL_ID_SIZE as u64,
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ pub enum PinPermission {
|
|||||||
GetAssertion = 0x02,
|
GetAssertion = 0x02,
|
||||||
CredentialManagement = 0x04,
|
CredentialManagement = 0x04,
|
||||||
BioEnrollment = 0x08,
|
BioEnrollment = 0x08,
|
||||||
PlatformConfiguration = 0x10,
|
LargeBlobWrite = 0x10,
|
||||||
AuthenticatorConfiguration = 0x20,
|
AuthenticatorConfiguration = 0x20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub enum ResponseData {
|
|||||||
AuthenticatorReset,
|
AuthenticatorReset,
|
||||||
AuthenticatorCredentialManagement(Option<AuthenticatorCredentialManagementResponse>),
|
AuthenticatorCredentialManagement(Option<AuthenticatorCredentialManagementResponse>),
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
|
AuthenticatorLargeBlobs(Option<AuthenticatorLargeBlobsResponse>),
|
||||||
// TODO(kaczmarczyck) dummy, extend
|
// TODO(kaczmarczyck) dummy, extend
|
||||||
AuthenticatorConfig,
|
AuthenticatorConfig,
|
||||||
AuthenticatorVendor(AuthenticatorVendorResponse),
|
AuthenticatorVendor(AuthenticatorVendorResponse),
|
||||||
@@ -49,6 +50,7 @@ impl From<ResponseData> for Option<cbor::Value> {
|
|||||||
ResponseData::AuthenticatorReset => None,
|
ResponseData::AuthenticatorReset => None,
|
||||||
ResponseData::AuthenticatorCredentialManagement(data) => data.map(|d| d.into()),
|
ResponseData::AuthenticatorCredentialManagement(data) => data.map(|d| d.into()),
|
||||||
ResponseData::AuthenticatorSelection => None,
|
ResponseData::AuthenticatorSelection => None,
|
||||||
|
ResponseData::AuthenticatorLargeBlobs(data) => data.map(|d| d.into()),
|
||||||
ResponseData::AuthenticatorConfig => None,
|
ResponseData::AuthenticatorConfig => None,
|
||||||
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
|
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
|
||||||
}
|
}
|
||||||
@@ -204,6 +206,22 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
|
||||||
|
pub struct AuthenticatorLargeBlobsResponse {
|
||||||
|
pub config: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthenticatorLargeBlobsResponse> for cbor::Value {
|
||||||
|
fn from(platform_large_blobs_response: AuthenticatorLargeBlobsResponse) -> Self {
|
||||||
|
let AuthenticatorLargeBlobsResponse { config } = platform_large_blobs_response;
|
||||||
|
|
||||||
|
cbor_map_options! {
|
||||||
|
0x01 => config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
|
||||||
@@ -510,6 +528,23 @@ mod test {
|
|||||||
assert_eq!(response_cbor, None);
|
assert_eq!(response_cbor, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_large_blobs_into_cbor() {
|
||||||
|
let large_blobs_response = AuthenticatorLargeBlobsResponse { config: vec![0xC0] };
|
||||||
|
let response_cbor: Option<cbor::Value> =
|
||||||
|
ResponseData::AuthenticatorLargeBlobs(Some(large_blobs_response)).into();
|
||||||
|
let expected_cbor = cbor_map_options! {
|
||||||
|
0x01 => vec![0xC0],
|
||||||
|
};
|
||||||
|
assert_eq!(response_cbor, Some(expected_cbor));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_large_blobs_into_cbor() {
|
||||||
|
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorLargeBlobs(None).into();
|
||||||
|
assert_eq!(response_cbor, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_config_into_cbor() {
|
fn test_config_into_cbor() {
|
||||||
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorConfig.into();
|
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorConfig.into();
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ use alloc::vec;
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use cbor::cbor_array_vec;
|
use cbor::cbor_array_vec;
|
||||||
|
use core::cmp;
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use crypto::rng256::Rng256;
|
use crypto::rng256::Rng256;
|
||||||
use persistent_store::StoreUpdate;
|
use persistent_store::{fragment, StoreUpdate};
|
||||||
|
|
||||||
// Those constants may be modified before compilation to tune the behavior of the key.
|
// Those constants may be modified before compilation to tune the behavior of the key.
|
||||||
//
|
//
|
||||||
@@ -59,6 +60,7 @@ const DEFAULT_MIN_PIN_LENGTH_RP_IDS: Vec<String> = Vec::new();
|
|||||||
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
||||||
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
||||||
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
||||||
|
pub const MAX_LARGE_BLOB_ARRAY_SIZE: usize = 2048;
|
||||||
|
|
||||||
/// Wrapper for master keys.
|
/// Wrapper for master keys.
|
||||||
pub struct MasterKeys {
|
pub struct MasterKeys {
|
||||||
@@ -467,6 +469,51 @@ impl PersistentStore {
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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).
|
||||||
|
pub fn get_large_blob_array(
|
||||||
|
&self,
|
||||||
|
offset: usize,
|
||||||
|
byte_count: usize,
|
||||||
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
|
let byte_range = offset..offset + byte_count;
|
||||||
|
let output = fragment::read_range(&self.store, &key::LARGE_BLOB_SHARDS, byte_range)?;
|
||||||
|
Ok(output.unwrap_or_else(|| {
|
||||||
|
const EMPTY_LARGE_BLOB: [u8; 17] = [
|
||||||
|
0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5,
|
||||||
|
0x7A, 0x6D, 0x3C,
|
||||||
|
];
|
||||||
|
let last_index = cmp::min(EMPTY_LARGE_BLOB.len(), offset + byte_count);
|
||||||
|
EMPTY_LARGE_BLOB
|
||||||
|
.get(offset..last_index)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_vec()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a byte vector as the serialized large blobs array.
|
||||||
|
pub fn commit_large_blob_array(
|
||||||
|
&mut self,
|
||||||
|
large_blob_array: &[u8],
|
||||||
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
|
// This input should have been caught at caller level.
|
||||||
|
if large_blob_array.len() > MAX_LARGE_BLOB_ARRAY_SIZE {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
Ok(fragment::write(
|
||||||
|
&mut self.store,
|
||||||
|
&key::LARGE_BLOB_SHARDS,
|
||||||
|
large_blob_array,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the attestation private key if defined.
|
/// Returns the attestation private key if defined.
|
||||||
pub fn attestation_private_key(
|
pub fn attestation_private_key(
|
||||||
&self,
|
&self,
|
||||||
@@ -1144,6 +1191,86 @@ mod test {
|
|||||||
assert_eq!(persistent_store.min_pin_length_rp_ids().unwrap(), rp_ids);
|
assert_eq!(persistent_store.min_pin_length_rp_ids().unwrap(), rp_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_large_blob_array_size() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
#[allow(clippy::assertions_on_constants)]
|
||||||
|
{
|
||||||
|
assert!(MAX_LARGE_BLOB_ARRAY_SIZE >= 1024);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
MAX_LARGE_BLOB_ARRAY_SIZE
|
||||||
|
<= persistent_store.store.max_value_length()
|
||||||
|
* (key::LARGE_BLOB_SHARDS.end - key::LARGE_BLOB_SHARDS.start)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_commit_get_large_blob_array() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
let large_blob_array = vec![0x01, 0x02, 0x03];
|
||||||
|
assert!(persistent_store
|
||||||
|
.commit_large_blob_array(&large_blob_array)
|
||||||
|
.is_ok());
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(0, 1).unwrap();
|
||||||
|
assert_eq!(vec![0x01], restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(1, 1).unwrap();
|
||||||
|
assert_eq!(vec![0x02], restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(2, 1).unwrap();
|
||||||
|
assert_eq!(vec![0x03], restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(2, 2).unwrap();
|
||||||
|
assert_eq!(vec![0x03], restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(3, 1).unwrap();
|
||||||
|
assert_eq!(Vec::<u8>::new(), restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(4, 1).unwrap();
|
||||||
|
assert_eq!(Vec::<u8>::new(), restored_large_blob_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_commit_get_large_blob_array_overwrite() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
let large_blob_array = vec![0x11; 5];
|
||||||
|
assert!(persistent_store
|
||||||
|
.commit_large_blob_array(&large_blob_array)
|
||||||
|
.is_ok());
|
||||||
|
let large_blob_array = vec![0x22; 4];
|
||||||
|
assert!(persistent_store
|
||||||
|
.commit_large_blob_array(&large_blob_array)
|
||||||
|
.is_ok());
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(0, 5).unwrap();
|
||||||
|
assert_eq!(large_blob_array, restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(4, 1).unwrap();
|
||||||
|
assert_eq!(Vec::<u8>::new(), restored_large_blob_array);
|
||||||
|
|
||||||
|
assert!(persistent_store.commit_large_blob_array(&[]).is_ok());
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(0, 20).unwrap();
|
||||||
|
// Committing an empty array resets to the default blob of 17 byte.
|
||||||
|
assert_eq!(restored_large_blob_array.len(), 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_commit_get_large_blob_array_no_commit() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
let empty_blob_array = vec![
|
||||||
|
0x80, 0x76, 0xBE, 0x8B, 0x52, 0x8D, 0x00, 0x75, 0xF7, 0xAA, 0xE9, 0x8D, 0x6F, 0xA5,
|
||||||
|
0x7A, 0x6D, 0x3C,
|
||||||
|
];
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(0, 17).unwrap();
|
||||||
|
assert_eq!(empty_blob_array, restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(0, 1).unwrap();
|
||||||
|
assert_eq!(vec![0x80], restored_large_blob_array);
|
||||||
|
let restored_large_blob_array = persistent_store.get_large_blob_array(16, 1).unwrap();
|
||||||
|
assert_eq!(vec![0x3C], restored_large_blob_array);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_global_signature_counter() {
|
fn test_global_signature_counter() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ make_partition! {
|
|||||||
/// board may configure `MAX_SUPPORTED_RESIDENT_KEYS` depending on the storage size.
|
/// board may configure `MAX_SUPPORTED_RESIDENT_KEYS` depending on the storage size.
|
||||||
CREDENTIALS = 1700..2000;
|
CREDENTIALS = 1700..2000;
|
||||||
|
|
||||||
|
/// Storage for the serialized large blob array.
|
||||||
|
///
|
||||||
|
/// The stored large blob can be too big for one key, so it has to be sharded.
|
||||||
|
LARGE_BLOB_SHARDS = 2000..2004;
|
||||||
|
|
||||||
/// If this entry exists and equals 1, the PIN needs to be changed.
|
/// If this entry exists and equals 1, the PIN needs to be changed.
|
||||||
FORCE_PIN_CHANGE = 2040;
|
FORCE_PIN_CHANGE = 2040;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user