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 //! V0 advertisement support.
16 
17 use crate::credential::matched::{MatchedCredential, WithMatchedCredential};
18 use crate::legacy::deserialize::intermediate::{IntermediateAdvContents, UnencryptedAdvContents};
19 use crate::{
20     credential::{
21         book::CredentialBook, v0::V0DiscoveryCryptoMaterial, DiscoveryMetadataCryptoMaterial,
22     },
23     header::V0Encoding,
24     legacy::deserialize::{DecryptError, DecryptedAdvContents},
25     AdvDeserializationError,
26 };
27 use core::fmt;
28 use crypto_provider::CryptoProvider;
29 
30 pub mod data_elements;
31 pub mod deserialize;
32 pub mod serialize;
33 
34 #[cfg(test)]
35 mod random_data_elements;
36 
37 /// Advertisement capacity after 5 bytes of BLE header and 2 bytes of svc UUID are reserved from a
38 /// 31-byte advertisement
39 pub const BLE_4_ADV_SVC_MAX_CONTENT_LEN: usize = 24;
40 /// Maximum possible advertisement NP-level content: packet size minus 1 for version header
41 const NP_MAX_ADV_CONTENT_LEN: usize = BLE_4_ADV_SVC_MAX_CONTENT_LEN - 1;
42 /// Minimum advertisement NP-level content.
43 /// Only meaningful for unencrypted advertisements, as LDT advertisements already have salt, token, etc.
44 const NP_MIN_ADV_CONTENT_LEN: usize = 1;
45 /// Max length of an individual DE's content
46 pub(crate) const NP_MAX_DE_CONTENT_LEN: usize = NP_MAX_ADV_CONTENT_LEN - 1;
47 
48 /// Marker type to allow disambiguating between plaintext and encrypted packets at compile time.
49 ///
50 /// See also [PacketFlavorEnum] for when runtime flavor checks are more suitable.
51 pub trait PacketFlavor: fmt::Debug + Clone + Copy + PartialEq + Eq {
52     /// The corresponding [PacketFlavorEnum] variant.
53     const ENUM_VARIANT: PacketFlavorEnum;
54 }
55 
56 /// Marker type for plaintext packets (public identity and no identity).
57 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
58 pub struct Plaintext;
59 
60 impl PacketFlavor for Plaintext {
61     const ENUM_VARIANT: PacketFlavorEnum = PacketFlavorEnum::Plaintext;
62 }
63 
64 /// Marker type for ciphertext packets (private, trusted, and provisioned identity).
65 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
66 pub struct Ciphertext;
67 
68 impl PacketFlavor for Ciphertext {
69     const ENUM_VARIANT: PacketFlavorEnum = PacketFlavorEnum::Ciphertext;
70 }
71 
72 /// An enum version of the implementors of [PacketFlavor] for use cases where runtime checking is
73 /// a better fit than compile time checking.
74 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
75 pub enum PacketFlavorEnum {
76     /// Corresponds to [Plaintext].
77     Plaintext,
78     /// Corresponds to [Ciphertext].
79     Ciphertext,
80 }
81 
82 /// Deserialize and decrypt the contents of a v0 adv after the version header
deser_decrypt_v0<'adv, 'cred, B, P>( encoding: V0Encoding, cred_book: &'cred B, remaining: &'adv [u8], ) -> Result<V0AdvertisementContents<'adv, B::Matched>, AdvDeserializationError> where B: CredentialBook<'cred>, P: CryptoProvider,83 pub(crate) fn deser_decrypt_v0<'adv, 'cred, B, P>(
84     encoding: V0Encoding,
85     cred_book: &'cred B,
86     remaining: &'adv [u8],
87 ) -> Result<V0AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
88 where
89     B: CredentialBook<'cred>,
90     P: CryptoProvider,
91 {
92     match IntermediateAdvContents::deserialize::<P>(encoding, remaining)? {
93         IntermediateAdvContents::Unencrypted(p) => Ok(V0AdvertisementContents::Plaintext(p)),
94         IntermediateAdvContents::Ldt(c) => {
95             for (crypto_material, matched) in cred_book.v0_iter() {
96                 let ldt = crypto_material.ldt_adv_cipher::<P>();
97                 match c.try_decrypt(&ldt) {
98                     Ok(c) => {
99                         let metadata_nonce = crypto_material.metadata_nonce::<P>();
100                         return Ok(V0AdvertisementContents::Decrypted(WithMatchedCredential::new(
101                             matched,
102                             metadata_nonce,
103                             c,
104                         )));
105                     }
106                     Err(e) => match e {
107                         DecryptError::DecryptOrVerifyError => continue,
108                     },
109                 }
110             }
111             Ok(V0AdvertisementContents::NoMatchingCredentials)
112         }
113     }
114 }
115 
116 /// Advertisement content that was either already plaintext or has been decrypted.
117 #[derive(Debug, PartialEq, Eq)]
118 pub enum V0AdvertisementContents<'adv, M: MatchedCredential> {
119     /// Contents of a plaintext advertisement
120     Plaintext(UnencryptedAdvContents<'adv>),
121     /// Contents that was ciphertext in the original advertisement, and has been decrypted
122     /// with the credential in the [MatchedCredential]
123     Decrypted(WithMatchedCredential<M, DecryptedAdvContents>),
124     /// The advertisement was encrypted, but no credentials matched
125     NoMatchingCredentials,
126 }
127 
128 #[cfg(test)]
129 mod tests {
130     mod coverage_gaming {
131         use crate::legacy::{Ciphertext, PacketFlavorEnum, Plaintext};
132 
133         extern crate std;
134 
135         use std::format;
136 
137         #[test]
plaintext_flavor()138         fn plaintext_flavor() {
139             // debug
140             let _ = format!("{:?}", Plaintext);
141             // eq and clone
142             assert_eq!(Plaintext, Plaintext.clone())
143         }
144 
145         #[test]
ciphertext_flavor()146         fn ciphertext_flavor() {
147             // debug
148             let _ = format!("{:?}", Ciphertext);
149             // eq and clone
150             assert_eq!(Ciphertext, Ciphertext.clone())
151         }
152 
153         #[allow(clippy::clone_on_copy)]
154         #[test]
flavor_enum()155         fn flavor_enum() {
156             // clone
157             let _ = PacketFlavorEnum::Plaintext.clone();
158         }
159     }
160 }
161