rubble/
security.rs

1//! The LE Security Manager protocol.
2//!
3//! The Security Manager is a mandatory part of BLE and is connected to L2CAP channel `0x0006` when
4//! the Link-Layer connection is established.
5//!
6//! # BLE Security
7//!
8//! As is tradition, BLE security is a complexity nightmare. This section hopes to clear up a few
9//! things and tries to define terms used throughout the code and specficiation.
10//!
11//! ## Pairing and Bonding
12//!
13//! * **Pairing** is the process of generating and exchanging connection-specific keys in order to
14//!   accomplish an encrypted Link-Layer connection.
15//!
16//!   This is done by having the *Security Managers* of the devices talk to each other to perform
17//!   the key exchange, and then using *LL Control PDUs* to enable the negotiated encryption
18//!   parameters.
19//!
20//! * **Bonding** means permanently storing the shared keys derived by *Pairing* in order to reuse
21//!   them for later connections.
22//!
23//!   The way keys are stored is inherently platform- and application-dependent, we just have to
24//!   provide interfaces to export and import key sets.
25//!
26//! Most times, when talking about *pairing*, the *bonding* part is implied. If it were not, you
27//! would constantly have to re-pair devices when reconnecting them.
28//!
29//! ## LE Legacy Pairing vs. LE Secure Connections
30//!
31//! Bluetooth's security track record is an actual record in that it is so atrociously bad that this
32//! protocol should have never seen the light of the day. Alas, here we are.
33//!
34//! LE security is generally able to utilize *AES-128-CCM* for encryption, which isn't broken by
35//! itself (unlike the "export-grade" encryption used by earlier Bluetooth versions). However, the
36//! way the AES key is exchanged differs between *LE Legacy Pairing* and *LE Secure Connections*
37//! pairing, which hugely impacts actual security.
38//!
39//! ### LE Legacy Pairing
40//!
41//! For BLE 4.0 and 4.1, only the *LE Legacy Pairing* (as it is now known as) was available. Like
42//! every awfully designed protocol, they've rolled their own crypto and use their own key exchange
43//! procedure (with the usual catastrophic consequences). First, a shared 128-bit **T**emporary
44//! **K**ey (TK) is obtained, which is then used to generate the 128-bit **S**hort-**T**erm **K**ey
45//! (STK) that is used to initially encrypt the connection while other keys are exchanged.
46//!
47//! The STK is generated from the TK by mixing in random values from master (`Mrand`) and slave
48//! (`Srand`), which are exchanged in plain text. If a passive eavesdropper manages to obtain TK,
49//! they only need to listen for the `Mrand` and `Srand` value and can then compute the STK and
50//! decrypt the connection.
51//!
52//! There are 3 methods of determining the TK:
53//! * *"Just Works"*: TK=0
54//! * *Passkey Entry*: A 6-digit number is displayed on one device and input on the other device.
55//!   The number is directly used as the TK (after zero-padding it to 128 bits).
56//! * *Out-of-Band* (OOB): The 128-bit TK is provided by an external mechanism (eg. NFC).
57//!
58//! "Just Works" obviously is broken without any effort other than listening for the exchanged
59//! `Mrand` and `Srand` values.
60//!
61//! The Passkey Entry method only allows 1000000 different TKs (equivalent to using 20-bit keys)
62//! and does not do any key derivation. This makes it trivial to brute-force the TK by running the
63//! STK derivation up to a million times.
64//!
65//! **The only way to perform *LE Legacy Pairing* with meaningful protection against passive
66//! eavesdropping is by using a secure Out-of-Band channel for agreeing on the TK.**
67//!
68//! ### LE Secure Connections pairing
69//!
70//! Added with BLE 4.2, this finally uses established cryptography to do everything. It uses ECDH on
71//! the P-256 curve (aka "secp256r1" or "prime256v1").
72//!
73//! Using ECDH immediately protects against passive eavesdropping. MITM-protection works similarly
74//! to what *LE Legacy Pairing* attempted to do, but is actually relevant here since the base key
75//! exchange isn't broken to begin with. There are several user confirmation processes that can
76//! offer MITM-protection:
77//!
78//! * *"Just Works"*: No MITM-protection. Uses the *Numeric Comparison* protocol internally, with
79//!   automatic confirmation.
80//! * *Numeric Comparison*: Both devices display a 6-digit confirmation value and the user is
81//!   required to compare them and confirm on each device if they're equal.
82//! * *Passkey Entry*: Either a generated passkey is displayed on one device and input on the other,
83//!   or the user inputs the same passkey into both devices.
84//! * *Out-of-Band* (OOB): An Out-of-Band mechanism is used to exchange random nonces and confirm
85//!   values. The mechanism has to be secure against MITM.
86//!
87//! ## LE Privacy
88//!
89//! BLE devices are normally extremely easy to track. Since many people use BLE devices, and device
90//! addresses are device-unique, they can be very easily used to identify and track people just by
91//! recording BLE advertisements.
92//!
93//! The LE privacy feature can prevent this by changing the device address over time. Bonded devices
94//! can still *resolve* this address by using a shared **I**dentity **R**esolving **K**ey (IRK).
95//!
96//! This feature is not related to encryption or authentication of connections.
97
98use crate::l2cap::{Protocol, ProtocolObj, Sender};
99use crate::{bytes::*, utils::HexSlice, Error};
100use bitflags::bitflags;
101use core::fmt;
102
103/// Supported security levels.
104pub trait SecurityLevel {
105    /// The L2CAP MTU required by this security level.
106    const MTU: u8;
107}
108
109/// *LE Secure Connections* are not supported and will not be established.
110#[derive(Debug)]
111pub struct NoSecurity;
112impl SecurityLevel for NoSecurity {
113    /// 23 Bytes when *LE Secure Connections* are unsupported
114    const MTU: u8 = 23;
115}
116
117/// Indicates support for *LE Secure Connections*.
118#[derive(Debug)]
119pub struct SecureConnections;
120impl SecurityLevel for SecureConnections {
121    /// 65 Bytes when *LE Secure Connections* are supported
122    const MTU: u8 = 65;
123}
124
125/// The LE Security Manager.
126///
127/// Manages pairing and key generation and exchange.
128#[derive(Debug)]
129pub struct SecurityManager<S: SecurityLevel> {
130    _security: S,
131}
132
133impl SecurityManager<NoSecurity> {
134    pub fn no_security() -> Self {
135        Self {
136            _security: NoSecurity,
137        }
138    }
139}
140
141impl<S: SecurityLevel> ProtocolObj for SecurityManager<S> {
142    fn process_message(&mut self, message: &[u8], _responder: Sender<'_>) -> Result<(), Error> {
143        let cmd = Command::from_bytes(&mut ByteReader::new(message))?;
144        trace!("SMP cmd {:?}, {:?}", cmd, HexSlice(message));
145        match cmd {
146            Command::PairingRequest { .. } => {
147                warn!("pairing request NYI");
148            }
149            Command::Unknown {
150                code: CommandCode::Unknown(code),
151                data,
152            } => warn!(
153                "unknown security manager cmd: 0x{:02X} {:?}",
154                code,
155                HexSlice(data)
156            ),
157            Command::Unknown { code, data } => {
158                warn!("[NYI] SMP cmd {:?}: {:?}", code, HexSlice(data));
159            }
160        }
161
162        Ok(())
163    }
164}
165
166impl<S: SecurityLevel> Protocol for SecurityManager<S> {
167    const RSP_PDU_SIZE: u8 = S::MTU;
168}
169
170/// An SMP command.
171#[derive(Debug, Copy, Clone)]
172enum Command<'a> {
173    /// `0x01` Pairing request
174    PairingRequest {
175        /// The I/O capabilities of the initiator.
176        io: IoCapabilities,
177        /// Whether the initiator has OOB pairing data available.
178        oob: bool,
179        /// Initiator authentication requirements.
180        auth_req: AuthReq,
181        /// Maximum supported encryption key size in range 7..=16 Bytes.
182        ///
183        /// For BLE, this is always 16, since it always uses AES-128-CCM (even with the broken
184        /// *LE Legacy Pairing*). We consider anything smaller than 16 to be as insecure as a plain
185        /// text connection.
186        max_keysize: u8,
187        /// Set of keys the initiator (the device sending this request) wants to distribute to the
188        /// responder (the device receiving this request).
189        initiator_dist: KeyDistribution,
190        /// Set of keys the initiator requests the responder to generate and distribute.
191        responder_dist: KeyDistribution,
192    },
193    Unknown {
194        code: CommandCode,
195        data: &'a [u8],
196    },
197}
198
199impl<'a> FromBytes<'a> for Command<'a> {
200    fn from_bytes(bytes: &mut ByteReader<'a>) -> Result<Self, Error> {
201        let code = CommandCode::from(bytes.read_u8()?);
202        Ok(match code {
203            CommandCode::PairingRequest => Command::PairingRequest {
204                io: IoCapabilities::from(bytes.read_u8()?),
205                oob: bytes.read_u8()? == 0x01,
206                auth_req: AuthReq(bytes.read_u8()?),
207                max_keysize: bytes.read_u8()?,
208                initiator_dist: KeyDistribution::from_bits_truncate(bytes.read_u8()?),
209                responder_dist: KeyDistribution::from_bits_truncate(bytes.read_u8()?),
210            },
211            _ => Command::Unknown {
212                code,
213                data: bytes.read_rest(),
214            },
215        })
216    }
217}
218
219enum_with_unknown! {
220    #[derive(Debug, Copy, Clone)]
221    enum CommandCode(u8) {
222        PairingRequest = 0x01,
223        PairingResponse = 0x02,
224        PairingConfirm = 0x03,
225        PairingRandom = 0x04,
226        PairingFailed = 0x05,
227        EncryptionInformation = 0x06,
228        MasterIdentification = 0x07,
229        IdentityInformation = 0x08,
230        IdentityAddressInformation = 0x09,
231        SigningInformation = 0x0A,
232        SecurityRequest = 0x0B,
233        PairingPublicKey = 0x0C,
234        PairingDhKeyCheck = 0x0D,
235        PairingKeypressNotification = 0x0E,
236    }
237}
238
239enum_with_unknown! {
240    /// Describes the I/O capabilities of a device that can be used for the pairing process.
241    #[derive(Debug, Copy, Clone)]
242    pub enum IoCapabilities(u8) {
243        /// Device can display a 6-digit number, but has no input capabilities.
244        DisplayOnly = 0x00,
245
246        /// Device can display a 6-digit number and the user can input "Yes" or "No".
247        DisplayYesNo = 0x01,
248
249        /// Device does not have output capability, but the user can input a passcode.
250        KeyboardOnly = 0x02,
251
252        /// Device has no meaningful input and output capabilities.
253        NoInputNoOutput = 0x03,
254
255        /// Device can display a 6-digit passcode and allows passcode entry via a keyboard.
256        KeyboardDisplay = 0x04,
257    }
258}
259
260/// Authentication requirements exchanged during pairing requests.
261#[derive(Copy, Clone)]
262pub struct AuthReq(u8);
263
264impl AuthReq {
265    const BITS_BONDING: u8 = 0b0000_0011;
266    const BITS_MITM: u8 = 0b0000_0100;
267    const BITS_SC: u8 = 0b0000_1000;
268    const BITS_KEYPRESS: u8 = 0b0001_0000;
269
270    /// Returns the requested bonding.
271    pub fn bonding_type(&self) -> BondingType {
272        BondingType::from(self.0 & Self::BITS_BONDING)
273    }
274
275    pub fn set_bonding_type(&mut self, ty: BondingType) {
276        self.0 = (self.0 & !Self::BITS_BONDING) | u8::from(ty);
277    }
278
279    /// Returns whether MITM protection is requested.
280    pub fn mitm(&self) -> bool {
281        self.0 & Self::BITS_MITM != 0
282    }
283
284    pub fn set_mitm(&mut self, mitm: bool) {
285        self.0 = (self.0 & !Self::BITS_MITM) | if mitm { Self::BITS_MITM } else { 0 };
286    }
287
288    /// Returns whether *LE Secure Connection* pairing is supported and requested.
289    ///
290    /// If this returns `false`, *LE Legacy Pairing* will be used. Note that Rubble does not support
291    /// *LE Legacy Pairing* at the moment since it has serious security problems (refer to the
292    /// module docs for more info).
293    pub fn secure_connection(&self) -> bool {
294        self.0 & Self::BITS_SC != 0
295    }
296
297    /// Sets whether *LE Secure Connection* pairing is supported and requested.
298    pub fn set_secure_connection(&mut self, sc: bool) {
299        self.0 = (self.0 & !Self::BITS_SC) | if sc { Self::BITS_SC } else { 0 };
300    }
301
302    pub fn keypress(&self) -> bool {
303        self.0 & Self::BITS_KEYPRESS != 0
304    }
305
306    pub fn set_keypress(&mut self, keypress: bool) {
307        self.0 = (self.0 & !Self::BITS_KEYPRESS) | if keypress { Self::BITS_KEYPRESS } else { 0 };
308    }
309}
310
311impl fmt::Debug for AuthReq {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        f.debug_struct("AuthReq")
314            .field("bonding_type", &self.bonding_type())
315            .field("mitm", &self.mitm())
316            .field("secure_connection", &self.secure_connection())
317            .field("keypress", &self.keypress())
318            .finish()
319    }
320}
321
322enum_with_unknown! {
323    /// Whether to perform bonding in addition to pairing.
324    ///
325    /// If `Bonding` is selected, the exchanged keys are permanently stored on both devices. This
326    /// is usually what you want.
327    #[derive(Debug, Copy, Clone)]
328    pub enum BondingType(u8) {
329        /// No bonding should be performed; the exchanged keys should not be permanently stored.
330        ///
331        /// This is usually not what you want since it requires the user to perform pairing every
332        /// time the devices connect again.
333        NoBonding = 0b00,
334
335        /// Permanently store the exchanged keys to allow resuming encryption on future connections.
336        Bonding = 0b01,
337    }
338}
339
340bitflags! {
341    /// Indicates which types of keys a device requests for distribution.
342    pub struct KeyDistribution: u8 {
343        const ENC_KEY = (1 << 0);
344        const ID_KEY = (1 << 1);
345        const SIGN_KEY = (1 << 2);
346        const LINK_KEY = (1 << 3);
347    }
348}