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 use crate::{
16     credential::v1::*,
17     extended::{
18         deserialize::{DecryptedSection, SectionMic, VerificationMode},
19         section_signature_payload::*,
20         V1IdentityToken, NP_ADV_MAX_SECTION_LEN, V1_IDENTITY_TOKEN_LEN,
21     },
22     NP_SVC_UUID,
23 };
24 
25 use crate::deserialization_arena::DeserializationArenaAllocator;
26 
27 #[cfg(any(feature = "devtools", test))]
28 extern crate alloc;
29 
30 use crate::{
31     extended::{
32         deserialize::section::header::CiphertextExtendedIdentityToken,
33         salt::{MultiSalt, V1Salt},
34     },
35     header::V1AdvHeader,
36 };
37 #[cfg(any(feature = "devtools", test))]
38 use alloc::vec::Vec;
39 #[cfg(feature = "devtools")]
40 use array_view::ArrayView;
41 use core::fmt::Debug;
42 use crypto_provider::{
43     aes::ctr::{AesCtr, AesCtrNonce, NonceAndCounter},
44     hmac::Hmac,
45     CryptoProvider,
46 };
47 use np_hkdf::v1_salt::ExtendedV1Salt;
48 
49 #[cfg(test)]
50 use crate::extended::deserialize::encrypted_section::tests::IdentityResolutionOrDeserializationError;
51 
52 use super::ArenaOutOfSpace;
53 
54 #[cfg(test)]
55 mod tests;
56 
57 /// Represents the contents of an encrypted section
58 /// which are directly employed in identity resolution.
59 /// This does not incorporate any information about credentials.
60 ///
61 /// Should be re-used for multiple identity resolution attempts, if applicable, to amortize the
62 /// cost of calculating this data.
63 #[derive(PartialEq, Eq, Debug)]
64 pub(crate) struct SectionIdentityResolutionContents {
65     /// The ciphertext for the identity token
66     pub(crate) identity_token: CiphertextExtendedIdentityToken,
67     /// The 12-byte cryptographic nonce which is derived from the salt for a
68     /// particular section.
69     pub(crate) nonce: AesCtrNonce,
70 }
71 
72 impl SectionIdentityResolutionContents {
73     /// Decrypt the contained metadata-key ciphertext buffer whose bytes of plaintext are, maybe, the
74     /// metadata key for an NP identity, as specified via pre-calculated cryptographic materials
75     /// stored in some [`SectionIdentityResolutionMaterial`].
76     ///
77     /// This method does not decrypt an entire section's ciphertext aside from the metadata key,
78     /// and so verification (MIC or signature) needs to be done elsewhere.
79     ///
80     /// Returns `Some` if decrypting the metadata-key ciphertext produces plaintext whose HMAC
81     /// matches the expected MAC. Otherwise, returns `None`.
try_match<C: CryptoProvider>( &self, identity_resolution_material: &SectionIdentityResolutionMaterial, ) -> Option<IdentityMatch<C>>82     pub(crate) fn try_match<C: CryptoProvider>(
83         &self,
84         identity_resolution_material: &SectionIdentityResolutionMaterial,
85     ) -> Option<IdentityMatch<C>> {
86         let mut decrypt_buf = self.identity_token.0;
87         let mut cipher = C::AesCtr128::new(
88             &identity_resolution_material.aes_key,
89             NonceAndCounter::from_nonce(self.nonce),
90         );
91         cipher.apply_keystream(&mut decrypt_buf[..]);
92 
93         let identity_token_hmac_key: np_hkdf::NpHmacSha256Key =
94             identity_resolution_material.identity_token_hmac_key.into();
95         identity_token_hmac_key
96             .verify_hmac::<C>(
97                 &decrypt_buf[..],
98                 identity_resolution_material.expected_identity_token_hmac,
99             )
100             .ok()
101             .map(move |_| IdentityMatch {
102                 cipher,
103                 identity_token: V1IdentityToken(decrypt_buf),
104                 nonce: self.nonce,
105             })
106     }
107 }
108 
109 /// Carries data about an identity "match" for a particular section
110 /// against some particular V1 identity-resolution crypto-materials.
111 pub(crate) struct IdentityMatch<C: CryptoProvider> {
112     /// Decrypted identity token
113     identity_token: V1IdentityToken,
114     /// The AES-Ctr nonce to be used in section decryption and verification
115     nonce: AesCtrNonce,
116     /// The state of the AES-Ctr cipher after successfully decrypting
117     /// the metadata key ciphertext. May be used to decrypt the remainder
118     /// of the section ciphertext.
119     cipher: C::AesCtr128,
120 }
121 
122 /// Maximum length of a section's contents, after the metadata-key.
123 #[allow(unused)]
124 const MAX_SECTION_CONTENTS_LEN: usize = NP_ADV_MAX_SECTION_LEN - V1_IDENTITY_TOKEN_LEN;
125 
126 /// Bare, decrypted contents from an encrypted section,
127 /// including the decrypted metadata key and the decrypted section ciphertext.
128 /// At this point, verification of the plaintext contents has not yet been performed.
129 pub(crate) struct RawDecryptedSection<'a> {
130     // Only used with feature = "devtools"
131     #[allow(unused)]
132     pub(crate) identity_token: V1IdentityToken,
133     pub(crate) nonce: AesCtrNonce,
134     pub(crate) plaintext_contents: &'a [u8],
135 }
136 
137 #[cfg(feature = "devtools")]
138 impl<'a> RawDecryptedSection<'a> {
to_raw_bytes(&self) -> ArrayView<u8, NP_ADV_MAX_SECTION_LEN>139     pub(crate) fn to_raw_bytes(&self) -> ArrayView<u8, NP_ADV_MAX_SECTION_LEN> {
140         let mut result = Vec::new();
141         result.extend_from_slice(self.identity_token.as_slice());
142         result.extend_from_slice(self.plaintext_contents);
143         ArrayView::try_from_slice(&result).expect("Won't panic because of the involved lengths")
144     }
145 }
146 
147 /// Represents the contents of an encrypted section,
148 /// independent of the encryption type.
149 #[derive(PartialEq, Eq, Debug)]
150 pub(crate) struct EncryptedSectionContents<'adv, S> {
151     adv_header: V1AdvHeader,
152     format_bytes: &'adv [u8],
153     pub(crate) salt: S,
154     /// Ciphertext of identity token (part of section header)
155     identity_token: CiphertextExtendedIdentityToken,
156     /// The portion of the ciphertext that has been encrypted.
157     /// Length must be in `[0, NP_ADV_MAX_SECTION_LEN]`.
158     section_contents: &'adv [u8],
159     // The length byte exactly as it appears in the adv. This is the length of the encrypted
160     // contents plus any additional bytes of suffix
161     total_section_contents_len: u8,
162 }
163 
164 impl<'adv, S: V1Salt> EncryptedSectionContents<'adv, S> {
165     /// Constructs a representation of the contents of an encrypted V1 section
166     /// from the advertisement header, the section header, information about
167     /// the encryption used for identity verification, identity metadata,
168     /// and the entire section contents as an undecrypted ciphertext.
169     ///
170     /// # Panics
171     /// If `all_ciphertext` is greater than `NP_ADV_MAX_SECTION_LEN` bytes,
172     /// or less than `IDENTITY_TOKEN_LEN` bytes.
new( adv_header: V1AdvHeader, format_bytes: &'adv [u8], salt: S, identity_token: CiphertextExtendedIdentityToken, section_contents_len: u8, section_contents: &'adv [u8], ) -> Self173     pub(crate) fn new(
174         adv_header: V1AdvHeader,
175         format_bytes: &'adv [u8],
176         salt: S,
177         identity_token: CiphertextExtendedIdentityToken,
178         section_contents_len: u8,
179         section_contents: &'adv [u8],
180     ) -> Self {
181         assert!(section_contents.len() <= NP_ADV_MAX_SECTION_LEN - V1_IDENTITY_TOKEN_LEN);
182         Self {
183             adv_header,
184             format_bytes,
185             salt,
186             identity_token,
187             total_section_contents_len: section_contents_len,
188             section_contents,
189         }
190     }
191 
192     /// Gets the salt for this encrypted section
salt(&self) -> MultiSalt193     pub(crate) fn salt(&self) -> MultiSalt {
194         self.salt.into()
195     }
196 
197     /// Constructs some cryptographic contents for section identity-resolution
198     /// out of the entire contents of this encrypted section.
compute_identity_resolution_contents<C: CryptoProvider>( &self, ) -> SectionIdentityResolutionContents199     pub(crate) fn compute_identity_resolution_contents<C: CryptoProvider>(
200         &self,
201     ) -> SectionIdentityResolutionContents {
202         let nonce = self.salt.compute_nonce::<C>();
203         SectionIdentityResolutionContents { nonce, identity_token: self.identity_token }
204     }
205 
206     /// Given an identity-match, decrypts the ciphertext in this encrypted section
207     /// and returns the raw bytes of the decrypted plaintext.
decrypt_ciphertext<C: CryptoProvider>( &self, arena: &mut DeserializationArenaAllocator<'adv>, mut identity_match: IdentityMatch<C>, ) -> Result<RawDecryptedSection<'adv>, ArenaOutOfSpace>208     pub(crate) fn decrypt_ciphertext<C: CryptoProvider>(
209         &self,
210         arena: &mut DeserializationArenaAllocator<'adv>,
211         mut identity_match: IdentityMatch<C>,
212     ) -> Result<RawDecryptedSection<'adv>, ArenaOutOfSpace> {
213         // Fill decrypt_buf with the ciphertext after the section length
214         let decrypt_buf =
215             arena
216                 .allocate(u8::try_from(self.section_contents.len()).expect(
217                     "section_contents.len() must be in [0, NP_ADV_MAX_SECTION_CONTENTS_LEN - EXTENDED_IDENTITY_TOKEN_LEN]",
218                 ))?;
219         decrypt_buf.copy_from_slice(self.section_contents);
220 
221         // Decrypt everything after the metadata key
222         identity_match.cipher.apply_keystream(decrypt_buf);
223 
224         Ok(RawDecryptedSection {
225             identity_token: identity_match.identity_token,
226             nonce: identity_match.nonce,
227             plaintext_contents: decrypt_buf,
228         })
229     }
230 
231     /// Try decrypting into some raw bytes given some raw identity-resolution material.
232     #[cfg(feature = "devtools")]
try_resolve_identity_and_decrypt<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'adv>, identity_resolution_material: &SectionIdentityResolutionMaterial, ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>>233     pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
234         &self,
235         allocator: &mut DeserializationArenaAllocator<'adv>,
236         identity_resolution_material: &SectionIdentityResolutionMaterial,
237     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
238         self.compute_identity_resolution_contents::<P>()
239             .try_match(identity_resolution_material)
240             .map(|identity_match| {
241                 Ok(self.decrypt_ciphertext::<P>(allocator, identity_match)?.to_raw_bytes())
242             })
243     }
244 }
245 
246 /// An encrypted section which is verified using a ed25519 signature
247 #[derive(PartialEq, Eq, Debug)]
248 pub(crate) struct SignatureEncryptedSection<'a> {
249     pub(crate) contents: EncryptedSectionContents<'a, ExtendedV1Salt>,
250 }
251 
252 impl<'a> SignatureEncryptedSection<'a> {
253     /// Try deserializing into a [`DecryptedSection`] given an identity-match
254     /// with some paired verification material for the matched identity.
try_deserialize<P>( &self, arena: &mut DeserializationArenaAllocator<'a>, identity_match: IdentityMatch<P>, verification_material: &SignedSectionVerificationMaterial, ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>> where P: CryptoProvider,255     pub(crate) fn try_deserialize<P>(
256         &self,
257         arena: &mut DeserializationArenaAllocator<'a>,
258         identity_match: IdentityMatch<P>,
259         verification_material: &SignedSectionVerificationMaterial,
260     ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>>
261     where
262         P: CryptoProvider,
263     {
264         let identity_token = identity_match.identity_token;
265         let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?;
266         let nonce = raw_decrypted.nonce;
267         let remaining = raw_decrypted.plaintext_contents;
268 
269         let (plaintext_des, sig) = remaining
270             .split_last_chunk::<{ crypto_provider::ed25519::SIGNATURE_LENGTH }>()
271             .ok_or(SignatureVerificationError::SignatureMissing)?;
272 
273         let expected_signature = crypto_provider::ed25519::Signature::from(*sig);
274 
275         let section_signature_payload = SectionSignaturePayload::new(
276             self.contents.format_bytes,
277             self.contents.salt.bytes(),
278             &nonce,
279             identity_token.as_slice(),
280             self.contents.total_section_contents_len,
281             plaintext_des,
282         );
283 
284         let public_key = verification_material.signature_verification_public_key();
285 
286         section_signature_payload.verify::<P::Ed25519>(expected_signature, &public_key).map_err(
287             |e| {
288                 // Length of the payload should fit in the signature verification buffer.
289                 debug_assert!(e != np_ed25519::SignatureVerificationError::PayloadTooBig);
290                 SignatureVerificationError::SignatureMismatch
291             },
292         )?;
293 
294         Ok(DecryptedSection::new(
295             VerificationMode::Signature,
296             self.contents.salt(),
297             identity_token,
298             plaintext_des,
299         ))
300     }
301 
302     /// Try decrypting into some raw bytes given some raw signed crypto-material.
303     #[cfg(feature = "devtools")]
try_resolve_identity_and_decrypt<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &SignedSectionIdentityResolutionMaterial, ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>>304     pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
305         &self,
306         allocator: &mut DeserializationArenaAllocator<'a>,
307         identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
308     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
309         self.contents.try_resolve_identity_and_decrypt::<P>(
310             allocator,
311             identity_resolution_material.as_raw_resolution_material(),
312         )
313     }
314 
315     /// Try deserializing into a [Section] given some raw signed crypto-material.
316     ///
317     /// A less-efficient, one-shot way of getting
318     /// [EncryptedSectionContents::compute_identity_resolution_contents] and then attempting
319     /// deserialization.
320     ///
321     /// Normally, id resolution contents would be calculated for a bunch of sections, and then have
322     /// many identities tried on them. This just works for one identity.
323     #[cfg(test)]
try_resolve_identity_and_deserialize<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &SignedSectionIdentityResolutionMaterial, verification_material: &SignedSectionVerificationMaterial, ) -> Result< DecryptedSection, IdentityResolutionOrDeserializationError<SignatureVerificationError>, >324     pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
325         &self,
326         allocator: &mut DeserializationArenaAllocator<'a>,
327         identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
328         verification_material: &SignedSectionVerificationMaterial,
329     ) -> Result<
330         DecryptedSection,
331         IdentityResolutionOrDeserializationError<SignatureVerificationError>,
332     > {
333         match self
334             .contents
335             .compute_identity_resolution_contents::<P>()
336             .try_match::<P>(identity_resolution_material.as_raw_resolution_material())
337         {
338             Some(identity_match) => self
339                 .try_deserialize(allocator, identity_match, verification_material)
340                 .map_err(|e| e.into()),
341             None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError),
342         }
343     }
344 }
345 
346 /// An error when attempting to deserialize an encrypted advertisement,
347 /// assuming that we already have an identity-match.
348 ///
349 /// This should not be exposed publicly, since it's too
350 /// detailed.
351 #[derive(Debug, PartialEq, Eq)]
352 pub(crate) enum DeserializationError<V: VerificationError> {
353     /// Verification failed
354     VerificationError(V),
355     /// The given arena ran out of space
356     ArenaOutOfSpace,
357 }
358 
359 impl<V: VerificationError> From<ArenaOutOfSpace> for DeserializationError<V> {
from(_: ArenaOutOfSpace) -> Self360     fn from(_: ArenaOutOfSpace) -> Self {
361         Self::ArenaOutOfSpace
362     }
363 }
364 
365 impl<V: VerificationError> From<V> for DeserializationError<V> {
from(verification_error: V) -> Self366     fn from(verification_error: V) -> Self {
367         Self::VerificationError(verification_error)
368     }
369 }
370 
371 /// Common trait bound for errors which arise during
372 /// verification of encrypted section contents.
373 ///
374 /// Implementors should not be exposed publicly, since it's too
375 /// detailed.
376 pub(crate) trait VerificationError: Debug + PartialEq + Eq {}
377 
378 /// An error when attempting to verify a signature
379 #[derive(Debug, PartialEq, Eq)]
380 pub(crate) enum SignatureVerificationError {
381     /// The provided signature did not match the calculated signature
382     SignatureMismatch,
383     /// The provided signature is missing
384     SignatureMissing,
385 }
386 
387 impl VerificationError for SignatureVerificationError {}
388 
389 /// An encrypted section whose contents are verified to match a message integrity code (MIC)
390 #[derive(PartialEq, Eq, Debug)]
391 pub(crate) struct MicEncryptedSection<'a> {
392     pub(crate) contents: EncryptedSectionContents<'a, MultiSalt>,
393     pub(crate) mic: SectionMic,
394 }
395 
396 impl<'a> MicEncryptedSection<'a> {
397     /// Try deserializing into a [`DecryptedSection`].
398     ///
399     /// Returns an error if the credential is incorrect or if the section data is malformed.
try_deserialize<P>( &self, allocator: &mut DeserializationArenaAllocator<'a>, identity_match: IdentityMatch<P>, crypto_material: &impl V1DiscoveryCryptoMaterial, ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>> where P: CryptoProvider,400     pub(crate) fn try_deserialize<P>(
401         &self,
402         allocator: &mut DeserializationArenaAllocator<'a>,
403         identity_match: IdentityMatch<P>,
404         crypto_material: &impl V1DiscoveryCryptoMaterial,
405     ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>>
406     where
407         P: CryptoProvider,
408     {
409         let hmac_key = match self.contents.salt {
410             MultiSalt::Short(_) => {
411                 crypto_material.mic_short_salt_verification_material::<P>().mic_hmac_key()
412             }
413             MultiSalt::Extended(_) => {
414                 crypto_material.mic_extended_salt_verification_material::<P>().mic_hmac_key()
415             }
416         };
417 
418         let mut mic_hmac = hmac_key.build_hmac::<P>();
419         // if mic is ok, the section was generated by someone holding at least the shared credential
420         mic_hmac.update(&NP_SVC_UUID);
421         mic_hmac.update(&[self.contents.adv_header.contents()]);
422         // section format
423         mic_hmac.update(self.contents.format_bytes);
424         // salt bytes
425         mic_hmac.update(self.contents.salt.as_slice());
426         // nonce
427         mic_hmac.update(identity_match.nonce.as_slice());
428         // ciphertext identity token
429         mic_hmac.update(self.contents.identity_token.0.as_slice());
430         // section payload len
431         mic_hmac.update(&[self.contents.total_section_contents_len]);
432         // rest of encrypted contents
433         mic_hmac.update(self.contents.section_contents);
434         mic_hmac
435             // adv only contains first 16 bytes of HMAC
436             .verify_truncated_left(&self.mic.mic)
437             .map_err(|_e| MicVerificationError::MicMismatch)?;
438 
439         // plaintext identity token, already decrypted during identity match
440         let identity_token = identity_match.identity_token;
441         let raw_decrypted = self.contents.decrypt_ciphertext(allocator, identity_match)?;
442         Ok(DecryptedSection::new(
443             VerificationMode::Mic,
444             self.contents.salt(),
445             identity_token,
446             raw_decrypted.plaintext_contents,
447         ))
448     }
449 
450     /// Try decrypting into some raw bytes given some raw unsigned crypto-material.
451     #[cfg(feature = "devtools")]
try_resolve_short_salt_identity_and_decrypt<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &MicShortSaltSectionIdentityResolutionMaterial, ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>>452     pub(crate) fn try_resolve_short_salt_identity_and_decrypt<P: CryptoProvider>(
453         &self,
454         allocator: &mut DeserializationArenaAllocator<'a>,
455         identity_resolution_material: &MicShortSaltSectionIdentityResolutionMaterial,
456     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
457         self.contents.try_resolve_identity_and_decrypt::<P>(
458             allocator,
459             identity_resolution_material.as_raw_resolution_material(),
460         )
461     }
462 
463     /// Try decrypting into some raw bytes given some raw unsigned crypto-material.
464     #[cfg(feature = "devtools")]
try_resolve_extended_salt_identity_and_decrypt<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &MicExtendedSaltSectionIdentityResolutionMaterial, ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>>465     pub(crate) fn try_resolve_extended_salt_identity_and_decrypt<P: CryptoProvider>(
466         &self,
467         allocator: &mut DeserializationArenaAllocator<'a>,
468         identity_resolution_material: &MicExtendedSaltSectionIdentityResolutionMaterial,
469     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
470         self.contents.try_resolve_identity_and_decrypt::<P>(
471             allocator,
472             identity_resolution_material.as_raw_resolution_material(),
473         )
474     }
475 
476     /// Try deserializing into a [Section] given some raw unsigned crypto-material.
477     #[cfg(test)]
try_resolve_identity_and_deserialize<P: CryptoProvider>( &self, allocator: &mut DeserializationArenaAllocator<'a>, crypto_material: &impl V1DiscoveryCryptoMaterial, ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>>478     pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
479         &self,
480         allocator: &mut DeserializationArenaAllocator<'a>,
481         crypto_material: &impl V1DiscoveryCryptoMaterial,
482     ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>>
483     {
484         let section_identity_resolution_contents =
485             self.contents.compute_identity_resolution_contents::<P>();
486 
487         let identity_match = match self.contents.salt {
488             MultiSalt::Short(_) => section_identity_resolution_contents.try_match::<P>(
489                 crypto_material
490                     .mic_short_salt_identity_resolution_material::<P>()
491                     .as_raw_resolution_material(),
492             ),
493             MultiSalt::Extended(_) => section_identity_resolution_contents.try_match::<P>(
494                 crypto_material
495                     .mic_extended_salt_identity_resolution_material::<P>()
496                     .as_raw_resolution_material(),
497             ),
498         }
499         .ok_or(IdentityResolutionOrDeserializationError::IdentityMatchingError)?;
500 
501         self.try_deserialize(allocator, identity_match, crypto_material).map_err(|e| e.into())
502     }
503 }
504 
505 #[derive(Debug, PartialEq, Eq)]
506 pub(crate) enum MicVerificationError {
507     /// Calculated MIC did not match MIC from section
508     MicMismatch,
509 }
510 
511 impl VerificationError for MicVerificationError {}
512