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