burble_crypto/
lib.rs

1//! Bluetooth LE cryptographic toolbox ([Vol 3] Part H, Section 2.2).
2
3#![forbid(unsafe_code)]
4#![warn(unused_crate_dependencies)]
5
6use std::fmt::{Debug, Display, Formatter};
7use std::mem;
8
9use structbuf::{Packer, Unpacker};
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12pub use crate::{cmac::*, p256::*};
13
14mod cmac;
15mod p256;
16
17/// Codec for packing and unpacking SMP command parameters.
18pub trait Codec: Sized {
19    /// Packs command parameters into an SMP PDU.
20    fn pack(&self, p: &mut Packer);
21
22    /// Unpacks command parameters from an SMP PDU.
23    ///
24    /// Implementations should only return [`None`] if one of the unpacked
25    /// values cannot be decoded. The final [`Unpacker`] status is checked by
26    /// the caller.
27    #[must_use]
28    fn unpack(p: &mut Unpacker) -> Option<Self>;
29}
30
31/// Implements [`Codec`] for a `u128` newtype struct.
32macro_rules! u128_codec {
33    ($T:ty) => {
34        impl $crate::Codec for $T {
35            #[inline(always)]
36            fn pack(&self, p: &mut Packer) {
37                p.u128(self.0);
38            }
39
40            #[inline(always)]
41            fn unpack(p: &mut Unpacker) -> Option<Self> {
42                Some(Self(p.u128()))
43            }
44        }
45    };
46}
47
48/// Implements [`subtle::ConstantTimeEq`] for a newtype struct.
49macro_rules! ct_newtype {
50    ($T:ty) => {
51        impl subtle::ConstantTimeEq for $T {
52            #[inline(always)]
53            fn ct_eq(&self, other: &Self) -> subtle::Choice {
54                self.0.ct_eq(&other.0)
55            }
56        }
57
58        impl PartialEq for $T {
59            #[inline(always)]
60            fn eq(&self, other: &Self) -> bool {
61                bool::from(subtle::ConstantTimeEq::ct_eq(self, other))
62            }
63        }
64    };
65}
66
67/// 56-bit device address in big-endian byte order used by [`DHKey::f5`] and
68/// [`MacKey::f6`] functions ([Vol 3] Part H, Section 2.2.7 and 2.2.8).
69#[derive(Clone, Copy, Debug)]
70#[must_use]
71#[repr(transparent)]
72pub struct Addr([u8; 7]);
73
74impl Addr {
75    /// Creates a device address from a little-endian byte array.
76    #[inline]
77    pub fn from_le_bytes(is_random: bool, mut v: [u8; 6]) -> Self {
78        v.reverse();
79        let mut a = [0; 7];
80        a[0] = u8::from(is_random);
81        a[1..].copy_from_slice(&v);
82        Self(a)
83    }
84}
85
86/// Concatenated `AuthReq`, OOB data flag, and IO capability parameters used by
87/// [`MacKey::f6`] function ([Vol 3] Part H, Section 2.2.8).
88#[derive(Clone, Copy, Debug)]
89#[must_use]
90#[repr(transparent)]
91pub struct IoCap([u8; 3]);
92
93impl IoCap {
94    /// Creates new `IoCap` parameter.
95    #[inline(always)]
96    pub fn new(auth_req: u8, oob_data: bool, io_cap: u8) -> Self {
97        Self([auth_req, u8::from(oob_data), io_cap])
98    }
99}
100
101/// 128-bit random nonce value ([Vol 3] Part H, Section 2.3.5.6).
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103#[must_use]
104#[repr(transparent)]
105pub struct Nonce(u128);
106
107u128_codec!(Nonce);
108
109impl Nonce {
110    /// Generates a new non-zero random nonce value from the OS CSPRNG.
111    ///
112    /// # Panics
113    ///
114    /// Panics if the OS CSPRNG is broken.
115    #[allow(clippy::new_without_default)]
116    #[inline]
117    pub fn new() -> Self {
118        use rand_core::{OsRng, RngCore};
119        let mut b = [0; mem::size_of::<u128>()];
120        OsRng.fill_bytes(b.as_mut_slice());
121        let n = u128::from_ne_bytes(b);
122        assert_ne!(n, 0);
123        Self(n)
124    }
125
126    /// Generates LE Secure Connections confirm value
127    /// ([Vol 3] Part H, Section 2.2.6).
128    #[inline]
129    pub fn f4(&self, u: &PublicKeyX, v: &PublicKeyX, z: u8) -> Confirm {
130        let mut m = AesCmac::new(&Key::new(self.0));
131        m.update(u.as_be_bytes())
132            .update(v.as_be_bytes())
133            .update([z]);
134        Confirm(m.finalize())
135    }
136
137    /// Generates LE Secure Connections numeric comparison value
138    /// ([Vol 3] Part H, Section 2.2.9).
139    #[inline]
140    pub fn g2(&self, pkax: &PublicKeyX, pkbx: &PublicKeyX, nb: &Self) -> NumCompare {
141        let mut m = AesCmac::new(&Key::new(self.0));
142        m.update(pkax.as_be_bytes())
143            .update(pkbx.as_be_bytes())
144            .update(nb.0.to_be_bytes());
145        #[allow(clippy::cast_possible_truncation)]
146        NumCompare(m.finalize() as u32 % 1_000_000)
147    }
148}
149
150/// LE Secure Connections confirm value generated by [`Nonce::f4`].
151#[derive(Clone, Copy, Debug, Eq)]
152#[must_use]
153#[repr(transparent)]
154pub struct Confirm(u128);
155
156u128_codec!(Confirm);
157ct_newtype!(Confirm);
158
159/// 6-digit LE Secure Connections numeric comparison value generated by
160/// [`Nonce::g2`].
161#[derive(Clone, Copy, Eq, PartialEq)]
162#[must_use]
163#[repr(transparent)]
164pub struct NumCompare(u32);
165
166impl Debug for NumCompare {
167    #[inline]
168    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169        f.debug_tuple("NumCompare")
170            .field(&format_args!("{:06}", self.0))
171            .finish()
172    }
173}
174
175impl Display for NumCompare {
176    #[inline]
177    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
178        write!(f, "{:06}", self.0)
179    }
180}
181
182/// 128-bit key used to compute LE Secure Connections check value
183/// ([Vol 3] Part H, Section 2.2.8).
184#[must_use]
185#[repr(transparent)]
186pub struct MacKey(Key);
187
188debug_secret!(MacKey);
189
190impl MacKey {
191    /// Generates LE Secure Connections check value
192    /// ([Vol 3] Part H, Section 2.2.8).
193    #[inline]
194    pub fn f6(&self, n1: Nonce, n2: Nonce, r: u128, io_cap: IoCap, a1: Addr, a2: Addr) -> Check {
195        let mut m = AesCmac::new(&self.0);
196        m.update(n1.0.to_be_bytes())
197            .update(n2.0.to_be_bytes())
198            .update(r.to_be_bytes())
199            .update(io_cap.0)
200            .update(a1.0)
201            .update(a2.0);
202        Check(m.finalize())
203    }
204}
205
206/// LE Secure Connections Long Term Key.
207#[derive(Eq, PartialEq, Zeroize, ZeroizeOnDrop, serde::Deserialize, serde::Serialize)]
208#[must_use]
209#[repr(transparent)]
210#[serde(transparent)]
211pub struct LTK(#[serde(with = "u128ser")] u128);
212
213debug_secret!(LTK);
214
215impl LTK {
216    /// Creates a Long Term Key from a `u128` value.
217    #[inline(always)]
218    pub const fn new(k: u128) -> Self {
219        Self(k)
220    }
221}
222
223impl From<&LTK> for u128 {
224    #[inline(always)]
225    fn from(k: &LTK) -> Self {
226        k.0
227    }
228}
229
230/// LE Secure Connections check value generated by [`MacKey::f6`].
231#[derive(Clone, Copy, Debug, Eq)]
232#[must_use]
233#[repr(transparent)]
234pub struct Check(u128);
235
236u128_codec!(Check);
237ct_newtype!(Check);
238
239/// Serializer/deserializer for `u128` and `NonZeroU128`.
240#[doc(hidden)]
241pub mod u128ser {
242    use std::fmt;
243
244    use serde::{de, ser};
245
246    pub fn serialize<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
247    where
248        T: Into<u128> + Copy,
249        S: ser::Serializer,
250    {
251        // TODO: Handle non-human-readable formats?
252        use std::io::Write;
253        let mut b = std::io::Cursor::new([0_u8; 32]);
254        write!(b, "{:032X}", (*v).into()).expect("buffer overflow");
255        s.serialize_str(std::str::from_utf8(b.get_ref()).expect("invalid string"))
256    }
257
258    pub fn deserialize<'de, T, D>(d: D) -> Result<T, D::Error>
259    where
260        D: de::Deserializer<'de>,
261        T: TryFrom<u128>,
262        T::Error: fmt::Display,
263    {
264        struct U128;
265        impl de::Visitor<'_> for U128 {
266            type Value = u128;
267            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
268                f.write_str("128-bit hex string")
269            }
270            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
271                u128::from_str_radix(v, 16).map_err(de::Error::custom)
272            }
273        }
274        (d.deserialize_str(U128)).and_then(|v| T::try_from(v).map_err(de::Error::custom))
275    }
276}
277
278#[allow(clippy::unusual_byte_groupings)]
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn nonce() {
285        // No fair dice rolls for us!
286        assert_ne!(Nonce::new(), Nonce::new());
287    }
288
289    /// Confirm value generation function ([Vol 3] Part H, Section D.2).
290    #[test]
291    fn nonce_f4() {
292        let u = PublicKeyX::from_be_bytes(u256(
293            0x20b003d2_f297be2c_5e2c83a7_e9f9a5b9,
294            0xeff49111_acf4fddb_cc030148_0e359de6,
295        ));
296        let v = PublicKeyX::from_be_bytes(u256(
297            0x55188b3d_32f6bb9a_900afcfb_eed4e72a,
298            0x59cb9ac2_f19d7cfb_6b4fdd49_f47fc5fd,
299        ));
300        let x = Nonce(0xd5cb8454_d177733e_ffffb2ec_712baeab);
301        assert_eq!(x.f4(&u, &v, 0).0, 0xf2c916f1_07a9bd1c_f1eda1be_a974872d);
302    }
303
304    /// Numeric comparison generation function ([Vol 3] Part H, Section D.5).
305    #[allow(clippy::unreadable_literal)]
306    #[test]
307    fn nonce_g2() {
308        let u = PublicKeyX::from_be_bytes(u256(
309            0x20b003d2_f297be2c_5e2c83a7_e9f9a5b9,
310            0xeff49111_acf4fddb_cc030148_0e359de6,
311        ));
312        let v = PublicKeyX::from_be_bytes(u256(
313            0x55188b3d_32f6bb9a_900afcfb_eed4e72a,
314            0x59cb9ac2_f19d7cfb_6b4fdd49_f47fc5fd,
315        ));
316        let x = Nonce(0xd5cb8454_d177733e_ffffb2ec_712baeab);
317        let y = Nonce(0xa6e8e7cc_25a75f6e_216583f7_ff3dc4cf);
318        assert_eq!(x.g2(&u, &v, &y), NumCompare(0x2f9ed5ba % 1_000_000));
319    }
320
321    /// Check value generation function ([Vol 3] Part H, Section D.4).
322    #[test]
323    fn mac_key_f6() {
324        let k = MacKey(Key::new(0x2965f176_a1084a02_fd3f6a20_ce636e20));
325        let n1 = Nonce(0xd5cb8454_d177733e_ffffb2ec_712baeab);
326        let n2 = Nonce(0xa6e8e7cc_25a75f6e_216583f7_ff3dc4cf);
327        let r = 0x12a3343b_b453bb54_08da42d2_0c2d0fc8;
328        let io_cap = IoCap([0x01, 0x01, 0x02]);
329        let a1 = Addr([0x00, 0x56, 0x12, 0x37, 0x37, 0xbf, 0xce]);
330        let a2 = Addr([0x00, 0xa7, 0x13, 0x70, 0x2d, 0xcf, 0xc1]);
331        let c = k.f6(n1, n2, r, io_cap, a1, a2);
332        assert_eq!(c.0, 0xe3c47398_9cd0e8c5_d26c0b09_da958f61);
333    }
334}