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