1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence
16 //! advertisements.
17 //!
18 //! See `tests/examples_v0.rs` and `tests/examples_v1.rs` for some tests that show common
19 //! deserialization scenarios.
20 
21 #![no_std]
22 #![allow(clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
23 
24 #[cfg(any(test, feature = "alloc"))]
25 extern crate alloc;
26 
27 pub use strum;
28 
29 use crate::credential::matched::MatchedCredential;
30 use crate::extended::deserialize::{deser_decrypt_v1, V1AdvertisementContents};
31 use crate::{
32     credential::book::CredentialBook,
33     header::NpVersionHeader,
34     legacy::{deser_decrypt_v0, V0AdvertisementContents},
35 };
36 use core::fmt::Debug;
37 use crypto_provider::CryptoProvider;
38 use deserialization_arena::DeserializationArena;
39 use legacy::data_elements::DataElementDeserializeError;
40 
41 #[cfg(test)]
42 mod tests;
43 
44 pub mod credential;
45 pub mod deserialization_arena;
46 pub mod extended;
47 pub mod filter;
48 pub mod legacy;
49 pub mod shared_data;
50 
51 mod array_vec;
52 mod header;
53 mod helpers;
54 
55 /// Canonical form of NP's service UUID.
56 ///
57 /// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be
58 /// reversed depending on the host BT API.
59 pub const NP_SVC_UUID: [u8; 2] = [0xFC, 0xF1];
60 
61 /// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of
62 /// the service data for the NP UUID).
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,63 pub fn deserialize_advertisement<'adv, 'cred, B, P>(
64     arena: DeserializationArena<'adv>,
65     adv: &'adv [u8],
66     cred_book: &'cred B,
67 ) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
68 where
69     B: CredentialBook<'cred>,
70     P: CryptoProvider,
71 {
72     let (remaining, header) = NpVersionHeader::parse(adv)
73         .map_err(|_e| AdvDeserializationError::VersionHeaderParseError)?;
74     match header {
75         NpVersionHeader::V0(encoding) => deser_decrypt_v0::<B, P>(encoding, cred_book, remaining)
76             .map(DeserializedAdvertisement::V0),
77         NpVersionHeader::V1(header) => {
78             deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header)
79                 .map(DeserializedAdvertisement::V1)
80         }
81     }
82 }
83 
84 /// An NP advertisement with its header parsed.
85 #[allow(clippy::large_enum_variant)]
86 #[derive(Debug, PartialEq, Eq)]
87 pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> {
88     /// V0 header has all reserved bits, so there is no data to represent other than the version
89     /// itself.
90     V0(V0AdvertisementContents<'adv, M>),
91     /// V1 advertisement
92     V1(V1AdvertisementContents<'adv, M>),
93 }
94 
95 impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> {
96     /// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents`
97     /// variant. If the underlying advertisement is not V0, this will instead return `None`.
into_v0(self) -> Option<V0AdvertisementContents<'adv, M>>98     pub fn into_v0(self) -> Option<V0AdvertisementContents<'adv, M>> {
99         match self {
100             Self::V0(x) => Some(x),
101             _ => None,
102         }
103     }
104     /// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents`
105     /// variant. If the underlying advertisement is not V1, this will instead return `None`.
into_v1(self) -> Option<V1AdvertisementContents<'adv, M>>106     pub fn into_v1(self) -> Option<V1AdvertisementContents<'adv, M>> {
107         match self {
108             Self::V1(x) => Some(x),
109             _ => None,
110         }
111     }
112 }
113 
114 /// Errors that can occur during advertisement deserialization.
115 #[derive(PartialEq)]
116 pub enum AdvDeserializationError {
117     /// The advertisement header could not be parsed
118     VersionHeaderParseError,
119     /// The advertisement content could not be parsed
120     ParseError {
121         /// Potentially hazardous details about deserialization errors. Read the documentation for
122         /// [AdvDeserializationErrorDetailsHazmat] before using this field.
123         details_hazmat: AdvDeserializationErrorDetailsHazmat,
124     },
125 }
126 
127 impl Debug for AdvDeserializationError {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result128     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129         match self {
130             AdvDeserializationError::VersionHeaderParseError => {
131                 write!(f, "VersionHeaderParseError")
132             }
133             AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"),
134         }
135     }
136 }
137 
138 /// Potentially hazardous details about deserialization errors. These error information can
139 /// potentially expose side-channel information about the plaintext of the advertisements and/or
140 /// the keys used to decrypt them. For any place that you avoid exposing the keys directly
141 /// (e.g. across FFIs, print to log, etc), avoid exposing these error details as well.
142 #[derive(PartialEq)]
143 pub enum AdvDeserializationErrorDetailsHazmat {
144     /// Parsing the overall advertisement or DE structure failed
145     AdvertisementDeserializeError,
146     /// Deserializing an individual DE from its DE contents failed
147     V0DataElementDeserializeError(DataElementDeserializeError),
148     /// Non-identity DE contents must not be empty
149     NoPublicDataElements,
150 }
151 
152 impl From<legacy::deserialize::AdvDeserializeError> for AdvDeserializationError {
from(err: legacy::deserialize::AdvDeserializeError) -> Self153     fn from(err: legacy::deserialize::AdvDeserializeError) -> Self {
154         match err {
155             legacy::deserialize::AdvDeserializeError::NoDataElements => {
156                 AdvDeserializationError::ParseError {
157                     details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements,
158                 }
159             }
160             legacy::deserialize::AdvDeserializeError::InvalidStructure => {
161                 AdvDeserializationError::ParseError {
162                     details_hazmat:
163                         AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
164                 }
165             }
166         }
167     }
168 }
169 
170 /// DE length is out of range (e.g. > 4 bits for encoded V0, > max DE size for actual V0, >127 for
171 /// V1) or invalid for the relevant DE type.
172 #[derive(Debug, PartialEq, Eq)]
173 pub struct DeLengthOutOfRange;
174 
175 pub(crate) mod private {
176     /// A marker trait to prevent other crates from implementing traits that
177     /// are intended to be only implemented internally.
178     pub trait Sealed {}
179 }
180