cbor: allow user to control nesting (#329)

* cbor: allow user to control nesting

 - Make the default read/write entrypoints allow infinite nesting.
 - Add {read,write}_nested() entrypoints that allow the crate user to
   control the depth of nesting that's allowed.
 - Along the way, convert the write[_nested] variants to return a
   `Result<(), EncoderError>` rather than a bool.  This exposes
   more failure information (and forces the caller to take notice
   of those tailures), and allows use of the ? operator.

* fixup: transmute error

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
This commit is contained in:
David Drysdale
2021-06-18 17:39:54 +00:00
committed by GitHub
parent dbce426e9f
commit 0287a09573
10 changed files with 111 additions and 99 deletions

View File

@@ -58,10 +58,10 @@ fn main() {
// Serialize to bytes. // Serialize to bytes.
let mut manual_data = vec![]; let mut manual_data = vec![];
sk_cbor::writer::write(manual_object, &mut manual_data); sk_cbor::writer::write(manual_object, &mut manual_data).unwrap();
let hex_manual_data = hexify(&manual_data); let hex_manual_data = hexify(&manual_data);
let mut macro_data = vec![]; let mut macro_data = vec![];
sk_cbor::writer::write(macro_object, &mut macro_data); sk_cbor::writer::write(macro_object, &mut macro_data).unwrap();
let hex_macro_data = hexify(&macro_data); let hex_macro_data = hexify(&macro_data);
assert_eq!(hex_manual_data, hex_macro_data); assert_eq!(hex_manual_data, hex_macro_data);

View File

@@ -8,7 +8,7 @@ use sk_cbor as cbor;
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
if let Ok(value) = cbor::read(data) { if let Ok(value) = cbor::read(data) {
let mut result = Vec::new(); let mut result = Vec::new();
assert!(cbor::write(value, &mut result)); assert!(cbor::write(value, &mut result).is_ok());
assert_eq!(result, data); assert_eq!(result, data);
}; };
}); });

View File

