use crate::{ crypto::{PinUvAuthParam, PinUvAuthToken}, ctap2::server::UserVerificationRequirement, errors::{AuthenticatorError, UnsupportedOption}, transport::errors::HIDError, FidoDevice, }; use serde::{ de::{Error as SerdeError, IgnoredAny, MapAccess, Visitor}, ser::{Error as SerError, SerializeMap}, Deserialize, Deserializer, Serialize, Serializer, }; use serde_bytes::ByteBuf; use serde_cbor::{from_slice, to_vec, Value}; use sha2::{Digest, Sha256}; use std::fmt; use super::{Command, CommandError, CtapResponse, PinUvAuthCommand, RequestCtap2, StatusCode}; #[derive(Debug)] pub(crate) struct LargeBlobs { get: Option, // (0x01) Unsigned integer Optional The number of bytes requested to read. MUST NOT be present if set is present. set: Option, // (0x02) Byte String Optional A fragment to write. MUST NOT be present if get is present. offset: u64, // (0x03) Unsigned integer Required The byte offset at which to read/write. length: Option, // (0x04) Unsigned integer Optional The total length of a write operation. Present if, and only if, set is present and offset is zero. pin_uv_auth_param: Option, // (0x05) authenticate(pinUvAuthToken, 32×0xff || h’0c00' || uint32LittleEndian(offset) || SHA-256(contents of set byte string, i.e. not including an outer CBOR tag with major type two)) } impl PinUvAuthCommand for LargeBlobs { fn set_pin_uv_auth_param( &mut self, pin_uv_auth_token: Option, ) -> Result<(), AuthenticatorError> { let mut param = None; if let Some(token) = pin_uv_auth_token { // pinUvAuthParam (0x05): the result of calling // authenticate(pinUvAuthToken, 32×0xff || h’0c00' || uint32LittleEndian(offset) || SHA-256(contents of set byte string, i.e. not including an outer CBOR tag with major type two)) let mut data = vec![0xff; 32]; data.extend([0x0c, 0x00]); data.extend((self.offset as u32).to_le_bytes()); if let Some(ref set) = self.set { data.extend(Sha256::digest(set.as_slice())); } param = Some(token.derive(&data).map_err(CommandError::Crypto)?); } self.pin_uv_auth_param = param; Ok(()) } fn can_skip_user_verification( &mut self, _info: &crate::AuthenticatorInfo, _uv: UserVerificationRequirement, ) -> bool { // "discouraged" does not exist for LargeBlobs false } fn set_uv_option(&mut self, _uv: Option) { /* No-op */ } fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam> { self.pin_uv_auth_param.as_ref() } fn get_rp_id(&self) -> Option<&String> { None } fn hmac_requested(&self) -> bool { false } } impl Serialize for LargeBlobs { fn serialize(&self, serializer: S) -> Result where S: Serializer, { if self.set.is_none() && self.get.is_none() { return Err(SerError::custom("Either set or get has to be set")); } let mut map_len = 2; // get/set and offset if self.length.is_some() { map_len += 1; } if self.pin_uv_auth_param.is_some() { map_len += 2; } let mut map = serializer.serialize_map(Some(map_len))?; if let Some(ref get) = self.get { map.serialize_entry(&0x01, get)?; } if let Some(ref set) = self.set { map.serialize_entry(&0x02, set)?; } map.serialize_entry(&0x03, &self.offset)?; if let Some(ref length) = self.length { map.serialize_entry(&0x04, length)?; } if let Some(ref pin_uv_auth_param) = self.pin_uv_auth_param { map.serialize_entry(&0x05, pin_uv_auth_param)?; map.serialize_entry(&0x06, &pin_uv_auth_param.pin_protocol.id())?; } map.end() } } impl RequestCtap2 for LargeBlobs { type Output = LargeBlobSegment; fn command(&self) -> Command { Command::LargeBlobs } fn wire_format(&self) -> Result, HIDError> { let output = to_vec(&self).map_err(CommandError::Serializing)?; trace!("client subcommmand: {:04X?}", &output); Ok(output) } fn handle_response_ctap2( &self, _dev: &mut Dev, input: &[u8], ) -> Result where Dev: FidoDevice, { if input.is_empty() { return Err(CommandError::InputTooSmall.into()); } let status: StatusCode = input[0].into(); if status.is_ok() { if input.len() > 1 { let payload = &input[1..]; Ok(payload.to_vec()) } else { // Some subcommands return only an OK-status without any data Ok(Vec::new()) } } else { let data: Option = if input.len() > 1 { let payload = &input[1..]; Some(from_slice(payload).map_err(CommandError::Deserializing)?) } else { None }; Err(CommandError::StatusCode(status, data).into()) } } fn send_to_virtual_device( &self, _dev: &mut Dev, ) -> Result { unimplemented!() } } #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct LargeBlobArrayElement { /// AEAD_AES_256_GCM ciphertext, implicitly including the AEAD “authentication tag” at the end. pub ciphertext: Vec, /// AEAD_AES_256_GCM nonce. MUST be exactly 12 bytes long. pub nonce: [u8; 12], /// Contains the length, in bytes, of the uncompressed data. pub orig_size: u64, } impl Serialize for LargeBlobArrayElement { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let map_len = 3; // The Array is the only one let mut map = serializer.serialize_map(Some(map_len))?; map.serialize_entry(&0x01, &self.ciphertext)?; map.serialize_entry(&0x02, &self.nonce)?; map.serialize_entry(&0x03, &self.orig_size)?; map.end() } } impl<'de> Deserialize<'de> for LargeBlobArrayElement { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct LargeBlobArrayElementVisitor; impl<'de> Visitor<'de> for LargeBlobArrayElementVisitor { type Value = LargeBlobArrayElement; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut map: M) -> Result where M: MapAccess<'de>, { let mut ciphertext = None; // Sub-level (0x01) let mut nonce = None; // Sub-level (0x02) let mut orig_size = None; // Sub-level (0x03) // Parsing out the top-level "large-blob array" while let Some(key) = map.next_key()? { match key { 0x01 => { if ciphertext.is_some() { return Err(SerdeError::duplicate_field("ciphertext")); } ciphertext = Some(map.next_value()?); } 0x02 => { if nonce.is_some() { return Err(SerdeError::duplicate_field("nonce")); } nonce = Some(map.next_value()?); } 0x03 => { if orig_size.is_some() { return Err(SerdeError::duplicate_field("orig_size")); } orig_size = Some(map.next_value()?); } k => { warn!("LargeBlobArray: unexpected key: {:?}", k); let _ = map.next_value::()?; continue; } } } let ciphertext = ciphertext.ok_or_else(|| M::Error::missing_field("ciphertext"))?; let nonce = nonce.ok_or_else(|| M::Error::missing_field("nonce"))?; let orig_size = orig_size.ok_or_else(|| M::Error::missing_field("orig_size"))?; Ok(LargeBlobArrayElement { ciphertext, nonce, orig_size, }) } } deserializer.deserialize_bytes(LargeBlobArrayElementVisitor) } } #[derive(Default, Debug, PartialEq, Eq)] pub struct LargeBlobsResponse { pub(crate) large_blob_array: Vec, /// Truncated SHA-256 hash of the preceding bytes pub(crate) hash: [u8; 16], pub(crate) byte_len: u64, } impl<'de> Deserialize<'de> for LargeBlobsResponse { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct LargeBlobsResponseVisitor; impl<'de> Visitor<'de> for LargeBlobsResponseVisitor { type Value = LargeBlobsResponse; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut map: M) -> Result where M: MapAccess<'de>, { // this data is serialized as a CBOR-encoded array (called the large-blob array) of large-blob maps, concatenated with 16 following bytes. Those final 16 bytes are the truncated SHA-256 hash of the preceding bytes. let mut response = None; // Top-level 0x01 // Parsing out the top-level "large-blob array" while let Some(key) = map.next_key()? { match key { 0x01 => { if response.is_some() { return Err(SerdeError::duplicate_field("response")); } let payload: ByteBuf = map.next_value()?; // Note: the minimum length of a serialized large-blob array is 17 bytes. Omitting 16 bytes for the trailing SHA-256 hash, this leaves just one byte. This is the size of an empty CBOR array. if payload.len() < 17 { return Err(SerdeError::invalid_length( payload.len(), &">= 17 bytes", )); } // split off trailing hash-bytes let (mut large_blob, mut hash_slice) = payload.split_at(payload.len() - 16); let expected_hash = Sha256::digest(large_blob); // The initial serialized large-blob array is the value of the serialized large-blob array on a fresh authenticator, as well as immediately after a reset. It is the byte string h'8076be8b528d0075f7aae98d6fa57a6d3c', which is an empty CBOR array (80) followed by LEFT(SHA-256(h'80'), 16). let default_large_blob = [0x80]; let default_hash = [ 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7, 0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c, ]; // Once complete, the platform MUST confirm that the embedded SHA-256 hash is correct, based on the definition above. If not, the configuration is corrupt and the platform MUST discard it and act as if the initial serialized large-blob array was received. if &expected_hash[0..16] != hash_slice { warn!("Large blob array hash doesn't match with the expected value! Assuming an empty array."); large_blob = &default_large_blob; hash_slice = &default_hash; } let byte_len = large_blob.len() as u64; let large_blob_array: Vec = from_slice(large_blob).map_err(M::Error::custom)?; let mut hash = [0u8; 16]; hash.copy_from_slice(hash_slice); response = Some(LargeBlobsResponse { large_blob_array, hash, byte_len, }); } k => { warn!("LargeBlobsResponse: unexpected key: {:?}", k); let _ = map.next_value::()?; continue; } } } let response = response.ok_or_else(|| M::Error::missing_field("large_blob_bytes"))?; Ok(response) } } deserializer.deserialize_bytes(LargeBlobsResponseVisitor) } } pub type LargeBlobSegment = Vec; impl CtapResponse for LargeBlobSegment {} pub fn read_large_blob_array( dev: &mut Dev, keep_alive: &dyn Fn() -> bool, ) -> Result where Dev: FidoDevice, { // Spec: // A per-authenticator constant, maxFragmentLength, is here defined as the value of maxMsgSize (from the authenticatorGetInfo response) minus 64. // If no maxMsgSize is given in the authenticatorGetInfo response) then it defaults to 1024, leaving maxFragmentLength to default to 960. // // In the highly unlikely case of a max_msg_size smaller than 65 (leaving zero-byte fragment-length), we error out, saying that largeBlobs is unsupported. let max_msg_size = dev .get_authenticator_info() .and_then(|i| i.max_msg_size) .unwrap_or(1024); if max_msg_size <= 64 { return Err(AuthenticatorError::UnsupportedOption( UnsupportedOption::LargeBlobs, )); } let max_fragment_length = max_msg_size - 64; let mut bytes = vec![]; let mut offset = 0; loop { let cmd = LargeBlobs { get: Some(max_fragment_length as u64), set: None, offset, length: None, pin_uv_auth_param: None, }; let mut segment = dev.send_cbor_cancellable(&cmd, keep_alive)?; let segment_len = segment.len(); bytes.append(&mut segment); // Spec: // If the length of the response is equal to the value of get then more data may be // available and the platform SHOULD repeatedly issue requests, each time updating offset // to equal the amount of data received so far. It stops once a short (or empty) // fragment is returned. if segment_len < max_fragment_length { // The last segment was smaller than the max-size // so we have read all there is to read. break; } else { // There is still more data. So set the offset and repeat offset += segment_len as u64; continue; } } let response: LargeBlobsResponse = from_slice(&bytes).map_err(CommandError::Deserializing)?; Ok(response) } pub fn write_large_blob_segment( dev: &mut Dev, keep_alive: &dyn Fn() -> bool, bytes: &[u8], initial_offset: u64, pin_uv_auth_token: Option, ) -> Result<(), AuthenticatorError> where Dev: FidoDevice, { // Spec: // A per-authenticator constant, maxFragmentLength, is here defined as the value of maxMsgSize (from the authenticatorGetInfo response) minus 64. // If no maxMsgSize is given in the authenticatorGetInfo response) then it defaults to 1024, leaving maxFragmentLength to default to 960. // // In the highly unlikely case of a max_msg_size smaller than 65 (leaving zero-byte fragment-length), we error out, saying that largeBlobs is unsupported. let max_msg_size = dev .get_authenticator_info() .and_then(|i| i.max_msg_size) .unwrap_or(1024); if max_msg_size <= 64 { return Err(AuthenticatorError::UnsupportedOption( UnsupportedOption::LargeBlobs, )); } let max_fragment_length = max_msg_size - 64; let total_length = bytes.len(); let mut offset = initial_offset; for chunk in bytes.chunks(max_fragment_length) { let chunk_len = chunk.len(); let mut cmd = LargeBlobs { get: None, set: Some(ByteBuf::from(chunk)), offset, length: if offset == 0 { Some(total_length as u64) } else { None }, pin_uv_auth_param: None, }; cmd.set_pin_uv_auth_param(pin_uv_auth_token.clone())?; dev.send_cbor_cancellable(&cmd, keep_alive)?; offset += chunk_len as u64; } Ok(()) } pub fn add_large_blob( dev: &mut Dev, keep_alive: &dyn Fn() -> bool, blob: LargeBlobArrayElement, pin_uv_auth_token: Option, ) -> Result<(), AuthenticatorError> where Dev: FidoDevice, { let mut array = read_large_blob_array(dev, keep_alive)?; // Adding it array.large_blob_array.push(blob); // Then rewriting the whole array let mut bytes = to_vec(&array.large_blob_array).map_err(CommandError::Serializing)?; let mut hasher = Sha256::new(); hasher.update(&bytes); let hash = hasher.finalize(); bytes.extend_from_slice(&hash[..16]); write_large_blob_segment(dev, keep_alive, &bytes, 0, pin_uv_auth_token) } #[cfg(test)] pub mod tests { use super::*; use crate::consts::HIDCmd; use crate::transport::device_selector::Device; use crate::transport::hid::HIDDevice; use crate::transport::platform::device::{IN_HID_RPT_SIZE, OUT_HID_RPT_SIZE}; use crate::transport::{FidoDevice, FidoProtocol}; use rand::{thread_rng, RngCore}; fn add_bytes_to_read(cid: &[u8], bytes: &[u8], device: &mut Device) { let mut data = Vec::new(); let payload_len = (bytes.len() + 1) as u16; // We skip the very first byte (HIDCmd::Cbor), as we will insert it below data.extend(payload_len.to_be_bytes()); data.push(0x00); // status == success data.extend(bytes); let chunks = data.chunks(IN_HID_RPT_SIZE - 5); for (id, chunk) in chunks.enumerate() { let mut msg = cid.to_vec(); let state_or_seq = if id == 0 { HIDCmd::Cbor.into() } else { (id - 1) as u8 // SEQ }; msg.push(state_or_seq); msg.extend(chunk); device.add_read(&msg, 0); } } fn add_bytes_to_write(cid: &[u8], bytes: &[u8], device: &mut Device) { let mut data = Vec::new(); let payload_len = (bytes.len()) as u16; // We skip the very first byte (HIDCmd::Cbor), as we will insert it below data.extend(payload_len.to_be_bytes()); data.extend(bytes); let chunks = data.chunks(OUT_HID_RPT_SIZE - 5); for (id, chunk) in chunks.enumerate() { let mut msg = cid.to_vec(); let state_or_seq = if id == 0 { HIDCmd::Cbor.into() } else { (id - 1) as u8 // SEQ }; msg.push(state_or_seq); msg.extend(chunk); device.add_write(&msg, 0); } } #[test] fn test_read_large_blob_array() { let keep_alive = || true; let mut device = Device::new("commands/large_blobs").unwrap(); assert_eq!(device.get_protocol(), FidoProtocol::CTAP2); // 'initialize' the device let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); device.set_cid(cid); let cmd = [ 0xa2, // map(2) 0x01, // unsigned(1) - get 0x19, 0x03, 0xc0, // unsigned(960) 0x03, // unsigned(3) - offset 0x00, // unsigned(0) ]; let mut msg = cid.to_vec(); msg.extend(vec![HIDCmd::Cbor.into(), 0x00, cmd.len() as u8 + 1]); // cmd + bcnt msg.extend(vec![0x0C]); // LargeBlobs msg.extend(cmd); // Actual command device.add_write(&msg, 0); add_bytes_to_read(&cid, &LARGE_BLOB_ARRAY, &mut device); let array = read_large_blob_array(&mut device, &keep_alive) .expect("Failed to read large blob array"); let expected = get_expected_large_blobs_response(); assert_eq!(expected, array); } #[test] fn test_read_large_blob_array_with_wrong_hash() { let keep_alive = || true; let mut device = Device::new("commands/large_blobs").unwrap(); assert_eq!(device.get_protocol(), FidoProtocol::CTAP2); // 'initialize' the device let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); device.set_cid(cid); let cmd = [ 0xa2, // map(2) 0x01, // unsigned(1) - get 0x19, 0x03, 0xc0, // unsigned(960) 0x03, // unsigned(3) - offset 0x00, // unsigned(0) ]; let mut msg = cid.to_vec(); msg.extend(vec![HIDCmd::Cbor.into(), 0x00, cmd.len() as u8 + 1]); // cmd + bcnt msg.extend(vec![0x0C]); // LargeBlobs msg.extend(cmd); // Actual command device.add_write(&msg, 0); let mut payload = LARGE_BLOB_ARRAY; payload[483] += 1; // Changing one byte in the hash add_bytes_to_read(&cid, &payload, &mut device); // Should succeed, but give us the default empty Large blob array, as defined by the spec let array = read_large_blob_array(&mut device, &keep_alive) .expect("Failed to read large blob array"); let expected = LargeBlobsResponse { large_blob_array: vec![], hash: [ 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7, 0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c, ], byte_len: 1, }; assert_eq!(expected, array); } #[test] fn test_read_large_blob_array_multi_read() { let keep_alive = || true; let mut device = Device::new("commands/large_blobs").unwrap(); assert_eq!(device.get_protocol(), FidoProtocol::CTAP2); device.set_authenticator_info(crate::AuthenticatorInfo { max_msg_size: Some(164), // Note: This value minus 64 will be the fragment size ..Default::default() }); // 'initialize' the device let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); device.set_cid(cid); for ii in 0..5 { let mut cmd = vec![ 0xa2, // map(2) 0x01, // unsigned(1) - get 0x18, 0x64, // unsigned(100) 0x03, // unsigned(3) - offset ]; cmd.extend(&to_vec(&serde_cbor::Value::Integer(ii * 100)).unwrap()); let mut msg = cid.to_vec(); msg.extend(vec![HIDCmd::Cbor.into(), 0x00, cmd.len() as u8 + 1]); // cmd + bcnt msg.extend(vec![0x0C]); // LargeBlobs msg.extend(cmd); // Actual command device.add_write(&msg, 0); } for chunk in LARGE_BLOB_ARRAY.chunks(100) { add_bytes_to_read(&cid, chunk, &mut device); } let array = read_large_blob_array(&mut device, &keep_alive) .expect("Failed to read large blob array"); let expected = get_expected_large_blobs_response(); assert_eq!(expected, array); } #[test] fn test_add_large_blob_element() { let keep_alive = || true; let mut device = Device::new("commands/large_blobs").unwrap(); assert_eq!(device.get_protocol(), FidoProtocol::CTAP2); // First we read the whole existing array let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); device.set_cid(cid); let cmd = [ 0xa2, // map(2) 0x01, // unsigned(1) - get 0x19, 0x03, 0xc0, // unsigned(960) 0x03, // unsigned(3) - offset 0x00, // unsigned(0) ]; let mut msg = cid.to_vec(); msg.extend(vec![HIDCmd::Cbor.into(), 0x00, cmd.len() as u8 + 1]); // cmd + bcnt msg.extend(vec![0x0C]); // LargeBlobs msg.extend(cmd); // Actual command device.add_write(&msg, 0); add_bytes_to_read(&cid, &LARGE_BLOB_ARRAY, &mut device); // Now add write-command let mut cmd = vec![ 0x0C, // LargeBlobs 0xa3, // map(3) 0x02, // unsigned(1) - set 0x59, 0x02, 0x78, // unsigned(632) 479+153 ]; cmd.extend(LARGE_BLOB_ARRAY_LONGER); cmd.extend([ 0x03, // unsigned(3) - offset 0x00, // unsigned(0) 0x04, // unsigned(4) - length 0x19, 0x02, 0x78, // unsigned(631) ]); add_bytes_to_write(&cid, &cmd, &mut device); // empty success-command add_bytes_to_read(&cid, &[], &mut device); let add_parsed = additional_blob_element(); add_large_blob(&mut device, &keep_alive, add_parsed, None) .expect("Failed to write add large blob element"); } #[test] fn test_add_large_blob_element_multi_write() { let keep_alive = || true; let mut device = Device::new("commands/large_blobs").unwrap(); assert_eq!(device.get_protocol(), FidoProtocol::CTAP2); device.set_authenticator_info(crate::AuthenticatorInfo { max_msg_size: Some(164), // Note: This value minus 64 will be the fragment size ..Default::default() }); // First we read the whole existing array let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); device.set_cid(cid); for ii in 0..5 { let mut cmd = vec![ 0xa2, // map(2) 0x01, // unsigned(1) - get 0x18, 0x64, // unsigned(100) 0x03, // unsigned(3) - offset ]; cmd.extend(&to_vec(&serde_cbor::Value::Integer(ii * 100)).unwrap()); let mut msg = cid.to_vec(); msg.extend(vec![HIDCmd::Cbor.into(), 0x00, cmd.len() as u8 + 1]); // cmd + bcnt msg.extend(vec![0x0C]); // LargeBlobs msg.extend(cmd); // Actual command device.add_write(&msg, 0); } for chunk in LARGE_BLOB_ARRAY.chunks(100) { add_bytes_to_read(&cid, chunk, &mut device); } // Now add write-command for (ii, chunk) in LARGE_BLOB_ARRAY_LONGER.chunks(100).enumerate() { let mut cmd = vec![ 0x0C, // LargeBlobs ]; if ii == 0 { cmd.push(0xa3); // map(3) // with 'length' } else { cmd.push(0xa2); // map(2) // without 'length' } cmd.push(0x02); // unsigned(1) - set if ii == 6 { cmd.extend([0x58, 0x20]); // unsigned(32) Remaining bytes } else { cmd.extend([0x58, 0x64]); // unsigned(100) Remaining bytes } cmd.extend(chunk); cmd.push(0x03); // unsigned(3) - offset cmd.extend(&to_vec(&serde_cbor::Value::Integer((ii * 100) as i128)).unwrap()); if ii == 0 { cmd.extend([ 0x04, // unsigned(4) - length 0x19, 0x02, 0x78, // unsigned(631) ]); } add_bytes_to_write(&cid, &cmd, &mut device); // empty success-command add_bytes_to_read(&cid, &[], &mut device); } let add_parsed = additional_blob_element(); add_large_blob(&mut device, &keep_alive, add_parsed, None) .expect("Failed to write add large blob element"); } fn additional_blob_element() -> LargeBlobArrayElement { LargeBlobArrayElement { ciphertext: vec![ 116, 199, 82, 206, 68, 131, 237, 242, 213, 144, 244, 185, 155, 148, 217, 62, 245, 5, 128, 162, 176, 99, 5, 160, 186, 68, 88, 140, 38, 255, 168, 254, 88, 161, 188, 30, 113, 221, 67, 21, 88, 43, 211, 17, 190, 252, 14, 186, 225, 200, 135, 186, 168, 255, 232, 51, 151, 183, 194, 134, 160, 250, 191, 141, ], nonce: [117, 86, 137, 126, 205, 2, 34, 50, 18, 20, 165, 104], orig_size: 34, } } fn get_expected_large_blobs_response() -> LargeBlobsResponse { LargeBlobsResponse { large_blob_array: vec![ LargeBlobArrayElement { ciphertext: vec![ 116, 199, 82, 206, 68, 131, 237, 242, 213, 144, 244, 185, 155, 148, 217, 62, 245, 5, 128, 162, 176, 99, 5, 160, 186, 68, 88, 140, 38, 255, 168, 254, 88, 161, 188, 30, 113, 221, 67, 21, 88, 43, 211, 17, 190, 252, 14, 186, 225, 200, 135, 186, 168, 255, 232, 51, 151, 183, 194, 134, 160, 250, 191, 141, ], nonce: [117, 86, 137, 126, 205, 2, 34, 50, 18, 20, 165, 104], orig_size: 34, }, LargeBlobArrayElement { ciphertext: vec![ 71, 124, 111, 114, 77, 240, 163, 5, 124, 7, 191, 2, 177, 167, 200, 95, 248, 163, 235, 77, 195, 106, 253, 23, 183, 119, 55, 17, 50, 238, 217, 248, 56, 135, 48, 49, 101, 132, 66, 78, 58, 23, 101, 77, 52, 213, 89, 73, 34, 61, 237, 8, 219, 1, 208, 245, 129, 101, 234, 114, 170, 54, 7, 147, 59, 226, 32, ], nonce: [99, 132, 251, 236, 134, 156, 86, 195, 121, 49, 205, 162], orig_size: 36, }, LargeBlobArrayElement { ciphertext: vec![ 212, 135, 116, 12, 170, 245, 186, 103, 147, 112, 196, 29, 43, 120, 236, 175, 205, 84, 184, 231, 118, 152, 76, 60, 216, 128, 204, 166, 96, 8, 67, 3, 163, 242, 243, 124, 156, 65, 138, 98, 66, 46, 201, 40, 219, 236, 53, 43, 107, 14, 135, 23, 99, 150, 240, 14, 234, 153, 115, 94, 180, 117, 162, 213, ], nonce: [231, 165, 15, 21, 64, 8, 234, 133, 6, 223, 226, 134], orig_size: 34, }, ], hash: [ 0x15, 0xee, 0x84, 0xa0, 0xce, 0x5d, 0xa7, 0xd6, 0x6d, 0x3e, 0xb6, 0xf2, 0xc1, 0x40, 0x28, 0x65, ], byte_len: 463, } } #[rustfmt::skip] pub const LARGE_BLOB_ARRAY: [u8; 484] = [ 0xa1, // map(1) 0x01, // unsigned(1) 0x59, 0x01, 0xdf, // bytes(479) 0x83, // array(3) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x40, // array(64) 0x18, 0x74, 0x18, 0xc7, 0x18, 0x52, 0x18, 0xce, 0x18, 0x44, 0x18, 0x83, 0x18, 0xed, 0x18, 0xf2, 0x18, 0xd5, 0x18, 0x90, 0x18, 0xf4, 0x18, 0xb9, 0x18, 0x9b, 0x18, 0x94, 0x18, 0xd9, 0x18, 0x3e, 0x18, 0xf5, 0x05, 0x18, 0x80, 0x18, 0xa2, 0x18, 0xb0, 0x18, 0x63, 0x05, 0x18, 0xa0, 0x18, 0xba, 0x18, 0x44, 0x18, 0x58, 0x18, 0x8c, 0x18, 0x26, 0x18, 0xff, 0x18, 0xa8, 0x18, 0xfe, 0x18, 0x58, 0x18, 0xa1, 0x18, 0xbc, 0x18, 0x1e, 0x18, 0x71, 0x18, 0xdd, 0x18, 0x43, 0x15, 0x18, 0x58, 0x18, 0x2b, 0x18, 0xd3, 0x11, 0x18, 0xbe, 0x18, 0xfc, 0x0e, 0x18, 0xba, 0x18, 0xe1, 0x18, 0xc8, 0x18, 0x87, 0x18, 0xba, 0x18, 0xa8, 0x18, 0xff, 0x18, 0xe8, 0x18, 0x33, 0x18, 0x97, 0x18, 0xb7, 0x18, 0xc2, 0x18, 0x86, 0x18, 0xa0, 0x18, 0xfa, 0x18, 0xbf, 0x18, 0x8d, 0x02, // unsigned(2) - nonce 0x8c, // array(12) 0x18, 0x75, 0x18, 0x56, 0x18, 0x89, 0x18, 0x7e, 0x18, 0xcd, 0x02, 0x18, 0x22, 0x18, 0x32, 0x12, 0x14, 0x18, 0xa5, 0x18, 0x68, 0x03, // unsigned(3) - origSize 0x18, 0x22, // unsigned(34) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x43, // array(67) 0x18, 0x47, 0x18, 0x7c, 0x18, 0x6f, 0x18, 0x72, 0x18, 0x4d, 0x18, 0xf0, 0x18, 0xa3, 0x05, 0x18, 0x7c, 0x07, 0x18, 0xbf, 0x02, 0x18, 0xb1, 0x18, 0xa7, 0x18, 0xc8, 0x18, 0x5f, 0x18, 0xf8, 0x18, 0xa3, 0x18, 0xeb, 0x18, 0x4d, 0x18, 0xc3, 0x18, 0x6a, 0x18, 0xfd, 0x17, 0x18, 0xb7, 0x18, 0x77, 0x18, 0x37, 0x11, 0x18, 0x32, 0x18, 0xee, 0x18, 0xd9, 0x18, 0xf8, 0x18, 0x38, 0x18, 0x87, 0x18, 0x30, 0x18, 0x31, 0x18, 0x65, 0x18, 0x84, 0x18, 0x42, 0x18, 0x4e, 0x18, 0x3a, 0x17, 0x18, 0x65, 0x18, 0x4d, 0x18, 0x34, 0x18, 0xd5, 0x18, 0x59, 0x18, 0x49, 0x18, 0x22, 0x18, 0x3d, 0x18, 0xed, 0x08, 0x18, 0xdb, 0x01, 0x18, 0xd0, 0x18, 0xf5, 0x18, 0x81, 0x18, 0x65, 0x18, 0xea, 0x18, 0x72, 0x18, 0xaa, 0x18, 0x36, 0x07, 0x18, 0x93, 0x18, 0x3b, 0x18, 0xe2, 0x18, 0x20, 0x02, // unsigned(2) 0x8c, // array(12) - nonce 0x18, 0x63, 0x18, 0x84, 0x18, 0xfb, 0x18, 0xec, 0x18, 0x86, 0x18, 0x9c, 0x18, 0x56, 0x18, 0xc3, 0x18, 0x79, 0x18, 0x31, 0x18, 0xcd, 0x18, 0xa2, 0x03, // unsigned(3) - origSize 0x18, 0x24, // unsigned(36) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x40, // array(64) 0x18, 0xd4, 0x18, 0x87, 0x18, 0x74, 0x0c, 0x18, 0xaa, 0x18, 0xf5, 0x18, 0xba, 0x18, 0x67, 0x18, 0x93, 0x18, 0x70, 0x18, 0xc4, 0x18, 0x1d, 0x18, 0x2b, 0x18, 0x78, 0x18, 0xec, 0x18, 0xaf, 0x18, 0xcd, 0x18, 0x54, 0x18, 0xb8, 0x18, 0xe7, 0x18, 0x76, 0x18, 0x98, 0x18, 0x4c, 0x18, 0x3c, 0x18, 0xd8, 0x18, 0x80, 0x18, 0xcc, 0x18, 0xa6, 0x18, 0x60, 0x08, 0x18, 0x43, 0x03, 0x18, 0xa3, 0x18, 0xf2, 0x18, 0xf3, 0x18, 0x7c, 0x18, 0x9c, 0x18, 0x41, 0x18, 0x8a, 0x18, 0x62, 0x18, 0x42, 0x18, 0x2e, 0x18, 0xc9, 0x18, 0x28, 0x18, 0xdb, 0x18, 0xec, 0x18, 0x35, 0x18, 0x2b, 0x18, 0x6b, 0x0e, 0x18, 0x87, 0x17, 0x18, 0x63, 0x18, 0x96, 0x18, 0xf0, 0x0e, 0x18, 0xea, 0x18, 0x99, 0x18, 0x73, 0x18, 0x5e, 0x18, 0xb4, 0x18, 0x75, 0x18, 0xa2, 0x18, 0xd5, 0x02, // unsigned(2) - nonce 0x8c, // array(12) 0x18, 0xe7, 0x18, 0xa5, 0x0f, 0x15, 0x18, 0x40, 0x08, 0x18, 0xea, 0x18, 0x85, 0x06, 0x18, 0xdf, 0x18, 0xe2, 0x18, 0x86, 0x03, // unsigned(3) - origSize 0x18, 0x22, // unsigned(34) 0x15, 0xee, 0x84, 0xa0, 0xce, 0x5d, 0xa7, 0xd6, 0x6d, 0x3e, 0xb6, 0xf2, 0xc1, 0x40, 0x28, 0x65 // trailing hash-bytes ]; #[rustfmt::skip] pub const LARGE_BLOB_ARRAY_LONGER: [u8; 632] = [ // Skipping initial map(1) -> unsigned(1) -> bytes(632), as we have to change that with // fragmented writes 0x84, // array(4) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x40, // array(64) 0x18, 0x74, 0x18, 0xc7, 0x18, 0x52, 0x18, 0xce, 0x18, 0x44, 0x18, 0x83, 0x18, 0xed, 0x18, 0xf2, 0x18, 0xd5, 0x18, 0x90, 0x18, 0xf4, 0x18, 0xb9, 0x18, 0x9b, 0x18, 0x94, 0x18, 0xd9, 0x18, 0x3e, 0x18, 0xf5, 0x05, 0x18, 0x80, 0x18, 0xa2, 0x18, 0xb0, 0x18, 0x63, 0x05, 0x18, 0xa0, 0x18, 0xba, 0x18, 0x44, 0x18, 0x58, 0x18, 0x8c, 0x18, 0x26, 0x18, 0xff, 0x18, 0xa8, 0x18, 0xfe, 0x18, 0x58, 0x18, 0xa1, 0x18, 0xbc, 0x18, 0x1e, 0x18, 0x71, 0x18, 0xdd, 0x18, 0x43, 0x15, 0x18, 0x58, 0x18, 0x2b, 0x18, 0xd3, 0x11, 0x18, 0xbe, 0x18, 0xfc, 0x0e, 0x18, 0xba, 0x18, 0xe1, 0x18, 0xc8, 0x18, 0x87, 0x18, 0xba, 0x18, 0xa8, 0x18, 0xff, 0x18, 0xe8, 0x18, 0x33, 0x18, 0x97, 0x18, 0xb7, 0x18, 0xc2, 0x18, 0x86, 0x18, 0xa0, 0x18, 0xfa, 0x18, 0xbf, 0x18, 0x8d, 0x02, // unsigned(2) - nonce 0x8c, // array(12) 0x18, 0x75, 0x18, 0x56, 0x18, 0x89, 0x18, 0x7e, 0x18, 0xcd, 0x02, 0x18, 0x22, 0x18, 0x32, 0x12, 0x14, 0x18, 0xa5, 0x18, 0x68, 0x03, // unsigned(3) - origSize 0x18, 0x22, // unsigned(34) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x43, // array(67) 0x18, 0x47, 0x18, 0x7c, 0x18, 0x6f, 0x18, 0x72, 0x18, 0x4d, 0x18, 0xf0, 0x18, 0xa3, 0x05, 0x18, 0x7c, 0x07, 0x18, 0xbf, 0x02, 0x18, 0xb1, 0x18, 0xa7, 0x18, 0xc8, 0x18, 0x5f, 0x18, 0xf8, 0x18, 0xa3, 0x18, 0xeb, 0x18, 0x4d, 0x18, 0xc3, 0x18, 0x6a, 0x18, 0xfd, 0x17, 0x18, 0xb7, 0x18, 0x77, 0x18, 0x37, 0x11, 0x18, 0x32, 0x18, 0xee, 0x18, 0xd9, 0x18, 0xf8, 0x18, 0x38, 0x18, 0x87, 0x18, 0x30, 0x18, 0x31, 0x18, 0x65, 0x18, 0x84, 0x18, 0x42, 0x18, 0x4e, 0x18, 0x3a, 0x17, 0x18, 0x65, 0x18, 0x4d, 0x18, 0x34, 0x18, 0xd5, 0x18, 0x59, 0x18, 0x49, 0x18, 0x22, 0x18, 0x3d, 0x18, 0xed, 0x08, 0x18, 0xdb, 0x01, 0x18, 0xd0, 0x18, 0xf5, 0x18, 0x81, 0x18, 0x65, 0x18, 0xea, 0x18, 0x72, 0x18, 0xaa, 0x18, 0x36, 0x07, 0x18, 0x93, 0x18, 0x3b, 0x18, 0xe2, 0x18, 0x20, 0x02, // unsigned(2) 0x8c, // array(12) - nonce 0x18, 0x63, 0x18, 0x84, 0x18, 0xfb, 0x18, 0xec, 0x18, 0x86, 0x18, 0x9c, 0x18, 0x56, 0x18, 0xc3, 0x18, 0x79, 0x18, 0x31, 0x18, 0xcd, 0x18, 0xa2, 0x03, // unsigned(3) - origSize 0x18, 0x24, // unsigned(36) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x40, // array(64) 0x18, 0xd4, 0x18, 0x87, 0x18, 0x74, 0x0c, 0x18, 0xaa, 0x18, 0xf5, 0x18, 0xba, 0x18, 0x67, 0x18, 0x93, 0x18, 0x70, 0x18, 0xc4, 0x18, 0x1d, 0x18, 0x2b, 0x18, 0x78, 0x18, 0xec, 0x18, 0xaf, 0x18, 0xcd, 0x18, 0x54, 0x18, 0xb8, 0x18, 0xe7, 0x18, 0x76, 0x18, 0x98, 0x18, 0x4c, 0x18, 0x3c, 0x18, 0xd8, 0x18, 0x80, 0x18, 0xcc, 0x18, 0xa6, 0x18, 0x60, 0x08, 0x18, 0x43, 0x03, 0x18, 0xa3, 0x18, 0xf2, 0x18, 0xf3, 0x18, 0x7c, 0x18, 0x9c, 0x18, 0x41, 0x18, 0x8a, 0x18, 0x62, 0x18, 0x42, 0x18, 0x2e, 0x18, 0xc9, 0x18, 0x28, 0x18, 0xdb, 0x18, 0xec, 0x18, 0x35, 0x18, 0x2b, 0x18, 0x6b, 0x0e, 0x18, 0x87, 0x17, 0x18, 0x63, 0x18, 0x96, 0x18, 0xf0, 0x0e, 0x18, 0xea, 0x18, 0x99, 0x18, 0x73, 0x18, 0x5e, 0x18, 0xb4, 0x18, 0x75, 0x18, 0xa2, 0x18, 0xd5, 0x02, // unsigned(2) - nonce 0x8c, // array(12) 0x18, 0xe7, 0x18, 0xa5, 0x0f, 0x15, 0x18, 0x40, 0x08, 0x18, 0xea, 0x18, 0x85, 0x06, 0x18, 0xdf, 0x18, 0xe2, 0x18, 0x86, 0x03, // unsigned(3) - origSize 0x18, 0x22, // unsigned(34) 0xa3, // map(3) 0x01, // unsigned(1) - ciphertext 0x98, 0x40, // array(64) 0x18, 0x74, 0x18, 0xc7, 0x18, 0x52, 0x18, 0xce, 0x18, 0x44, 0x18, 0x83, 0x18, 0xed, 0x18, 0xf2, 0x18, 0xd5, 0x18, 0x90, 0x18, 0xf4, 0x18, 0xb9, 0x18, 0x9b, 0x18, 0x94, 0x18, 0xd9, 0x18, 0x3e, 0x18, 0xf5, 0x05, 0x18, 0x80, 0x18, 0xa2, 0x18, 0xb0, 0x18, 0x63, 0x05, 0x18, 0xa0, 0x18, 0xba, 0x18, 0x44, 0x18, 0x58, 0x18, 0x8c, 0x18, 0x26, 0x18, 0xff, 0x18, 0xa8, 0x18, 0xfe, 0x18, 0x58, 0x18, 0xa1, 0x18, 0xbc, 0x18, 0x1e, 0x18, 0x71, 0x18, 0xdd, 0x18, 0x43, 0x15, 0x18, 0x58, 0x18, 0x2b, 0x18, 0xd3, 0x11, 0x18, 0xbe, 0x18, 0xfc, 0x0e, 0x18, 0xba, 0x18, 0xe1, 0x18, 0xc8, 0x18, 0x87, 0x18, 0xba, 0x18, 0xa8, 0x18, 0xff, 0x18, 0xe8, 0x18, 0x33, 0x18, 0x97, 0x18, 0xb7, 0x18, 0xc2, 0x18, 0x86, 0x18, 0xa0, 0x18, 0xfa, 0x18, 0xbf, 0x18, 0x8d, 0x02, // unsigned(2) - nonce 0x8c, // array(12) 0x18, 0x75, 0x18, 0x56, 0x18, 0x89, 0x18, 0x7e, 0x18, 0xcd, 0x02, 0x18, 0x22, 0x18, 0x32, 0x12, 0x14, 0x18, 0xa5, 0x18, 0x68, 0x03, // unsigned(3) - origSize 0x18, 0x22, // unsigned(34) 0xb9, 0xd5, 0x4e, 0x96, 0xcf, 0x6e, 0xd8, 0xf6, 0xb4, 0x4c, 0x2e, 0xdc, 0xec, 0x76, 0x67, 0x0, // trailing hash-bytes ]; }