blob: 1c42796cf613a73cec2a726b6ec34e2ca4d34d51 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence
//! advertisements.
//!
//! See `tests/examples_v0.rs` and `tests/examples_v1.rs` for some tests that show common
//! deserialization scenarios.
#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#[cfg(any(test, feature = "alloc"))]
extern crate alloc;
extern crate core;
use crate::{
credential::{
book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v1::V1DiscoveryCryptoMaterial,
DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion,
},
deserialization_arena::ArenaOutOfSpace,
extended::deserialize::{
encrypted_section::*, parse_sections, CiphertextSection, DataElementParseError,
DataElementParsingIterator, DecryptedSection, IntermediateSection, PlaintextSection,
Section, SectionDeserializeError,
},
legacy::deserialize::{
DecryptError, DecryptedAdvContents, IntermediateAdvContents, PlaintextAdvContents,
},
};
#[cfg(any(test, feature = "alloc"))]
use alloc::vec::Vec;
use array_vec::ArrayVecOption;
#[cfg(feature = "devtools")]
use array_view::ArrayView;
use core::fmt::Debug;
use crypto_provider::CryptoProvider;
use deserialization_arena::{DeserializationArena, DeserializationArenaAllocator};
#[cfg(feature = "devtools")]
use extended::NP_ADV_MAX_SECTION_LEN;
use extended::NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT;
use legacy::{data_elements::DataElementDeserializeError, deserialize::AdvDeserializeError};
use nom::{combinator, number};
pub use strum;
mod array_vec;
pub mod credential;
pub mod de_type;
#[cfg(test)]
mod deser_v0_tests;
#[cfg(test)]
mod deser_v1_tests;
pub mod deserialization_arena;
pub mod extended;
pub mod filter;
#[cfg(test)]
mod header_parse_tests;
pub mod legacy;
pub mod shared_data;
/// Canonical form of NP's service UUID.
///
/// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be
/// reversed depending on the host BT API.
pub const NP_SVC_UUID: [u8; 2] = [0xFC, 0xF1];
/// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of
/// the service data for the NP UUID).
pub fn deserialize_advertisement<'adv, 'cred, B, P>(
arena: DeserializationArena<'adv>,
adv: &'adv [u8],
cred_book: &'cred B,
) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
where
B: CredentialBook<'cred>,
P: CryptoProvider,
{
let (remaining, header) =
parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?;
match header {
AdvHeader::V0 => {
deser_decrypt_v0::<B, P>(cred_book, remaining).map(DeserializedAdvertisement::V0)
}
AdvHeader::V1(header) => deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header)
.map(DeserializedAdvertisement::V1),
}
}
/// The encryption scheme used for a V1 advertisement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum V1EncryptionScheme {
/// Indicates MIC-based encryption and verification.
Mic,
/// Indicates signature-based encryption and verification.
Signature,
}
/// Error in decryption operations for `deser_decrypt_v1_section_bytes_for_dev_tools`.
#[cfg(feature = "devtools")]
#[derive(Debug, Clone)]
pub enum AdvDecryptionError {
/// Cannot decrypt because the input section is not encrypted.
InputNotEncrypted,
/// Error parsing the given section.
ParseError,
/// No suitable credential found to decrypt the given section.
NoMatchingCredentials,
}
/// Decrypt, but do not further deserialize the v1 bytes, intended for developer tooling uses only.
/// Production uses should use [deserialize_advertisement] instead, which deserializes to a
/// structured format and provides extra type safety.
#[cfg(feature = "devtools")]
pub fn deser_decrypt_v1_section_bytes_for_dev_tools<'adv, 'cred, B, P>(
arena: DeserializationArena<'adv>,
cred_book: &'cred B,
header_byte: u8,
section_bytes: &'adv [u8],
) -> Result<(ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, V1EncryptionScheme), AdvDecryptionError>
where
B: CredentialBook<'cred>,
P: CryptoProvider,
{
let header = V1Header { header_byte };
let int_sections =
parse_sections(header, section_bytes).map_err(|_| AdvDecryptionError::ParseError)?;
let cipher_section = match &int_sections[0] {
IntermediateSection::Plaintext(_) => Err(AdvDecryptionError::InputNotEncrypted)?,
IntermediateSection::Ciphertext(section) => section,
};
let mut allocator = arena.into_allocator();
for (crypto_material, _) in cred_book.v1_iter() {
if let Some(plaintext) = cipher_section
.try_resolve_identity_and_decrypt::<_, P>(&mut allocator, &crypto_material)
{
let pt = plaintext.expect(concat!(
"Should not run out of space because DeserializationArenaAllocator is big ",
"enough to hold a single advertisement, and we exit immediately upon ",
"successful decryption",
));
let encryption_scheme = match cipher_section {
CiphertextSection::SignatureEncryptedIdentity(_) => V1EncryptionScheme::Signature,
CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic,
};
return Ok((pt, encryption_scheme));
}
}
Err(AdvDecryptionError::NoMatchingCredentials)
}
/// A ciphertext section which has not yet been
/// resolved to an identity, but for which some
/// `SectionIdentityResolutionContents` have been
/// pre-computed for speedy identity-resolution.
struct ResolvableCiphertextSection<'a> {
identity_resolution_contents: SectionIdentityResolutionContents,
ciphertext_section: CiphertextSection<'a>,
}
/// A collection of possibly-deserialized sections which are separated according
/// to whether/not they're intermediate encrypted sections (of either type)
/// or fully-deserialized, with a running count of the number of malformed sections.
/// Each potentially-valid section is tagged with a 0-based index derived from the original
/// section ordering as they appeared within the original advertisement to ensure
/// that the fully-deserialized advertisement may be correctly reconstructed.
struct SectionsInProcessing<'adv, M: MatchedCredential> {
deserialized_sections: ArrayVecOption<
(usize, V1DeserializedSection<'adv, M>),
{ NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
>,
encrypted_sections: ArrayVecOption<
(usize, ResolvableCiphertextSection<'adv>),
{ NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
>,
malformed_sections_count: usize,
}
impl<'adv, M: MatchedCredential> SectionsInProcessing<'adv, M> {
/// Attempts to parse a V1 advertisement's contents after the version header
/// into a collection of not-yet-fully-deserialized sections which may
/// require credentials to be decrypted.
fn from_advertisement_contents<C: CryptoProvider>(
header: V1Header,
remaining: &'adv [u8],
) -> Result<Self, AdvDeserializationError> {
let int_sections =
parse_sections(header, remaining).map_err(|_| AdvDeserializationError::ParseError {
details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
})?;
let mut deserialized_sections = ArrayVecOption::default();
let mut encrypted_sections = ArrayVecOption::default();
// keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`.
for (idx, s) in int_sections.into_iter().enumerate() {
match s {
IntermediateSection::Plaintext(p) => {
deserialized_sections.push((idx, V1DeserializedSection::Plaintext(p)))
}
IntermediateSection::Ciphertext(ciphertext_section) => {
let identity_resolution_contents =
ciphertext_section.contents().compute_identity_resolution_contents::<C>();
let resolvable_ciphertext_section = ResolvableCiphertextSection {
identity_resolution_contents,
ciphertext_section,
};
encrypted_sections.push((idx, resolvable_ciphertext_section));
}
}
}
Ok(Self { deserialized_sections, encrypted_sections, malformed_sections_count: 0 })
}
/// Returns true iff we have resolved all sections to identities.
fn resolved_all_identities(&self) -> bool {
self.encrypted_sections.is_empty()
}
/// Runs through all of the encrypted sections in processing, and attempts
/// to use the given credential to decrypt them. Suitable for situations
/// where iterating over credentials is relatively slow compared to
/// the cost of iterating over sections-in-memory.
fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>(
&mut self,
arena: &mut DeserializationArenaAllocator<'adv>,
crypto_material: C,
match_data: M,
) -> Result<(), ArenaOutOfSpace> {
let mut i = 0;
while i < self.encrypted_sections.len() {
let (section_idx, section): &(usize, ResolvableCiphertextSection) =
&self.encrypted_sections[i];
// Fast-path: Check for an identity match, ignore if there's no identity match.
let identity_resolution_contents = &section.identity_resolution_contents;
let identity_resolution_material = match &section.ciphertext_section {
CiphertextSection::MicEncryptedIdentity(_) => crypto_material
.unsigned_identity_resolution_material::<P>()
.into_raw_resolution_material(),
CiphertextSection::SignatureEncryptedIdentity(_) => crypto_material
.signed_identity_resolution_material::<P>()
.into_raw_resolution_material(),
};
match identity_resolution_contents.try_match::<P>(&identity_resolution_material) {
None => {
// Try again with another section
i += 1;
continue;
}
Some(identity_match) => {
// The identity matched, so now we need to more closely scrutinize
// the provided ciphertext. Try to decrypt and parse the section.
let metadata_nonce = crypto_material.metadata_nonce::<P>();
let deserialization_result = match &section.ciphertext_section {
CiphertextSection::SignatureEncryptedIdentity(c) => c
.try_deserialize(
arena,
identity_match,
&crypto_material.signed_verification_material::<P>(),
)
.map_err(SectionDeserializeError::from),
CiphertextSection::MicEncryptedIdentity(c) => c
.try_deserialize(
arena,
identity_match,
&crypto_material.unsigned_verification_material::<P>(),
)
.map_err(SectionDeserializeError::from),
};
match deserialization_result {
Ok(s) => {
self.deserialized_sections.push((
*section_idx,
V1DeserializedSection::Decrypted(WithMatchedCredential::new(
match_data.clone(),
metadata_nonce,
s,
)),
));
}
Err(e) => match e {
SectionDeserializeError::IncorrectCredential => {
// keep it around to try with another credential
i += 1;
continue;
}
SectionDeserializeError::ParseError => {
// the credential worked, but the section itself was bogus
self.malformed_sections_count += 1;
}
SectionDeserializeError::ArenaOutOfSpace => {
return Err(ArenaOutOfSpace)
}
},
}
// By default, if we have an identity match, assume that decrypting the section worked,
// or that the section was somehow invalid.
// We don't care about maintaining order, so use O(1) remove
self.encrypted_sections.swap_remove(i);
// don't advance i -- it now points to a new element
}
}
}
Ok(())
}
/// Packages the current state of the deserialization process into a
/// `V1AdvertisementContents` representing a fully-deserialized V1 advertisement.
///
/// This method should only be called after all sections were either successfully
/// decrypted or have had all relevant credentials checked against
/// them without obtaining a successful identity-match and/or subsequent
/// cryptographic verification of the section contents.
fn finished_with_decryption_attempts(mut self) -> V1AdvertisementContents<'adv, M> {
// Invalid sections = malformed sections + number of encrypted sections
// which we could not manage to decrypt with any of our credentials
let invalid_sections_count = self.malformed_sections_count + self.encrypted_sections.len();
// Put the deserialized sections back into the original ordering for
// the returned `V1AdvertisementContents`
// (Note: idx is unique, so unstable sort is ok)
self.deserialized_sections.sort_unstable_by_key(|(idx, _section)| *idx);
let ordered_sections = self.deserialized_sections.into_iter().map(|(_idx, s)| s).collect();
V1AdvertisementContents::new(ordered_sections, invalid_sections_count)
}
}
/// Deserialize and decrypt the contents of a v1 adv after the version header
fn deser_decrypt_v1<'adv, 'cred, B, P>(
arena: DeserializationArena<'adv>,
cred_book: &'cred B,
remaining: &'adv [u8],
header: V1Header,
) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
where
B: CredentialBook<'cred>,
P: CryptoProvider,
{
let mut sections_in_processing =
SectionsInProcessing::<'_, B::Matched>::from_advertisement_contents::<P>(
header, remaining,
)?;
let mut allocator = arena.into_allocator();
// Hot loop
// We assume that iterating credentials is more expensive than iterating sections
for (crypto_material, match_data) in cred_book.v1_iter() {
sections_in_processing
.try_decrypt_with_credential::<_, P>(&mut allocator, crypto_material, match_data)
.expect(concat!(
"Should not run out of space because DeserializationArenaAllocator is big ",
"enough to hold a single advertisement, and we exit immediately upon ",
"successful decryption",
));
if sections_in_processing.resolved_all_identities() {
// No need to consider the other credentials
break;
}
}
Ok(sections_in_processing.finished_with_decryption_attempts())
}
/// Deserialize and decrypt the contents of a v0 adv after the version header
fn deser_decrypt_v0<'adv, 'cred, B, P>(
cred_book: &'cred B,
remaining: &'adv [u8],
) -> Result<V0AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
where
B: CredentialBook<'cred>,
P: CryptoProvider,
{
let contents = legacy::deserialize::deserialize_adv_contents::<P>(remaining)?;
match contents {
IntermediateAdvContents::Plaintext(p) => Ok(V0AdvertisementContents::Plaintext(p)),
IntermediateAdvContents::Ciphertext(c) => {
for (crypto_material, matched) in cred_book.v0_iter() {
let ldt = crypto_material.ldt_adv_cipher::<P>();
match c.try_decrypt(&ldt) {
Ok(c) => {
let metadata_nonce = crypto_material.metadata_nonce::<P>();
return Ok(V0AdvertisementContents::Decrypted(WithMatchedCredential::new(
matched,
metadata_nonce,
c,
)));
}
Err(e) => match e {
DecryptError::DecryptOrVerifyError => continue,
DecryptError::DeserializeError(e) => {
return Err(e.into());
}
},
}
}
Ok(V0AdvertisementContents::NoMatchingCredentials)
}
}
}
/// Parse a NP advertisement header.
///
/// This can be used on all versions of advertisements since it's the header that determines the
/// version.
///
/// Returns a `nom::IResult` with the parsed header and the remaining bytes of the advertisement.
fn parse_adv_header(adv: &[u8]) -> nom::IResult<&[u8], AdvHeader> {
// header bits: VVVxxxxx
let (remaining, (header_byte, version, _low_bits)) = combinator::verify(
// splitting a byte at a bit boundary to take lower 5 bits
combinator::map(number::complete::u8, |byte| (byte, byte >> 5, byte & 0x1F)),
|&(_header_byte, version, low_bits)| match version {
// reserved bits, for any version, must be zero
PROTOCOL_VERSION_LEGACY | PROTOCOL_VERSION_EXTENDED => low_bits == 0,
_ => false,
},
)(adv)?;
match version {
PROTOCOL_VERSION_LEGACY => Ok((remaining, AdvHeader::V0)),
PROTOCOL_VERSION_EXTENDED => Ok((remaining, AdvHeader::V1(V1Header { header_byte }))),
_ => unreachable!(),
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum AdvHeader {
V0,
V1(V1Header),
}
/// An NP advertisement with its header parsed.
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq)]
pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> {
/// V0 header has all reserved bits, so there is no data to represent other than the version
/// itself.
V0(V0AdvertisementContents<'adv, M>),
/// V1 advertisement
V1(V1AdvertisementContents<'adv, M>),
}
impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> {
/// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents`
/// variant. If the underlying advertisement is not V0, this will instead return `None`.
pub fn into_v0(self) -> Option<V0AdvertisementContents<'adv, M>> {
match self {
Self::V0(x) => Some(x),
_ => None,
}
}
/// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents`
/// variant. If the underlying advertisement is not V1, this will instead return `None`.
pub fn into_v1(self) -> Option<V1AdvertisementContents<'adv, M>> {
match self {
Self::V1(x) => Some(x),
_ => None,
}
}
}
/// The contents of a deserialized and decrypted V1 advertisement.
#[derive(Debug, PartialEq, Eq)]
pub struct V1AdvertisementContents<'adv, M: MatchedCredential> {
sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>,
invalid_sections: usize,
}
impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> {
fn new(
sections: ArrayVecOption<
V1DeserializedSection<'adv, M>,
NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
>,
invalid_sections: usize,
) -> Self {
Self { sections, invalid_sections }
}
/// Destructures this V1 advertisement into just the sections
/// which could be successfully deserialized and decrypted
pub fn into_sections(
self,
) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT> {
self.sections
}
/// The sections that could be successfully deserialized and decrypted
pub fn sections(&self) -> impl ExactSizeIterator<Item = &V1DeserializedSection<M>> {
self.sections.iter()
}
/// The number of sections that could not be parsed or decrypted.
pub fn invalid_sections_count(&self) -> usize {
self.invalid_sections
}
}
/// Advertisement content that was either already plaintext or has been decrypted.
#[derive(Debug, PartialEq, Eq)]
pub enum V0AdvertisementContents<'adv, M: MatchedCredential> {
/// Contents of an originally plaintext advertisement
Plaintext(PlaintextAdvContents<'adv>),
/// Contents that was ciphertext in the original advertisement, and has been decrypted
/// with the credential in the [MatchedCredential]
Decrypted(WithMatchedCredential<M, DecryptedAdvContents>),
/// The advertisement was encrypted, but no credentials matched
NoMatchingCredentials,
}
/// Advertisement content that was either already plaintext or has been decrypted.
#[derive(Debug, PartialEq, Eq)]
pub enum V1DeserializedSection<'adv, M: MatchedCredential> {
/// Section that was plaintext in the original advertisement
Plaintext(PlaintextSection<'adv>),
/// Section that was ciphertext in the original advertisement, and has been decrypted
/// with the credential in the [MatchedCredential]
Decrypted(WithMatchedCredential<M, DecryptedSection<'adv>>),
}
impl<'adv, M> Section<'adv, DataElementParseError> for V1DeserializedSection<'adv, M>
where
M: MatchedCredential,
{
type Iterator = DataElementParsingIterator<'adv>;
fn iter_data_elements(&self) -> Self::Iterator {
match self {
V1DeserializedSection::Plaintext(p) => p.iter_data_elements(),
V1DeserializedSection::Decrypted(d) => d.contents.iter_data_elements(),
}
}
}
/// 16-byte metadata keys, as employed for metadata decryption.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub struct MetadataKey(pub [u8; 16]);
impl AsRef<[u8]> for MetadataKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
/// Common trait to deserialized, decrypted V0 advs and V1 sections which
/// exposes relevant data about matched identities.
pub trait HasIdentityMatch {
/// The protocol version for which this advertisement
/// content has an identity-match.
type Version: ProtocolVersion;
/// Gets the decrypted plaintext version-specific
/// metadata key for the associated identity.
fn metadata_key(&self) -> <Self::Version as ProtocolVersion>::MetadataKey;
}
#[cfg(any(test, feature = "alloc"))]
/// Type for errors from [`WithMatchedCredential#decrypt_metadata`]
#[derive(Debug)]
pub enum MatchedMetadataDecryptionError<M: MatchedCredential> {
/// Retrieving the encrypted metadata failed for one reason
/// or another, so we didn't get a chance to try decryption.
RetrievalFailed(<M as MatchedCredential>::EncryptedMetadataFetchError),
/// The encrypted metadata could be retrieved, but it did
/// not successfully decrypt against the matched identity.
/// This could be an indication of data corruption or
/// of malformed crypto on the sender-side.
DecryptionFailed,
}
/// Decrypted advertisement content with the [MatchedCredential] from the credential that decrypted
/// it, along with any other information which is relevant to the identity-match.
#[derive(Debug, PartialEq, Eq)]
pub struct WithMatchedCredential<M: MatchedCredential, T: HasIdentityMatch> {
matched: M,
/// The 12-byte metadata nonce as derived from the key-seed HKDF
/// to be used for decrypting the encrypted metadata in the attached
/// matched-credential.
metadata_nonce: [u8; 12],
contents: T,
}
impl<M: MatchedCredential, T: HasIdentityMatch> WithMatchedCredential<M, T> {
fn new(matched: M, metadata_nonce: [u8; 12], contents: T) -> Self {
Self { matched, metadata_nonce, contents }
}
/// Credential data for the credential that decrypted the content.
pub fn matched_credential(&self) -> &M {
&self.matched
}
/// The decrypted advertisement content.
pub fn contents(&self) -> &T {
&self.contents
}
#[cfg(any(test, feature = "alloc"))]
fn decrypt_metadata_from_fetch<C: CryptoProvider>(
&self,
encrypted_metadata: &[u8],
) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> {
let metadata_key = self.contents.metadata_key();
<<T as HasIdentityMatch>::Version as ProtocolVersion>::decrypt_metadata::<C>(
self.metadata_nonce,
metadata_key,
encrypted_metadata,
)
.map_err(|_| MatchedMetadataDecryptionError::DecryptionFailed)
}
#[cfg(any(test, feature = "alloc"))]
/// Attempts to decrypt the encrypted metadata
/// associated with the matched credential
/// based on the details of the identity-match.
pub fn decrypt_metadata<C: CryptoProvider>(
&self,
) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> {
self.matched
.fetch_encrypted_metadata()
.map_err(|e| MatchedMetadataDecryptionError::RetrievalFailed(e))
.and_then(|x| Self::decrypt_metadata_from_fetch::<C>(self, x.as_ref()))
}
}
/// Data in a V1 advertisement header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) struct V1Header {
header_byte: u8,
}
const PROTOCOL_VERSION_LEGACY: u8 = 0;
const PROTOCOL_VERSION_EXTENDED: u8 = 1;
/// Errors that can occur during advertisement deserialization.
#[derive(PartialEq)]
pub enum AdvDeserializationError {
/// The advertisement header could not be parsed
HeaderParseError,
/// The advertisement content could not be parsed
ParseError {
/// Potentially hazardous details about deserialization errors. Read the documentation for
/// [AdvDeserializationErrorDetailsHazmat] before using this field.
details_hazmat: AdvDeserializationErrorDetailsHazmat,
},
}
impl Debug for AdvDeserializationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AdvDeserializationError::HeaderParseError => write!(f, "HeaderParseError"),
AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"),
}
}
}
/// Potentially hazardous details about deserialization errors. These error information can
/// potentially expose side-channel information about the plaintext of the advertisements and/or
/// the keys used to decrypt them. For any place that you avoid exposing the keys directly
/// (e.g. across FFIs, print to log, etc), avoid exposing these error details as well.
#[derive(PartialEq)]
pub enum AdvDeserializationErrorDetailsHazmat {
/// Parsing the overall advertisement or DE structure failed
AdvertisementDeserializeError,
/// Deserializing an individual DE from its DE contents failed
V0DataElementDeserializeError(DataElementDeserializeError),
/// Must not have any other top level data elements if there is an encrypted identity DE
TooManyTopLevelDataElements,
/// Must not have an identity DE inside an identity DE
InvalidDataElementHierarchy,
/// Must have an identity DE
MissingIdentity,
/// Non-identity DE contents must not be empty
NoPublicDataElements,
}
impl From<AdvDeserializeError> for AdvDeserializationError {
fn from(err: AdvDeserializeError) -> Self {
match err {
AdvDeserializeError::AdvertisementDeserializeError => {
AdvDeserializationError::ParseError {
details_hazmat:
AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
}
}
AdvDeserializeError::TooManyTopLevelDataElements => {
AdvDeserializationError::ParseError {
details_hazmat:
AdvDeserializationErrorDetailsHazmat::TooManyTopLevelDataElements,
}
}
AdvDeserializeError::MissingIdentity => AdvDeserializationError::ParseError {
details_hazmat: AdvDeserializationErrorDetailsHazmat::MissingIdentity,
},
AdvDeserializeError::NoPublicDataElements => AdvDeserializationError::ParseError {
details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements,
},
}
}
}
/// DE length is out of range (e.g. > 4 bits for encoded V0, > max DE size for actual V0, >127 for
/// V1) or invalid for the relevant DE type.
#[derive(Debug, PartialEq, Eq)]
pub struct DeLengthOutOfRange;
/// The identity mode for a deserialized plaintext section or advertisement.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum PlaintextIdentityMode {
/// A "Public Identity" DE was present in the section
Public,
}
/// A "public identity" -- a nonspecific "empty identity".
///
/// Used when serializing V0 advertisements or V1 sections.
#[derive(Default, Debug)]
pub struct PublicIdentity;