@@ -37,11 +37,18 @@ pub enum DecoderError {
OutOfRangeIntegerValue, OutOfRangeIntegerValue,
} }
/// Deserialize CBOR binary data to produce a single [`Value`], expecting that there /// Deserialize CBOR binary data to produce a single [`Value`], expecting that there is no additional data.
/// is no additional data. /// Supports arbitrarily nested CBOR (so the [`DecoderError::TooMuchNesting`] error is never emitted).
pub fn read(encoded_cbor: &[u8]) -> Result<Value, DecoderError> { pub fn read(encoded_cbor: &[u8]) -> Result<Value, DecoderError> {
read_nested(encoded_cbor, None)
}
/// Deserialize CBOR binary data to produce a single [`Value`], expecting that there is no additional data. If
/// `max_nest` is `Some(max)`, then nested structures are only supported up to the given limit (returning
/// [`DecoderError::TooMuchNesting`] if the limit is hit).
pub fn read_nested(encoded_cbor: &[u8], max_nest: Option<i8>) -> Result<Value, DecoderError> {
let mut reader = Reader::new(encoded_cbor); let mut reader = Reader::new(encoded_cbor);
let value = reader.decode_complete_data_item(Reader::MAX_NESTING_DEPTH)?; let value = reader.decode_complete_data_item(max_nest)?;
if !reader.remaining_cbor.is_empty() { if !reader.remaining_cbor.is_empty() {
return Err(DecoderError::ExtraneousData); return Err(DecoderError::ExtraneousData);
} }
@@ -53,8 +60,6 @@ struct Reader<'a> {
} }
impl<'a> Reader<'a> { impl<'a> Reader<'a> {
const MAX_NESTING_DEPTH: i8 = 4;
pub fn new(cbor: &'a [u8]) -> Reader<'a> { pub fn new(cbor: &'a [u8]) -> Reader<'a> {
Reader { Reader {
remaining_cbor: cbor, remaining_cbor: cbor,
@@ -63,9 +68,9 @@ impl<'a> Reader<'a> {
pub fn decode_complete_data_item( pub fn decode_complete_data_item(
&mut self, &mut self,
remaining_depth: i8, remaining_depth: Option<i8>,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
if remaining_depth < 0 { if remaining_depth.map_or(false, |d| d < 0) {
return Err(DecoderError::TooMuchNesting); return Err(DecoderError::TooMuchNesting);
} }
@@ -162,12 +167,12 @@ impl<'a> Reader<'a> {
fn read_array_content( fn read_array_content(
&mut self, &mut self,
size_value: u64, size_value: u64,
remaining_depth: i8, remaining_depth: Option<i8>,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
// Don't set the capacity already, it is an unsanitized input. // Don't set the capacity already, it is an unsanitized input.
let mut value_array = Vec::new(); let mut value_array = Vec::new();
for _ in 0..size_value { for _ in 0..size_value {
value_array.push(self.decode_complete_data_item(remaining_depth - 1)?); value_array.push(self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?);
} }
Ok(cbor_array_vec!(value_array)) Ok(cbor_array_vec!(value_array))
} }
@@ -175,17 +180,20 @@ impl<'a> Reader<'a> {
fn read_map_content( fn read_map_content(
&mut self, &mut self,
size_value: u64, size_value: u64,
remaining_depth: i8, remaining_depth: Option<i8>,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
let mut value_map = Vec::<(Value, Value)>::new(); let mut value_map = Vec::<(Value, Value)>::new();
for _ in 0..size_value { for _ in 0..size_value {
let key = self.decode_complete_data_item(remaining_depth - 1)?; let key = self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?;
if let Some(last_item) = value_map.last() { if let Some(last_item) = value_map.last() {
if last_item.0 >= key { if last_item.0 >= key {
return Err(DecoderError::OutOfOrderKey); return Err(DecoderError::OutOfOrderKey);
} }
} }
value_map.push((key, self.decode_complete_data_item(remaining_depth - 1)?)); value_map.push((
key,
self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?,
));
} }
Ok(cbor_map_collection!(value_map)) Ok(cbor_map_collection!(value_map))
} }
@@ -193,9 +201,9 @@ impl<'a> Reader<'a> {
fn read_tagged_content( fn read_tagged_content(
&mut self, &mut self,
tag_value: u64, tag_value: u64,
remaining_depth: i8, remaining_depth: Option<i8>,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
let inner_value = self.decode_complete_data_item(remaining_depth - 1)?; let inner_value = self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?;
Ok(cbor_tagged!(tag_value, inner_value)) Ok(cbor_tagged!(tag_value, inner_value))
} }
@@ -679,7 +687,7 @@ mod test {
]; ];
for cbor in cases { for cbor in cases {
let mut reader = Reader::new(&cbor); let mut reader = Reader::new(&cbor);
assert!(reader.decode_complete_data_item(0).is_ok()); assert!(reader.decode_complete_data_item(Some(0)).is_ok());
} }
let map_cbor = vec![ let map_cbor = vec![
0xa2, // map of 2 pairs 0xa2, // map of 2 pairs
@@ -690,11 +698,11 @@ mod test {
]; ];
let mut reader = Reader::new(&map_cbor); let mut reader = Reader::new(&map_cbor);
assert_eq!( assert_eq!(
reader.decode_complete_data_item(1), reader.decode_complete_data_item(Some(1)),
Err(DecoderError::TooMuchNesting) Err(DecoderError::TooMuchNesting)
); );
reader = Reader::new(&map_cbor); reader = Reader::new(&map_cbor);
assert!(reader.decode_complete_data_item(2).is_ok()); assert!(reader.decode_complete_data_item(Some(2)).is_ok());
} }
#[test] #[test]

View File

@@ -130,9 +130,9 @@ impl Ord for Value {
(v1, v2) => { (v1, v2) => {
// This case could handle all of the above as well. Checking individually is faster. // This case could handle all of the above as well. Checking individually is faster.
let mut encoding1 = Vec::new(); let mut encoding1 = Vec::new();
write(v1.clone(), &mut encoding1); let _ = write(v1.clone(), &mut encoding1);
let mut encoding2 = Vec::new(); let mut encoding2 = Vec::new();
write(v2.clone(), &mut encoding2); let _ = write(v2.clone(), &mut encoding2);
encoding1.cmp(&encoding2) encoding1.cmp(&encoding2)
} }
} }

View File

@@ -17,11 +17,29 @@
use super::values::{Constants, Value}; use super::values::{Constants, Value};
use alloc::vec::Vec; use alloc::vec::Vec;
/// Possible errors from a serialization operation.
#[derive(Debug, PartialEq)]
pub enum EncoderError {
TooMuchNesting,
DuplicateMapKey,
}
/// Convert a [`Value`] to serialized CBOR data, consuming it along the way and appending to the provided vector. /// Convert a [`Value`] to serialized CBOR data, consuming it along the way and appending to the provided vector.
/// Returns a `bool` indicating whether conversion succeeded. /// Supports arbitrarily nested CBOR (so the [`EncoderError::TooMuchNesting`] error is never emitted).
pub fn write(value: Value, encoded_cbor: &mut Vec<u8>) -> bool { pub fn write(value: Value, encoded_cbor: &mut Vec<u8>) -> Result<(), EncoderError> {
write_nested(value, encoded_cbor, None)
}
/// Convert a [`Value`] to serialized CBOR data, consuming it along the way and appending to the provided vector. If
/// `max_nest` is `Some(max)`, then nested structures are only supported up to the given limit (returning
/// [`DecoderError::TooMuchNesting`] if the limit is hit).
pub fn write_nested(
value: Value,
encoded_cbor: &mut Vec<u8>,
max_nest: Option<i8>,
) -> Result<(), EncoderError> {
let mut writer = Writer::new(encoded_cbor); let mut writer = Writer::new(encoded_cbor);
writer.encode_cbor(value, Writer::MAX_NESTING_DEPTH) writer.encode_cbor(value, max_nest)
} }
struct Writer<'a> { struct Writer<'a> {
@@ -29,15 +47,17 @@ struct Writer<'a> {
} }
impl<'a> Writer<'a> { impl<'a> Writer<'a> {
const MAX_NESTING_DEPTH: i8 = 4;
pub fn new(encoded_cbor: &mut Vec<u8>) -> Writer { pub fn new(encoded_cbor: &mut Vec<u8>) -> Writer {
Writer { encoded_cbor } Writer { encoded_cbor }
} }
fn encode_cbor(&mut self, value: Value, remaining_depth: i8) -> bool { fn encode_cbor(
if remaining_depth < 0 { &mut self,
return false; value: Value,
remaining_depth: Option<i8>,
) -> Result<(), EncoderError> {
if remaining_depth.map_or(false, |d| d < 0) {
return Err(EncoderError::TooMuchNesting);
} }
let type_label = value.type_label(); let type_label = value.type_label();
match value { match value {
@@ -54,9 +74,7 @@ impl<'a> Writer<'a> {
Value::Array(array) => { Value::Array(array) => {
self.start_item(type_label, array.len() as u64); self.start_item(type_label, array.len() as u64);
for el in array { for el in array {
if !self.encode_cbor(el, remaining_depth - 1) { self.encode_cbor(el, remaining_depth.map(|d| d - 1))?;
return false;
}
} }
} }
Value::Map(mut map) => { Value::Map(mut map) => {
@@ -64,27 +82,21 @@ impl<'a> Writer<'a> {
let map_len = map.len(); let map_len = map.len();
map.dedup_by(|a, b| a.0.eq(&b.0)); map.dedup_by(|a, b| a.0.eq(&b.0));
if map_len != map.len() { if map_len != map.len() {
return false; return Err(EncoderError::DuplicateMapKey);
} }
self.start_item(type_label, map_len as u64); self.start_item(type_label, map_len as u64);
for (k, v) in map { for (k, v) in map {
if !self.encode_cbor(k, remaining_depth - 1) { self.encode_cbor(k, remaining_depth.map(|d| d - 1))?;
return false; self.encode_cbor(v, remaining_depth.map(|d| d - 1))?;
}
if !self.encode_cbor(v, remaining_depth - 1) {
return false;
}
} }
} }
Value::Tag(tag, inner_value) => { Value::Tag(tag, inner_value) => {
self.start_item(type_label, tag); self.start_item(type_label, tag);
if !self.encode_cbor(*inner_value, remaining_depth - 1) { self.encode_cbor(*inner_value, remaining_depth.map(|d| d - 1))?;
return false;
}
} }
Value::Simple(simple_value) => self.start_item(type_label, simple_value as u64), Value::Simple(simple_value) => self.start_item(type_label, simple_value as u64),
} }
true Ok(())
} }
fn start_item(&mut self, type_label: u8, size: u64) { fn start_item(&mut self, type_label: u8, size: u64) {
@@ -115,7 +127,7 @@ mod test {
fn write_return(value: Value) -> Option<Vec<u8>> { fn write_return(value: Value) -> Option<Vec<u8>> {
let mut encoded_cbor = Vec::new(); let mut encoded_cbor = Vec::new();
if write(value, &mut encoded_cbor) { if write(value, &mut encoded_cbor).is_ok() {
Some(encoded_cbor) Some(encoded_cbor)
} else { } else {
None None
@@ -459,12 +471,12 @@ mod test {
for (value, level) in positive_cases { for (value, level) in positive_cases {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Writer::new(&mut buf); let mut writer = Writer::new(&mut buf);
assert!(writer.encode_cbor(value, level)); assert!(writer.encode_cbor(value, Some(level)).is_ok());
} }
for (value, level) in negative_cases { for (value, level) in negative_cases {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Writer::new(&mut buf); let mut writer = Writer::new(&mut buf);
assert!(!writer.encode_cbor(value, level)); assert!(!writer.encode_cbor(value, Some(level)).is_ok());
} }
} }
@@ -480,9 +492,10 @@ mod test {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Writer::new(&mut buf); let mut writer = Writer::new(&mut buf);
assert!(writer.encode_cbor(cbor_map.clone(), 2)); assert!(writer.encode_cbor(cbor_map.clone(), Some(2)).is_ok());
assert!(writer.encode_cbor(cbor_map.clone(), None).is_ok());
writer = Writer::new(&mut buf); writer = Writer::new(&mut buf);
assert!(!writer.encode_cbor(cbor_map, 1)); assert!(writer.encode_cbor(cbor_map, Some(1)).is_err());
} }
#[test] #[test]
@@ -502,9 +515,9 @@ mod test {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Writer::new(&mut buf); let mut writer = Writer::new(&mut buf);
assert!(writer.encode_cbor(cbor_array.clone(), 3)); assert!(writer.encode_cbor(cbor_array.clone(), Some(3)).is_ok());
writer = Writer::new(&mut buf); writer = Writer::new(&mut buf);
assert!(!writer.encode_cbor(cbor_array, 2)); assert!(writer.encode_cbor(cbor_array, Some(2)).is_err());
} }
#[test] #[test]
@@ -530,8 +543,9 @@ mod test {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = Writer::new(&mut buf); let mut writer = Writer::new(&mut buf);
assert!(writer.encode_cbor(cbor_map.clone(), 5)); assert!(writer.encode_cbor(cbor_map.clone(), Some(5)).is_ok());
assert!(writer.encode_cbor(cbor_map.clone(), None).is_ok());
writer = Writer::new(&mut buf); writer = Writer::new(&mut buf);
assert!(!writer.encode_cbor(cbor_map, 4)); assert!(writer.encode_cbor(cbor_map, Some(4)).is_err());
} }
} }

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use super::cbor_read;
use super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SIZE}; use super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SIZE};
use super::data_formats::{ use super::data_formats::{
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string, extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
@@ -50,12 +51,6 @@ pub enum Command {
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters), AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
} }
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
fn from(_: cbor::reader::DecoderError) -> Self {
Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR
}
}
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;
@@ -82,13 +77,13 @@ impl Command {
let command_value = bytes[0]; let command_value = bytes[0];
match command_value { match command_value {
Command::AUTHENTICATOR_MAKE_CREDENTIAL => { Command::AUTHENTICATOR_MAKE_CREDENTIAL => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorMakeCredential( Ok(Command::AuthenticatorMakeCredential(
AuthenticatorMakeCredentialParameters::try_from(decoded_cbor)?, AuthenticatorMakeCredentialParameters::try_from(decoded_cbor)?,
)) ))
} }
Command::AUTHENTICATOR_GET_ASSERTION => { Command::AUTHENTICATOR_GET_ASSERTION => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorGetAssertion( Ok(Command::AuthenticatorGetAssertion(
AuthenticatorGetAssertionParameters::try_from(decoded_cbor)?, AuthenticatorGetAssertionParameters::try_from(decoded_cbor)?,
)) ))
@@ -98,7 +93,7 @@ impl Command {
Ok(Command::AuthenticatorGetInfo) Ok(Command::AuthenticatorGetInfo)
} }
Command::AUTHENTICATOR_CLIENT_PIN => { Command::AUTHENTICATOR_CLIENT_PIN => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorClientPin( Ok(Command::AuthenticatorClientPin(
AuthenticatorClientPinParameters::try_from(decoded_cbor)?, AuthenticatorClientPinParameters::try_from(decoded_cbor)?,
)) ))
@@ -112,7 +107,7 @@ impl Command {
Ok(Command::AuthenticatorGetNextAssertion) Ok(Command::AuthenticatorGetNextAssertion)
} }
Command::AUTHENTICATOR_CREDENTIAL_MANAGEMENT => { Command::AUTHENTICATOR_CREDENTIAL_MANAGEMENT => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorCredentialManagement( Ok(Command::AuthenticatorCredentialManagement(
AuthenticatorCredentialManagementParameters::try_from(decoded_cbor)?, AuthenticatorCredentialManagementParameters::try_from(decoded_cbor)?,
)) ))
@@ -122,19 +117,19 @@ impl Command {
Ok(Command::AuthenticatorSelection) Ok(Command::AuthenticatorSelection)
} }
Command::AUTHENTICATOR_LARGE_BLOBS => { Command::AUTHENTICATOR_LARGE_BLOBS => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorLargeBlobs( Ok(Command::AuthenticatorLargeBlobs(
AuthenticatorLargeBlobsParameters::try_from(decoded_cbor)?, 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(
AuthenticatorConfigParameters::try_from(decoded_cbor)?, AuthenticatorConfigParameters::try_from(decoded_cbor)?,
)) ))
} }
Command::AUTHENTICATOR_VENDOR_CONFIGURE => { Command::AUTHENTICATOR_VENDOR_CONFIGURE => {
let decoded_cbor = cbor::read(&bytes[1..])?; let decoded_cbor = cbor_read(&bytes[1..])?;
Ok(Command::AuthenticatorVendorConfigure( Ok(Command::AuthenticatorVendorConfigure(
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?, AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
)) ))

View File

@@ -20,7 +20,6 @@ use super::response::ResponseData;
use super::status_code::Ctap2StatusCode; use super::status_code::Ctap2StatusCode;
use super::storage::PersistentStore; use super::storage::PersistentStore;
use alloc::vec; use alloc::vec;
use sk_cbor as cbor;
/// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig. /// Processes the subcommand enableEnterpriseAttestation for AuthenticatorConfig.
fn process_enable_enterprise_attestation( fn process_enable_enterprise_attestation(
@@ -100,9 +99,7 @@ pub fn process_config(
let mut config_data = vec![0xFF; 32]; let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, sub_command as u8]); config_data.extend(&[0x0D, sub_command as u8]);
if let Some(sub_command_params) = sub_command_params.clone() { if let Some(sub_command_params) = sub_command_params.clone() {
if !cbor::write(sub_command_params.into(), &mut config_data) { super::cbor_write(sub_command_params.into(), &mut config_data)?;
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
} }
client_pin.verify_pin_uv_auth_token( client_pin.verify_pin_uv_auth_token(
&config_data, &config_data,

View File

@@ -30,7 +30,6 @@ use alloc::vec::Vec;
use crypto::sha256::Sha256; use crypto::sha256::Sha256;
use crypto::Hash256; use crypto::Hash256;
use libtock_drivers::timer::ClockValue; use libtock_drivers::timer::ClockValue;
use sk_cbor as cbor;
/// Generates a set with all existing RP IDs. /// Generates a set with all existing RP IDs.
fn get_stored_rp_ids( fn get_stored_rp_ids(
@@ -289,9 +288,7 @@ pub fn process_credential_management(
pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?; pin_uv_auth_protocol.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
let mut management_data = vec![sub_command as u8]; let mut management_data = vec![sub_command as u8];
if let Some(sub_command_params) = sub_command_params.clone() { if let Some(sub_command_params) = sub_command_params.clone() {
if !cbor::write(sub_command_params.into(), &mut management_data) { super::cbor_write(sub_command_params.into(), &mut management_data)?;
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
} }
client_pin.verify_pin_uv_auth_token( client_pin.verify_pin_uv_auth_token(
&management_data, &management_data,

View File

@@ -98,6 +98,9 @@ const AT_FLAG: u8 = 0x40;
// Set this bit when an extension is used. // Set this bit when an extension is used.
const ED_FLAG: u8 = 0x80; const ED_FLAG: u8 = 0x80;
// CTAP2 specification section 6 requires that the depth of nested CBOR structures be limited to at most four levels.
const MAX_CBOR_NESTING_DEPTH: i8 = 4;
pub const TOUCH_TIMEOUT_MS: isize = 30000; pub const TOUCH_TIMEOUT_MS: isize = 30000;
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
const U2F_UP_PROMPT_TIMEOUT: Duration<isize> = Duration::from_ms(10000); const U2F_UP_PROMPT_TIMEOUT: Duration<isize> = Duration::from_ms(10000);
@@ -118,6 +121,17 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
alg: SignatureAlgorithm::ES256, alg: SignatureAlgorithm::ES256,
}; };
// Helpers to perform CBOR read/write while respecting CTAP2 nesting limits.
fn cbor_read(encoded_cbor: &[u8]) -> Result<cbor::Value, Ctap2StatusCode> {
cbor::reader::read_nested(encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
.map_err(|_e| Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)
}
fn cbor_write(value: cbor::Value, mut encoded_cbor: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
cbor::writer::write_nested(value, &mut encoded_cbor, Some(MAX_CBOR_NESTING_DEPTH))
.map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110 // This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding. // (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool. // We change the return value, since we don't need the bool.
@@ -474,7 +488,7 @@ where
Ok(response_data) => { Ok(response_data) => {
let mut response_vec = vec![0x00]; let mut response_vec = vec![0x00];
if let Some(value) = response_data.into() { if let Some(value) = response_data.into() {
if !cbor::write(value, &mut response_vec) { if cbor_write(value, &mut response_vec).is_err() {
response_vec = response_vec =
vec![Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8]; vec![Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR as u8];
} }
@@ -690,9 +704,7 @@ where
} }
auth_data.extend(vec![0x00, credential_id.len() as u8]); auth_data.extend(vec![0x00, credential_id.len() as u8]);
auth_data.extend(&credential_id); auth_data.extend(&credential_id);
if !cbor::write(cbor::Value::from(CoseKey::from(pk)), &mut auth_data) { cbor_write(cbor::Value::from(CoseKey::from(pk)), &mut auth_data)?;
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
if has_extension_output { if has_extension_output {
let hmac_secret_output = if extensions.hmac_secret { let hmac_secret_output = if extensions.hmac_secret {
Some(true) Some(true)
@@ -711,9 +723,7 @@ where
"hmac-secret" => hmac_secret_output, "hmac-secret" => hmac_secret_output,
"minPinLength" => min_pin_length_output, "minPinLength" => min_pin_length_output,
}; };
if !cbor::write(extensions_output, &mut auth_data) { cbor_write(extensions_output, &mut auth_data)?;
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
} }
let mut signature_data = auth_data.clone(); let mut signature_data = auth_data.clone();
@@ -808,9 +818,7 @@ where
"credBlob" => cred_blob, "credBlob" => cred_blob,
"hmac-secret" => encrypted_output, "hmac-secret" => encrypted_output,
}; };
if !cbor::write(extensions_output, &mut auth_data) { cbor_write(extensions_output, &mut auth_data)?;
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
} }
let large_blob_key = match extensions.large_blob_key { let large_blob_key = match extensions.large_blob_key {
Some(true) => credential.large_blob_key, Some(true) => credential.large_blob_key,
@@ -1324,7 +1332,7 @@ mod test {
}; };
let mut response_cbor = vec![0x00]; let mut response_cbor = vec![0x00];
assert!(cbor::write(expected_cbor, &mut response_cbor)); assert!(cbor_write(expected_cbor, &mut response_cbor).is_ok());
assert_eq!(info_reponse, response_cbor); assert_eq!(info_reponse, response_cbor);
} }
@@ -2646,7 +2654,7 @@ mod test {
}, },
4 => cbor_array![ES256_CRED_PARAM], 4 => cbor_array![ES256_CRED_PARAM],
}; };
assert!(cbor::write(cbor_value, &mut command_cbor)); assert!(cbor_write(cbor_value, &mut command_cbor).is_ok());
ctap_state.process_command(&command_cbor, DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE); ctap_state.process_command(&command_cbor, DUMMY_CHANNEL_ID, DUMMY_CLOCK_VALUE);
let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE); let get_assertion_response = ctap_state.process_get_next_assertion(DUMMY_CLOCK_VALUE);

View File

@@ -36,7 +36,6 @@ use core::cmp;
use core::convert::TryInto; use core::convert::TryInto;
use crypto::rng256::Rng256; use crypto::rng256::Rng256;
use persistent_store::{fragment, StoreUpdate}; use persistent_store::{fragment, StoreUpdate};
use sk_cbor as cbor;
use sk_cbor::cbor_array_vec; use sk_cbor::cbor_array_vec;
/// Wrapper for master keys. /// Wrapper for master keys.
@@ -721,23 +720,20 @@ impl<'a> Iterator for IterCredentials<'a> {
/// Deserializes a credential from storage representation. /// Deserializes a credential from storage representation.
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> { fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
let cbor = cbor::read(data).ok()?; let cbor = super::cbor_read(data).ok()?;
cbor.try_into().ok() cbor.try_into().ok()
} }
/// Serializes a credential to storage representation. /// Serializes a credential to storage representation.
fn serialize_credential(credential: PublicKeyCredentialSource) -> Result<Vec<u8>, Ctap2StatusCode> { fn serialize_credential(credential: PublicKeyCredentialSource) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut data = Vec::new(); let mut data = Vec::new();
if cbor::write(credential.into(), &mut data) { super::cbor_write(credential.into(), &mut data)?;
Ok(data) Ok(data)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}
} }
/// Deserializes a list of RP IDs from storage representation. /// Deserializes a list of RP IDs from storage representation.
fn deserialize_min_pin_length_rp_ids(data: &[u8]) -> Option<Vec<String>> { fn deserialize_min_pin_length_rp_ids(data: &[u8]) -> Option<Vec<String>> {
let cbor = cbor::read(data).ok()?; let cbor = super::cbor_read(data).ok()?;
extract_array(cbor) extract_array(cbor)
.ok()? .ok()?
.into_iter() .into_iter()
@@ -749,11 +745,8 @@ fn deserialize_min_pin_length_rp_ids(data: &[u8]) -> Option<Vec<String>> {
/// Serializes a list of RP IDs to storage representation. /// Serializes a list of RP IDs to storage representation.
fn serialize_min_pin_length_rp_ids(rp_ids: Vec<String>) -> Result<Vec<u8>, Ctap2StatusCode> { fn serialize_min_pin_length_rp_ids(rp_ids: Vec<String>) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut data = Vec::new(); let mut data = Vec::new();
if cbor::write(cbor_array_vec!(rp_ids), &mut data) { super::cbor_write(cbor_array_vec!(rp_ids), &mut data)?;
Ok(data) Ok(data)
} else {
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
}
} }
#[cfg(test)] #[cfg(test)]