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}