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 //! Deserialization for V1 advertisement contents
16 #[cfg(any(test, feature = "alloc"))]
17 use alloc::vec::Vec;
18
19 use core::{array::TryFromSliceError, fmt::Debug};
20
21 use crate::{
22 array_vec::ArrayVecOption,
23 credential::{
24 book::CredentialBook,
25 matched::{HasIdentityMatch, MatchedCredential, WithMatchedCredential},
26 v1::{V1DiscoveryCryptoMaterial, V1},
27 },
28 deserialization_arena::{ArenaOutOfSpace, DeserializationArena, DeserializationArenaAllocator},
29 extended::{
30 deserialize::{
31 data_element::{DataElement, DataElementParseError, DataElementParsingIterator},
32 encrypted_section::{
33 DeserializationError, MicVerificationError, SectionIdentityResolutionContents,
34 SignatureVerificationError,
35 },
36 section::intermediate::{
37 parse_sections, CiphertextSection, IntermediateSection, PlaintextSection,
38 },
39 },
40 salt::MultiSalt,
41 V1IdentityToken, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
42 },
43 header::V1AdvHeader,
44 AdvDeserializationError, AdvDeserializationErrorDetailsHazmat,
45 };
46 use crypto_provider::CryptoProvider;
47
48 #[cfg(test)]
49 mod tests;
50
51 pub mod data_element;
52 pub(crate) mod encrypted_section;
53 pub(crate) mod section;
54
55 /// Provides deserialization APIs which expose more of the internals, suitable for use only in
56 /// dev tools.
57 #[cfg(feature = "devtools")]
58 pub mod dev_tools;
59
60 /// Deserialize and decrypt the contents of a v1 adv after the version header
deser_decrypt_v1<'adv, 'cred, B, P>( arena: DeserializationArena<'adv>, cred_book: &'cred B, remaining: &'adv [u8], header: V1AdvHeader, ) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError> where B: CredentialBook<'cred>, P: CryptoProvider,61 pub(crate) fn deser_decrypt_v1<'adv, 'cred, B, P>(
62 arena: DeserializationArena<'adv>,
63 cred_book: &'cred B,
64 remaining: &'adv [u8],
65 header: V1AdvHeader,
66 ) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
67 where
68 B: CredentialBook<'cred>,
69 P: CryptoProvider,
70 {
71 let mut sections_in_processing =
72 SectionsInProcessing::<'_, B::Matched>::from_advertisement_contents::<P>(
73 header, remaining,
74 )?;
75
76 let mut allocator = arena.into_allocator();
77
78 // Hot loop
79 // We assume that iterating credentials is more expensive than iterating sections
80 for (crypto_material, match_data) in cred_book.v1_iter() {
81 sections_in_processing
82 .try_decrypt_with_credential::<_, P>(&mut allocator, crypto_material, match_data)
83 .expect(concat!(
84 "Should not run out of space because DeserializationArenaAllocator is big ",
85 "enough to hold a single advertisement, and we exit immediately upon ",
86 "successful decryption",
87 ));
88 if sections_in_processing.resolved_all_identities() {
89 // No need to consider the other credentials
90 break;
91 }
92 }
93 Ok(sections_in_processing.finished_with_decryption_attempts())
94 }
95
96 /// A section deserialized from a V1 advertisement.
97 pub trait Section<'adv, E: Debug> {
98 /// The iterator type used to iterate over data elements
99 type Iterator: Iterator<Item = Result<DataElement<'adv>, E>>;
100
101 /// Iterator over the data elements in a section, except for any DEs related to resolving the
102 /// identity or otherwise validating the payload (e.g. MIC, Signature, any identity DEs like
103 /// Private Identity).
iter_data_elements(&self) -> Self::Iterator104 fn iter_data_elements(&self) -> Self::Iterator;
105
106 /// Collects the data elements into a vector, eagerly catching and resolving any errors during
107 /// parsing.
108 #[cfg(any(test, feature = "alloc"))]
collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, E> where Self: Sized,109 fn collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, E>
110 where
111 Self: Sized,
112 {
113 self.iter_data_elements().collect()
114 }
115 }
116
117 /// Fully-parsed and verified decrypted contents from an encrypted section.
118 #[derive(Debug, PartialEq, Eq)]
119 pub struct DecryptedSection<'adv> {
120 verification_mode: VerificationMode,
121 identity_token: V1IdentityToken,
122 salt: MultiSalt,
123 /// Decrypted DE data, excluding any encoder suffix
124 plaintext: &'adv [u8],
125 }
126
127 impl<'adv> DecryptedSection<'adv> {
new( verification_mode: VerificationMode, salt: MultiSalt, identity_token: V1IdentityToken, plaintext: &'adv [u8], ) -> Self128 fn new(
129 verification_mode: VerificationMode,
130 salt: MultiSalt,
131 identity_token: V1IdentityToken,
132 plaintext: &'adv [u8],
133 ) -> Self {
134 Self { verification_mode, identity_token, salt, plaintext }
135 }
136
137 /// The verification mode used in the section.
verification_mode(&self) -> VerificationMode138 pub fn verification_mode(&self) -> VerificationMode {
139 self.verification_mode
140 }
141
142 /// The identity token extracted from the section
identity_token(&self) -> &V1IdentityToken143 pub fn identity_token(&self) -> &V1IdentityToken {
144 &self.identity_token
145 }
146
147 /// The salt used for decryption of this section.
salt(&self) -> &MultiSalt148 pub fn salt(&self) -> &MultiSalt {
149 &self.salt
150 }
151
152 #[cfg(test)]
plaintext(&self) -> &'adv [u8]153 pub(crate) fn plaintext(&self) -> &'adv [u8] {
154 self.plaintext
155 }
156 }
157
158 impl<'adv> HasIdentityMatch for DecryptedSection<'adv> {
159 type Version = V1;
identity_token(&self) -> V1IdentityToken160 fn identity_token(&self) -> V1IdentityToken {
161 self.identity_token
162 }
163 }
164
165 impl<'adv> Section<'adv, DataElementParseError> for DecryptedSection<'adv> {
166 type Iterator = DataElementParsingIterator<'adv>;
167
iter_data_elements(&self) -> Self::Iterator168 fn iter_data_elements(&self) -> Self::Iterator {
169 DataElementParsingIterator::new(self.plaintext)
170 }
171 }
172
173 /// Errors that can occur when deserializing a section
174 /// of a V1 advertisement.
175 #[derive(Debug, PartialEq, Eq)]
176 pub enum SectionDeserializeError {
177 /// The credential supplied did not match the section's identity data
178 IncorrectCredential,
179 /// Section data is malformed
180 ParseError,
181 /// The given arena is not large enough to hold the decrypted contents
182 ArenaOutOfSpace,
183 }
184
185 impl From<DeserializationError<MicVerificationError>> for SectionDeserializeError {
from(mic_deserialization_error: DeserializationError<MicVerificationError>) -> Self186 fn from(mic_deserialization_error: DeserializationError<MicVerificationError>) -> Self {
187 match mic_deserialization_error {
188 DeserializationError::VerificationError(MicVerificationError::MicMismatch) => {
189 Self::IncorrectCredential
190 }
191 DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace,
192 }
193 }
194 }
195
196 impl From<DeserializationError<SignatureVerificationError>> for SectionDeserializeError {
from( signature_deserialization_error: DeserializationError<SignatureVerificationError>, ) -> Self197 fn from(
198 signature_deserialization_error: DeserializationError<SignatureVerificationError>,
199 ) -> Self {
200 match signature_deserialization_error {
201 DeserializationError::VerificationError(
202 SignatureVerificationError::SignatureMissing,
203 ) => Self::ParseError,
204 DeserializationError::VerificationError(
205 SignatureVerificationError::SignatureMismatch,
206 ) => Self::IncorrectCredential,
207 DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace,
208 }
209 }
210 }
211
212 /// A ciphertext section which has not yet been
213 /// resolved to an identity, but for which some
214 /// `SectionIdentityResolutionContents` have been
215 /// pre-computed for speedy identity-resolution.
216 struct ResolvableCiphertextSection<'a> {
217 identity_resolution_contents: SectionIdentityResolutionContents,
218 ciphertext_section: CiphertextSection<'a>,
219 }
220
221 /// A collection of possibly-deserialized sections which are separated according
222 /// to whether/not they're intermediate encrypted sections (of either type)
223 /// or fully-deserialized, with a running count of the number of malformed sections.
224 /// Each potentially-valid section is tagged with a 0-based index derived from the original
225 /// section ordering as they appeared within the original advertisement to ensure
226 /// that the fully-deserialized advertisement may be correctly reconstructed.
227 struct SectionsInProcessing<'adv, M: MatchedCredential> {
228 deserialized_sections: ArrayVecOption<
229 (usize, V1DeserializedSection<'adv, M>),
230 { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
231 >,
232 encrypted_sections: ArrayVecOption<
233 (usize, ResolvableCiphertextSection<'adv>),
234 { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
235 >,
236 malformed_sections_count: usize,
237 }
238
239 impl<'adv, M: MatchedCredential> SectionsInProcessing<'adv, M> {
240 /// Attempts to parse a V1 advertisement's contents after the version header
241 /// into a collection of not-yet-fully-deserialized sections which may
242 /// require credentials to be decrypted.
from_advertisement_contents<C: CryptoProvider>( header: V1AdvHeader, remaining: &'adv [u8], ) -> Result<Self, AdvDeserializationError>243 fn from_advertisement_contents<C: CryptoProvider>(
244 header: V1AdvHeader,
245 remaining: &'adv [u8],
246 ) -> Result<Self, AdvDeserializationError> {
247 let int_sections =
248 parse_sections(header, remaining).map_err(|_| AdvDeserializationError::ParseError {
249 details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
250 })?;
251 let mut deserialized_sections = ArrayVecOption::default();
252 let mut encrypted_sections = ArrayVecOption::default();
253 // keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`.
254 for (idx, s) in int_sections.into_iter().enumerate() {
255 match s {
256 IntermediateSection::Plaintext(p) => {
257 deserialized_sections.push((idx, V1DeserializedSection::Plaintext(p)))
258 }
259 IntermediateSection::Ciphertext(ciphertext_section) => {
260 let identity_resolution_contents =
261 ciphertext_section.identity_resolution_contents::<C>();
262 let resolvable_ciphertext_section = ResolvableCiphertextSection {
263 identity_resolution_contents,
264 ciphertext_section,
265 };
266 encrypted_sections.push((idx, resolvable_ciphertext_section));
267 }
268 }
269 }
270 Ok(Self { deserialized_sections, encrypted_sections, malformed_sections_count: 0 })
271 }
272
273 /// Returns true iff we have resolved all sections to identities.
resolved_all_identities(&self) -> bool274 fn resolved_all_identities(&self) -> bool {
275 self.encrypted_sections.is_empty()
276 }
277
278 /// Runs through all of the encrypted sections in processing, and attempts
279 /// to use the given credential to decrypt them. Suitable for situations
280 /// where iterating over credentials is relatively slow compared to
281 /// the cost of iterating over sections-in-memory.
try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>( &mut self, arena: &mut DeserializationArenaAllocator<'adv>, crypto_material: C, match_data: M, ) -> Result<(), ArenaOutOfSpace>282 fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>(
283 &mut self,
284 arena: &mut DeserializationArenaAllocator<'adv>,
285 crypto_material: C,
286 match_data: M,
287 ) -> Result<(), ArenaOutOfSpace> {
288 let mut i = 0;
289 while i < self.encrypted_sections.len() {
290 let (section_idx, section): &(usize, ResolvableCiphertextSection) =
291 &self.encrypted_sections[i];
292 // Fast-path: Check for an identity match, ignore if there's no identity match.
293 let identity_resolution_contents = §ion.identity_resolution_contents;
294 let identity_resolution_material = match §ion.ciphertext_section {
295 CiphertextSection::MicEncrypted(m) => match m.contents.salt {
296 MultiSalt::Short(_) => crypto_material
297 .mic_short_salt_identity_resolution_material::<P>()
298 .into_raw_resolution_material(),
299 MultiSalt::Extended(_) => crypto_material
300 .mic_extended_salt_identity_resolution_material::<P>()
301 .into_raw_resolution_material(),
302 },
303
304 CiphertextSection::SignatureEncrypted(_) => crypto_material
305 .signed_identity_resolution_material::<P>()
306 .into_raw_resolution_material(),
307 };
308 match identity_resolution_contents.try_match::<P>(&identity_resolution_material) {
309 None => {
310 // Try again with another section
311 i += 1;
312 continue;
313 }
314 Some(identity_match) => {
315 // The identity matched, so now we need to more closely scrutinize
316 // the provided ciphertext. Try to decrypt and parse the section.
317 let deserialization_result = match §ion.ciphertext_section {
318 CiphertextSection::SignatureEncrypted(c) => c
319 .try_deserialize(
320 arena,
321 identity_match,
322 &crypto_material.signed_verification_material::<P>(),
323 )
324 .map_err(SectionDeserializeError::from),
325 CiphertextSection::MicEncrypted(c) => c
326 .try_deserialize(arena, identity_match, &crypto_material)
327 .map_err(SectionDeserializeError::from),
328 };
329 match deserialization_result {
330 Ok(s) => {
331 self.deserialized_sections.push((
332 *section_idx,
333 V1DeserializedSection::Decrypted(WithMatchedCredential::new(
334 match_data.clone(),
335 crypto_material.metadata_nonce::<P>(),
336 s,
337 )),
338 ));
339 }
340 Err(e) => match e {
341 SectionDeserializeError::IncorrectCredential => {
342 // keep it around to try with another credential
343 i += 1;
344 continue;
345 }
346 SectionDeserializeError::ParseError => {
347 // the credential worked, but the section itself was bogus
348 self.malformed_sections_count += 1;
349 }
350 SectionDeserializeError::ArenaOutOfSpace => {
351 return Err(ArenaOutOfSpace);
352 }
353 },
354 }
355 // By default, if we have an identity match, assume that decrypting the section worked,
356 // or that the section was somehow invalid.
357 // We don't care about maintaining order, so use O(1) remove
358 let _ = self.encrypted_sections.swap_remove(i);
359 // don't advance i -- it now points to a new element
360 }
361 }
362 }
363 Ok(())
364 }
365
366 /// Packages the current state of the deserialization process into a
367 /// `V1AdvertisementContents` representing a fully-deserialized V1 advertisement.
368 ///
369 /// This method should only be called after all sections were either successfully
370 /// decrypted or have had all relevant credentials checked against
371 /// them without obtaining a successful identity-match and/or subsequent
372 /// cryptographic verification of the section contents.
finished_with_decryption_attempts(mut self) -> V1AdvertisementContents<'adv, M>373 fn finished_with_decryption_attempts(mut self) -> V1AdvertisementContents<'adv, M> {
374 // Invalid sections = malformed sections + number of encrypted sections
375 // which we could not manage to decrypt with any of our credentials
376 let invalid_sections_count = self.malformed_sections_count + self.encrypted_sections.len();
377
378 // Put the deserialized sections back into the original ordering for
379 // the returned `V1AdvertisementContents`
380 // (Note: idx is unique, so unstable sort is ok)
381 self.deserialized_sections.sort_unstable_by_key(|(idx, _section)| *idx);
382 let ordered_sections = self.deserialized_sections.into_iter().map(|(_idx, s)| s).collect();
383 V1AdvertisementContents::new(ordered_sections, invalid_sections_count)
384 }
385 }
386
387 /// The contents of a deserialized and decrypted V1 advertisement.
388 #[derive(Debug, PartialEq, Eq)]
389 pub struct V1AdvertisementContents<'adv, M: MatchedCredential> {
390 sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>,
391 invalid_sections: usize,
392 }
393
394 impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> {
new( sections: ArrayVecOption< V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, >, invalid_sections: usize, ) -> Self395 fn new(
396 sections: ArrayVecOption<
397 V1DeserializedSection<'adv, M>,
398 NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
399 >,
400 invalid_sections: usize,
401 ) -> Self {
402 Self { sections, invalid_sections }
403 }
404
405 /// Destructures this V1 advertisement into just the sections
406 /// which could be successfully deserialized and decrypted
into_sections( self, ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>407 pub fn into_sections(
408 self,
409 ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT> {
410 self.sections
411 }
412
413 /// The sections that could be successfully deserialized and decrypted
sections(&self) -> impl ExactSizeIterator<Item = &V1DeserializedSection<M>>414 pub fn sections(&self) -> impl ExactSizeIterator<Item = &V1DeserializedSection<M>> {
415 self.sections.iter()
416 }
417
418 /// The number of sections that could not be parsed or decrypted.
invalid_sections_count(&self) -> usize419 pub fn invalid_sections_count(&self) -> usize {
420 self.invalid_sections
421 }
422 }
423
424 /// Advertisement content that was either already plaintext or has been decrypted.
425 #[derive(Debug, PartialEq, Eq)]
426 pub enum V1DeserializedSection<'adv, M: MatchedCredential> {
427 /// Section that was plaintext in the original advertisement
428 Plaintext(PlaintextSection<'adv>),
429 /// Section that was ciphertext in the original advertisement, and has been decrypted
430 /// with the credential in the [MatchedCredential]
431 Decrypted(WithMatchedCredential<M, DecryptedSection<'adv>>),
432 }
433
434 /// The level of integrity protection in an encrypted section
435 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
436 pub enum VerificationMode {
437 /// A symmetric MIC (message integrity code aka message authentication code) was verified.
438 ///
439 /// Since this is a symmetric operation, holders of the key material needed to verify a MIC
440 /// can also forge MICs.
441 Mic,
442 /// An asymmetric signature was verified.
443 ///
444 /// Since this is an asymmetric operation, only the holder of the private key can generate
445 /// signatures, so it offers a stronger level of authenticity protection than [Self::Mic].
446 Signature,
447 }
448
449 #[derive(PartialEq, Eq, Debug)]
450 pub(crate) struct SectionMic {
451 mic: [u8; Self::CONTENTS_LEN],
452 }
453
454 impl SectionMic {
455 /// 16-byte MIC
456 pub(crate) const CONTENTS_LEN: usize = 16;
457 }
458
459 impl From<[u8; 16]> for SectionMic {
from(value: [u8; 16]) -> Self460 fn from(value: [u8; 16]) -> Self {
461 SectionMic { mic: value }
462 }
463 }
464
465 impl TryFrom<&[u8]> for SectionMic {
466 type Error = TryFromSliceError;
467
try_from(value: &[u8]) -> Result<Self, Self::Error>468 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
469 let fixed_bytes: [u8; SectionMic::CONTENTS_LEN] = value.try_into()?;
470 Ok(Self { mic: fixed_bytes })
471 }
472 }
473
hi_bit_set(b: u8) -> bool474 fn hi_bit_set(b: u8) -> bool {
475 b & 0x80 > 0
476 }
477