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 for V0 advertisements.
16 //!
17 //! # Examples
18 //!
19 //! Serializing a plaintext advertisement:
20 //!
21 //! ```
22 //! use np_adv::{legacy::{data_elements::tx_power::TxPowerDataElement, serialize::*}, shared_data::*};
23 //!
24 //! let mut builder = AdvBuilder::new(UnencryptedEncoder);
25 //! builder
26 //! .add_data_element(TxPowerDataElement::from(TxPower::try_from(3).expect("3 is a valid TxPower value")))
27 //! .unwrap();
28 //! let packet = builder.into_advertisement().unwrap();
29 //! assert_eq!(
30 //! &[
31 //! 0x00, // Adv Header
32 //! 0x15, 0x03, // tx power de
33 //! ],
34 //! packet.as_slice()
35 //! );
36 //! ```
37 //!
38 //! Serializing an encrypted advertisement:
39 //!
40 //! ```
41 //! use np_adv::{shared_data::*, legacy::{data_elements::de_type::*, data_elements::tx_power::TxPowerDataElement, serialize::*, *}};
42 //! use np_adv::credential::{v0::{V0, V0BroadcastCredential}};
43 //! use crypto_provider::CryptoProvider;
44 //! use crypto_provider_default::CryptoProviderImpl;
45 //! use ldt_np_adv::{V0Salt, V0IdentityToken};
46 //!
47 //! // Generate these from proper CSPRNGs -- using fixed data here
48 //! let metadata_key = V0IdentityToken::from([0x33; 14]);
49 //! let salt = V0Salt::from([0x01, 0x02]);
50 //! let key_seed = [0x44; 32];
51 //!
52 //! let broadcast_cred = V0BroadcastCredential::new(
53 //! key_seed,
54 //! metadata_key,
55 //! );
56 //!
57 //! let mut builder = AdvBuilder::new(LdtEncoder::<CryptoProviderImpl>::new(
58 //! salt,
59 //! &broadcast_cred,
60 //! ));
61 //!
62 //! builder
63 //! .add_data_element(TxPowerDataElement::from(TxPower::try_from(3).expect("3 is a valid TxPower value")))
64 //! .unwrap();
65 //!
66 //! let packet = builder.into_advertisement().unwrap();
67 //! ```
68 use core::fmt;
69
70 use array_view::ArrayView;
71 use crypto_provider::CryptoProvider;
72 use ldt::LdtCipher;
73 use ldt_np_adv::{V0IdentityToken, V0_IDENTITY_TOKEN_LEN};
74 use sink::Sink;
75
76 use crate::credential::v0::V0BroadcastCredential;
77 use crate::{
78 extended::to_array_view,
79 header::{VERSION_HEADER_V0_LDT, VERSION_HEADER_V0_UNENCRYPTED},
80 legacy::{
81 data_elements::{
82 de_type::{DeActualLength, DeEncodedLength, DeTypeCode},
83 DataElementSerializationBuffer, DataElementSerializeError, SerializeDataElement,
84 },
85 Ciphertext, PacketFlavor, Plaintext, BLE_4_ADV_SVC_MAX_CONTENT_LEN, NP_MAX_ADV_CONTENT_LEN,
86 NP_MIN_ADV_CONTENT_LEN,
87 },
88 };
89
90 mod header;
91
92 #[cfg(test)]
93 pub(crate) mod tests;
94
95 /// An encoder used in serializing an advertisement.
96 pub trait AdvEncoder: fmt::Debug {
97 /// The flavor of packet this identity produces
98 type Flavor: PacketFlavor;
99 /// The error returned if postprocessing fails
100 type Error: fmt::Debug;
101
102 /// The version header to put at the start of the advertisement.
103 const VERSION_HEADER: u8;
104
105 /// The V0-specific header to be written at the start of the advertisement
106 /// immediately after the version header.
header(&self) -> header::V0Header107 fn header(&self) -> header::V0Header;
108
109 /// Perform identity-specific manipulation to the serialized DEs to produce the final
110 /// advertisement format.
111 ///
112 /// `buf` has the contents of [Self::header] written at the start, followed
113 /// by all of the DEs added to a packet. It does not include the NP top level header.
114 ///
115 /// The first `header_len` bytes of `buf` are the bytes produced by the call to [Self::header].
116 ///
117 /// Returns `Ok` if postprocessing was successful and the packet is finished, or `Err` if
118 /// postprocessing failed and the packet should be discarded.
postprocess(&self, header_len: usize, buf: &mut [u8]) -> Result<(), Self::Error>119 fn postprocess(&self, header_len: usize, buf: &mut [u8]) -> Result<(), Self::Error>;
120 }
121
122 /// An unencrypted encoder with no associated identity.
123 #[derive(Debug)]
124 pub struct UnencryptedEncoder;
125
126 impl AdvEncoder for UnencryptedEncoder {
127 type Flavor = Plaintext;
128 type Error = UnencryptedEncodeError;
129 const VERSION_HEADER: u8 = VERSION_HEADER_V0_UNENCRYPTED;
130
header(&self) -> header::V0Header131 fn header(&self) -> header::V0Header {
132 header::V0Header::unencrypted()
133 }
134
postprocess(&self, _header_len: usize, buf: &mut [u8]) -> Result<(), Self::Error>135 fn postprocess(&self, _header_len: usize, buf: &mut [u8]) -> Result<(), Self::Error> {
136 if buf.len() < NP_MIN_ADV_CONTENT_LEN {
137 Err(UnencryptedEncodeError::InvalidLength)
138 } else {
139 Ok(())
140 }
141 }
142 }
143
144 /// Unencrypted encoding errors
145 #[derive(Debug, PartialEq, Eq)]
146 pub enum UnencryptedEncodeError {
147 /// The advertisement content was outside the valid range
148 InvalidLength,
149 }
150
151 /// Encoder used for encrypted packets (private, trusted, provisioned) that encrypts other DEs
152 /// (as well as the metadata key).
153 pub struct LdtEncoder<C: CryptoProvider> {
154 salt: ldt_np_adv::V0Salt,
155 identity_token: V0IdentityToken,
156 ldt_enc: ldt_np_adv::NpLdtEncryptCipher<C>,
157 }
158
159 // Exclude sensitive members
160 impl<C: CryptoProvider> fmt::Debug for LdtEncoder<C> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 write!(f, "LdtEncoder {{ salt: {:X?} }}", self.salt)
163 }
164 }
165
166 impl<C: CryptoProvider> LdtEncoder<C> {
167 /// Build an `LdtIdentity` for the provided identity type, salt, and
168 /// broadcast crypto-materials.
new(salt: ldt_np_adv::V0Salt, broadcast_cred: &V0BroadcastCredential) -> Self169 pub fn new(salt: ldt_np_adv::V0Salt, broadcast_cred: &V0BroadcastCredential) -> Self {
170 let identity_token = broadcast_cred.identity_token();
171 let key_seed = broadcast_cred.key_seed();
172 let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
173 let ldt_key = key_seed_hkdf.v0_ldt_key();
174 let ldt_enc = ldt_np_adv::NpLdtEncryptCipher::<C>::new(&ldt_key);
175
176 Self { salt, identity_token, ldt_enc }
177 }
178 }
179
180 impl<C: CryptoProvider> AdvEncoder for LdtEncoder<C> {
181 type Flavor = Ciphertext;
182 type Error = LdtEncodeError;
183 const VERSION_HEADER: u8 = VERSION_HEADER_V0_LDT;
184
header(&self) -> header::V0Header185 fn header(&self) -> header::V0Header {
186 header::V0Header::ldt_short_salt(self.salt, self.identity_token)
187 }
188
postprocess(&self, header_len: usize, buf: &mut [u8]) -> Result<(), LdtEncodeError>189 fn postprocess(&self, header_len: usize, buf: &mut [u8]) -> Result<(), LdtEncodeError> {
190 // encrypt everything after v0 format and salt
191 self.ldt_enc
192 .encrypt(
193 &mut buf[header_len - V0_IDENTITY_TOKEN_LEN..],
194 &ldt_np_adv::salt_padder::<C>(self.salt),
195 )
196 .map_err(|e| match e {
197 // too short, not enough DEs
198 ldt::LdtError::InvalidLength(_) => LdtEncodeError::InvalidLength,
199 })
200 }
201 }
202
203 /// LDT encoding errors
204 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
205 pub enum LdtEncodeError {
206 /// The minimum size (2 bytes) or maximum size (7 bytes for BLE 4.2
207 /// advertisements) of DE data was not met
208 InvalidLength,
209 }
210
211 /// The serialized form of a V0 adv, suitable for setting as the value for the NP UUID svc data.
212 pub type SerializedAdv = ArrayView<u8, BLE_4_ADV_SVC_MAX_CONTENT_LEN>;
213
214 /// Accumulates DEs, then massages the serialized DEs with the configured identity to produce
215 /// the final advertisement.
216 #[derive(Debug)]
217 pub struct AdvBuilder<E: AdvEncoder> {
218 /// The first byte is for the adv header, then the next I::OVERHEAD_LEN bytes are set aside for
219 /// use by [AdvEncoder::postprocess].
220 buffer: [u8; BLE_4_ADV_SVC_MAX_CONTENT_LEN],
221 /// How much of the buffer is consumed.
222 /// Always <= buffer length, and at least 2
223 len: usize,
224 /// How long the header was
225 header_len: usize,
226 encoder: E,
227 }
228
229 impl<E: AdvEncoder> AdvBuilder<E> {
230 /// Create an empty AdvBuilder with the provided identity.
new(encoder: E) -> AdvBuilder<E>231 pub fn new(encoder: E) -> AdvBuilder<E> {
232 let mut buffer = [0; BLE_4_ADV_SVC_MAX_CONTENT_LEN];
233 buffer[0] = E::VERSION_HEADER;
234
235 // encode the rest of the V0 header
236 let header = encoder.header();
237 let header_len = header.as_slice().len();
238 // len will be at least 1, important for max_de_len safety in add_data_element
239 let len = 1 + header_len;
240 // check for broken identities
241 debug_assert!(len < buffer.len());
242 buffer[1..=header_len].copy_from_slice(header.as_slice());
243
244 AdvBuilder {
245 // conveniently the first byte is already 0, which is the correct header for v0.
246 // 3 bit version (000 since this is version 0), 5 bit reserved (also all 0)
247 buffer,
248 len,
249 header_len,
250 encoder,
251 }
252 }
253
254 /// Add the data element to the packet buffer, if there is space.
add_data_element<D: SerializeDataElement<E::Flavor>>( &mut self, data_element: D, ) -> Result<(), AddDataElementError>255 pub fn add_data_element<D: SerializeDataElement<E::Flavor>>(
256 &mut self,
257 data_element: D,
258 ) -> Result<(), AddDataElementError> {
259 // invariant: self.len <= buffer length
260 debug_assert!(self.len <= self.buffer.len());
261 let dest = &mut self.buffer[self.len..];
262 // because self.len is at least 1, the dest will be no more than
263 // `[BLE_ADV_SVC_CONTENT_LEN] - 1 = [NP_MAX_ADV_CONTENT_LEN]`
264 let de_buf = serialize_de(&data_element, dest.len())?;
265 dest[..de_buf.len()].copy_from_slice(de_buf.as_slice());
266 // invariant: de_buf fit in the remaining space, so adding its length is safe
267 self.len += de_buf.len();
268 debug_assert!(self.len <= self.buffer.len());
269
270 Ok(())
271 }
272
273 /// Return the finished advertisement (version header || V0 header || DEs),
274 /// or `None` if the adv could not be built.
into_advertisement(mut self) -> Result<SerializedAdv, E::Error>275 pub fn into_advertisement(mut self) -> Result<SerializedAdv, E::Error> {
276 // encrypt, if applicable
277 self.encoder
278 // skip NP version header for postprocessing
279 .postprocess(self.header_len, &mut self.buffer[1..self.len])
280 .map(|_| ArrayView::try_from_array(self.buffer, self.len).expect("len is always valid"))
281 }
282 }
283
284 /// Errors that can occur for [AdvBuilder.add_data_element].
285 #[derive(Debug, PartialEq, Eq)]
286 pub enum AddDataElementError {
287 /// The provided DE can't fit in the remaining space
288 InsufficientAdvSpace,
289 }
290
291 impl From<DataElementSerializeError> for AddDataElementError {
from(value: DataElementSerializeError) -> Self292 fn from(value: DataElementSerializeError) -> Self {
293 match value {
294 DataElementSerializeError::InsufficientSpace => Self::InsufficientAdvSpace,
295 }
296 }
297 }
298
299 /// Encode a DE type and length into a DE header byte.
encode_de_header(code: DeTypeCode, header_len: DeEncodedLength) -> u8300 pub(crate) fn encode_de_header(code: DeTypeCode, header_len: DeEncodedLength) -> u8 {
301 // 4 high bits are length, 4 low bits are type
302 (header_len.as_u8() << 4) | code.as_u8()
303 }
304
305 /// Encode a DE into a buffer.
306 ///
307 /// The buffer will contain the DE header and DE contents, if any, and will
308 /// not exceed `max_de_len`.
309 ///
310 /// # Panics
311 /// `max_de_len` must be no larger than [NP_MAX_ADV_CONTENT_LEN].
serialize_de<F: PacketFlavor, D: SerializeDataElement<F>>( data_element: &D, max_de_len: usize, ) -> Result<SerializedDataElement, AddDataElementError>312 fn serialize_de<F: PacketFlavor, D: SerializeDataElement<F>>(
313 data_element: &D,
314 max_de_len: usize,
315 ) -> Result<SerializedDataElement, AddDataElementError> {
316 let mut de_buf = DataElementSerializationBuffer::new(max_de_len)
317 .expect("max_de_len must not exceed NP_MAX_DE_CONTENT_LEN");
318
319 // placeholder for header
320 de_buf.try_push(0).ok_or(AddDataElementError::InsufficientAdvSpace)?;
321 data_element.serialize_contents(&mut de_buf)?;
322
323 let encoded_len = data_element.map_actual_len_to_encoded_len(
324 DeActualLength::try_from(de_buf.len() - 1)
325 .expect("DE fit in buffer after header, so it should be a valid size"),
326 );
327
328 let mut vec = de_buf.into_inner().into_inner();
329 vec[0] = encode_de_header(data_element.de_type_code(), encoded_len);
330
331 debug_assert!(vec.len() <= max_de_len);
332
333 Ok(SerializedDataElement::new(to_array_view(vec)))
334 }
335
336 /// The serialized form of a data element, including its header.
337 pub(crate) struct SerializedDataElement {
338 data: ArrayView<u8, NP_MAX_ADV_CONTENT_LEN>,
339 }
340
341 impl SerializedDataElement {
new(data: ArrayView<u8, NP_MAX_ADV_CONTENT_LEN>) -> Self342 fn new(data: ArrayView<u8, NP_MAX_ADV_CONTENT_LEN>) -> Self {
343 Self { data }
344 }
345
346 /// The serialized DE, starting with the DE header byte
as_slice(&self) -> &[u8]347 pub(crate) fn as_slice(&self) -> &[u8] {
348 self.data.as_slice()
349 }
350
len(&self) -> usize351 pub(crate) fn len(&self) -> usize {
352 self.data.len()
353 }
354 }
355