1 /******************************************************************************
2 *
3 * Copyright 2019 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at:
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 ******************************************************************************/
18
19 #include "security/pairing_handler_le.h"
20
21 #include "os/rand.h"
22
23 using bluetooth::os::GenerateRandom;
24
25 namespace bluetooth {
26 namespace security {
27
ExchangePublicKeys(const InitialInformations & i,OobDataFlag remote_have_oob_data)28 std::variant<PairingFailure, KeyExchangeResult> PairingHandlerLe::ExchangePublicKeys(const InitialInformations& i,
29 OobDataFlag remote_have_oob_data) {
30 // Generate ECDH, or use one that was used for OOB data
31 const auto [private_key, public_key] = (remote_have_oob_data == OobDataFlag::NOT_PRESENT || !i.my_oob_data)
32 ? GenerateECDHKeyPair()
33 : std::make_pair(i.my_oob_data->private_key, i.my_oob_data->public_key);
34
35 LOG_INFO("Public key exchange start");
36 std::unique_ptr<PairingPublicKeyBuilder> myPublicKey = PairingPublicKeyBuilder::Create(public_key.x, public_key.y);
37
38 if (!ValidateECDHPoint(public_key)) {
39 LOG_ERROR("Can't validate my own public key!!!");
40 return PairingFailure("Can't validate my own public key");
41 }
42
43 if (IAmCentral(i)) {
44 // Send pairing public key
45 LOG_INFO("Central sends out public key");
46 SendL2capPacket(i, std::move(myPublicKey));
47 }
48
49 LOG_INFO(" Waiting for Public key...");
50 auto response = WaitPairingPublicKey();
51 LOG_INFO(" Received public key");
52 if (std::holds_alternative<PairingFailure>(response)) {
53 return std::get<PairingFailure>(response);
54 }
55
56 EcdhPublicKey remote_public_key;
57 auto ppkv = std::get<PairingPublicKeyView>(response);
58 remote_public_key.x = ppkv.GetPublicKeyX();
59 remote_public_key.y = ppkv.GetPublicKeyY();
60 LOG_INFO("Received Public key from remote");
61
62 if (public_key.x == remote_public_key.x) {
63 LOG_INFO("Remote and local public keys can't match");
64 return PairingFailure("Remote and local public keys match");
65 }
66
67 // validate received public key
68 if (!ValidateECDHPoint(remote_public_key)) {
69 // TODO: Spec is unclear what should happend when the point is not on
70 // the correct curve: A device that detects an invalid public key from
71 // the peer at any point during the LE Secure Connections pairing
72 // process shall not use the resulting LTK, if any.
73 LOG_INFO("Can't validate remote public key");
74 return PairingFailure("Can't validate remote public key");
75 }
76
77 if (!IAmCentral(i)) {
78 LOG_INFO("Peripheral sends out public key");
79 // Send pairing public key
80 SendL2capPacket(i, std::move(myPublicKey));
81 }
82
83 LOG_INFO("Public key exchange finish");
84
85 std::array<uint8_t, 32> dhkey = ComputeDHKey(private_key, remote_public_key);
86
87 const EcdhPublicKey& PKa = IAmCentral(i) ? public_key : remote_public_key;
88 const EcdhPublicKey& PKb = IAmCentral(i) ? remote_public_key : public_key;
89
90 return KeyExchangeResult{PKa, PKb, dhkey};
91 }
92
DoSecureConnectionsStage1(const InitialInformations & i,const EcdhPublicKey & PKa,const EcdhPublicKey & PKb,const PairingRequestView & pairing_request,const PairingResponseView & pairing_response)93 Stage1ResultOrFailure PairingHandlerLe::DoSecureConnectionsStage1(const InitialInformations& i,
94 const EcdhPublicKey& PKa, const EcdhPublicKey& PKb,
95 const PairingRequestView& pairing_request,
96 const PairingResponseView& pairing_response) {
97 if (((pairing_request.GetAuthReq() & AuthReqMaskMitm) == 0) &&
98 ((pairing_response.GetAuthReq() & AuthReqMaskMitm) == 0)) {
99 // If both devices have not set MITM option, Just Works shall be used
100 return SecureConnectionsJustWorks(i, PKa, PKb);
101 }
102
103 if (pairing_request.GetOobDataFlag() == OobDataFlag::PRESENT ||
104 pairing_response.GetOobDataFlag() == OobDataFlag::PRESENT) {
105 OobDataFlag remote_oob_flag = IAmCentral(i) ? pairing_response.GetOobDataFlag() : pairing_request.GetOobDataFlag();
106 OobDataFlag my_oob_flag = IAmCentral(i) ? pairing_request.GetOobDataFlag() : pairing_response.GetOobDataFlag();
107 return SecureConnectionsOutOfBand(i, PKa, PKb, my_oob_flag, remote_oob_flag);
108 }
109
110 const auto& iom = pairing_request.GetIoCapability();
111 const auto& ios = pairing_response.GetIoCapability();
112
113 if ((iom == IoCapability::KEYBOARD_DISPLAY || iom == IoCapability::DISPLAY_YES_NO) &&
114 (ios == IoCapability::KEYBOARD_DISPLAY || ios == IoCapability::DISPLAY_YES_NO)) {
115 return SecureConnectionsNumericComparison(i, PKa, PKb);
116 }
117
118 if (iom == IoCapability::NO_INPUT_NO_OUTPUT || ios == IoCapability::NO_INPUT_NO_OUTPUT) {
119 return SecureConnectionsJustWorks(i, PKa, PKb);
120 }
121
122 if ((iom == IoCapability::DISPLAY_ONLY || iom == IoCapability::DISPLAY_YES_NO) &&
123 (ios == IoCapability::DISPLAY_ONLY || ios == IoCapability::DISPLAY_YES_NO)) {
124 return SecureConnectionsJustWorks(i, PKa, PKb);
125 }
126
127 IoCapability my_iocaps = IAmCentral(i) ? iom : ios;
128 IoCapability remote_iocaps = IAmCentral(i) ? ios : iom;
129 return SecureConnectionsPasskeyEntry(i, PKa, PKb, my_iocaps, remote_iocaps);
130 }
131
DoSecureConnectionsStage2(const InitialInformations & i,const EcdhPublicKey & PKa,const EcdhPublicKey & PKb,const PairingRequestView & pairing_request,const PairingResponseView & pairing_response,const Stage1Result stage1result,const std::array<uint8_t,32> & dhkey)132 Stage2ResultOrFailure PairingHandlerLe::DoSecureConnectionsStage2(const InitialInformations& i,
133 const EcdhPublicKey& PKa, const EcdhPublicKey& PKb,
134 const PairingRequestView& pairing_request,
135 const PairingResponseView& pairing_response,
136 const Stage1Result stage1result,
137 const std::array<uint8_t, 32>& dhkey) {
138 LOG_INFO("Authentication stage 2 started");
139
140 auto [Na, Nb, ra, rb] = stage1result;
141
142 // 2.3.5.6.5 Authentication stage 2 long term key calculation
143 uint8_t a[7];
144 uint8_t b[7];
145
146 if (IAmCentral(i)) {
147 memcpy(a, i.my_connection_address.GetAddress().data(), hci::Address::kLength);
148 a[6] = (uint8_t)i.my_connection_address.GetAddressType();
149 memcpy(b, i.remote_connection_address.GetAddress().data(), hci::Address::kLength);
150 b[6] = (uint8_t)i.remote_connection_address.GetAddressType();
151 } else {
152 memcpy(a, i.remote_connection_address.GetAddress().data(), hci::Address::kLength);
153 a[6] = (uint8_t)i.remote_connection_address.GetAddressType();
154 memcpy(b, i.my_connection_address.GetAddress().data(), hci::Address::kLength);
155 b[6] = (uint8_t)i.my_connection_address.GetAddressType();
156 }
157
158 Octet16 ltk, mac_key;
159 crypto_toolbox::f5((uint8_t*)dhkey.data(), Na, Nb, a, b, &mac_key, <k);
160
161 // DHKey exchange and check
162
163 std::array<uint8_t, 3> iocapA{static_cast<uint8_t>(pairing_request.GetIoCapability()),
164 static_cast<uint8_t>(pairing_request.GetOobDataFlag()), pairing_request.GetAuthReq()};
165 std::array<uint8_t, 3> iocapB{static_cast<uint8_t>(pairing_response.GetIoCapability()),
166 static_cast<uint8_t>(pairing_response.GetOobDataFlag()), pairing_response.GetAuthReq()};
167
168 // LOG(INFO) << +(IAmCentral(i)) << " LTK = " << base::HexEncode(ltk.data(), ltk.size());
169 // LOG(INFO) << +(IAmCentral(i)) << " MAC_KEY = " << base::HexEncode(mac_key.data(), mac_key.size());
170 // LOG(INFO) << +(IAmCentral(i)) << " Na = " << base::HexEncode(Na.data(), Na.size());
171 // LOG(INFO) << +(IAmCentral(i)) << " Nb = " << base::HexEncode(Nb.data(), Nb.size());
172 // LOG(INFO) << +(IAmCentral(i)) << " ra = " << base::HexEncode(ra.data(), ra.size());
173 // LOG(INFO) << +(IAmCentral(i)) << " rb = " << base::HexEncode(rb.data(), rb.size());
174 // LOG(INFO) << +(IAmCentral(i)) << " iocapA = " << base::HexEncode(iocapA.data(), iocapA.size());
175 // LOG(INFO) << +(IAmCentral(i)) << " iocapB = " << base::HexEncode(iocapB.data(), iocapB.size());
176 // LOG(INFO) << +(IAmCentral(i)) << " a = " << base::HexEncode(a, 7);
177 // LOG(INFO) << +(IAmCentral(i)) << " b = " << base::HexEncode(b, 7);
178
179 Octet16 Ea = crypto_toolbox::f6(mac_key, Na, Nb, rb, iocapA.data(), a, b);
180
181 Octet16 Eb = crypto_toolbox::f6(mac_key, Nb, Na, ra, iocapB.data(), b, a);
182
183 if (IAmCentral(i)) {
184 // send Pairing DHKey Check
185 SendL2capPacket(i, PairingDhKeyCheckBuilder::Create(Ea));
186
187 auto response = WaitPairingDHKeyCheck();
188 if (std::holds_alternative<PairingFailure>(response)) {
189 return std::get<PairingFailure>(response);
190 }
191
192 if (std::get<PairingDhKeyCheckView>(response).GetDhKeyCheck() != Eb) {
193 LOG_INFO("Ea != Eb, aborting!");
194 SendL2capPacket(i, PairingFailedBuilder::Create(PairingFailedReason::DHKEY_CHECK_FAILED));
195 return PairingFailure("Ea != Eb");
196 }
197 } else {
198 auto response = WaitPairingDHKeyCheck();
199 if (std::holds_alternative<PairingFailure>(response)) {
200 return std::get<PairingFailure>(response);
201 }
202
203 if (std::get<PairingDhKeyCheckView>(response).GetDhKeyCheck() != Ea) {
204 LOG_INFO("Ea != Eb, aborting!");
205 SendL2capPacket(i, PairingFailedBuilder::Create(PairingFailedReason::DHKEY_CHECK_FAILED));
206 return PairingFailure("Ea != Eb");
207 }
208
209 // send Pairing DHKey Check
210 SendL2capPacket(i, PairingDhKeyCheckBuilder::Create(Eb));
211 }
212
213 LOG_INFO("Authentication stage 2 (DHKey checks) finished");
214 return ltk;
215 }
216
SecureConnectionsOutOfBand(const InitialInformations & i,const EcdhPublicKey & Pka,const EcdhPublicKey & Pkb,OobDataFlag my_oob_flag,OobDataFlag remote_oob_flag)217 Stage1ResultOrFailure PairingHandlerLe::SecureConnectionsOutOfBand(const InitialInformations& i,
218 const EcdhPublicKey& Pka, const EcdhPublicKey& Pkb,
219 OobDataFlag my_oob_flag,
220 OobDataFlag remote_oob_flag) {
221 LOG_INFO("Out Of Band start");
222
223 Octet16 zeros{0};
224 Octet16 localR = (remote_oob_flag == OobDataFlag::PRESENT && i.my_oob_data) ? i.my_oob_data->r : zeros;
225 Octet16 remoteR;
226
227 if (my_oob_flag == OobDataFlag::NOT_PRESENT || (my_oob_flag == OobDataFlag::PRESENT && !i.remote_oob_data)) {
228 /* we have send the OOB data, but not received them. remote will check if
229 * C value is correct */
230 remoteR = zeros;
231 } else {
232 remoteR = i.remote_oob_data->le_sc_r;
233 Octet16 remoteC = i.remote_oob_data->le_sc_c;
234
235 Octet16 remoteC2;
236 if (IAmCentral(i)) {
237 remoteC2 = crypto_toolbox::f4((uint8_t*)Pkb.x.data(), (uint8_t*)Pkb.x.data(), remoteR, 0);
238 } else {
239 remoteC2 = crypto_toolbox::f4((uint8_t*)Pka.x.data(), (uint8_t*)Pka.x.data(), remoteR, 0);
240 }
241
242 if (remoteC2 != remoteC) {
243 LOG_ERROR("C_computed != C_from_remote, aborting!");
244 return PairingFailure("C_computed != C_from_remote, aborting");
245 }
246 }
247
248 Octet16 Na, Nb, ra, rb;
249 if (IAmCentral(i)) {
250 ra = localR;
251 rb = remoteR;
252 Na = GenerateRandom<16>();
253 // Send Pairing Random
254 SendL2capPacket(i, PairingRandomBuilder::Create(Na));
255
256 LOG_INFO("Central waits for Nb");
257 auto random = WaitPairingRandom();
258 if (std::holds_alternative<PairingFailure>(random)) {
259 return std::get<PairingFailure>(random);
260 }
261 Nb = std::get<PairingRandomView>(random).GetRandomValue();
262 } else {
263 ra = remoteR;
264 rb = localR;
265 Nb = GenerateRandom<16>();
266
267 LOG_INFO("Peripheral waits for random");
268 auto random = WaitPairingRandom();
269 if (std::holds_alternative<PairingFailure>(random)) {
270 return std::get<PairingFailure>(random);
271 }
272 Na = std::get<PairingRandomView>(random).GetRandomValue();
273
274 SendL2capPacket(i, PairingRandomBuilder::Create(Nb));
275 }
276
277 return Stage1Result{Na, Nb, ra, rb};
278 }
279
SecureConnectionsPasskeyEntry(const InitialInformations & i,const EcdhPublicKey & PKa,const EcdhPublicKey & PKb,IoCapability my_iocaps,IoCapability remote_iocaps)280 Stage1ResultOrFailure PairingHandlerLe::SecureConnectionsPasskeyEntry(const InitialInformations& i,
281 const EcdhPublicKey& PKa,
282 const EcdhPublicKey& PKb, IoCapability my_iocaps,
283 IoCapability remote_iocaps) {
284 LOG_INFO("Passkey Entry start");
285 Octet16 Na, Nb, ra{0}, rb{0};
286
287 uint32_t passkey;
288
289 if (my_iocaps == IoCapability::DISPLAY_ONLY || remote_iocaps == IoCapability::KEYBOARD_ONLY) {
290 // I display
291 passkey = GenerateRandom();
292 passkey &= 0x0fffff; /* maximum 20 significant bytes */
293 constexpr uint32_t PASSKEY_MAX = 999999;
294 while (passkey > PASSKEY_MAX) passkey >>= 1;
295
296 ConfirmationData data(i.remote_connection_address, i.remote_name, passkey);
297 i.user_interface_handler->Post(common::BindOnce(&UI::DisplayPasskey, common::Unretained(i.user_interface), data));
298
299 } else if (my_iocaps == IoCapability::KEYBOARD_ONLY || remote_iocaps == IoCapability::DISPLAY_ONLY) {
300 ConfirmationData data(i.remote_connection_address, i.remote_name);
301 i.user_interface_handler->Post(
302 common::BindOnce(&UI::DisplayEnterPasskeyDialog, common::Unretained(i.user_interface), data));
303 std::optional<PairingEvent> response = WaitUiPasskey();
304 if (!response) return PairingFailure("Passkey did not arrive!");
305
306 passkey = response->ui_value;
307
308 /*TODO: shall we send "Keypress Notification" after each key ? This would
309 * have impact on the SMP timeout*/
310
311 } else {
312 LOG(FATAL) << "THIS SHOULD NEVER HAPPEN";
313 return PairingFailure("FATAL!");
314 }
315
316 uint32_t bitmask = 0x01;
317 for (int loop = 0; loop < 20; loop++, bitmask <<= 1) {
318 LOG_INFO("Iteration no %d", loop);
319 bool bit_set = ((bitmask & passkey) != 0);
320 uint8_t ri = bit_set ? 0x81 : 0x80;
321
322 Octet16 Cai, Cbi, Nai, Nbi;
323 if (IAmCentral(i)) {
324 Nai = GenerateRandom<16>();
325
326 Cai = crypto_toolbox::f4((uint8_t*)PKa.x.data(), (uint8_t*)PKb.x.data(), Nai, ri);
327
328 // Send Pairing Confirm
329 LOG_INFO("Central sends Cai");
330 SendL2capPacket(i, PairingConfirmBuilder::Create(Cai));
331
332 LOG_INFO("Central waits for the Cbi");
333 auto confirm = WaitPairingConfirm();
334 if (std::holds_alternative<PairingFailure>(confirm)) {
335 return std::get<PairingFailure>(confirm);
336 }
337 Cbi = std::get<PairingConfirmView>(confirm).GetConfirmValue();
338
339 // Send Pairing Random
340 SendL2capPacket(i, PairingRandomBuilder::Create(Nai));
341
342 LOG_INFO("Central waits for Nbi");
343 auto random = WaitPairingRandom();
344 if (std::holds_alternative<PairingFailure>(random)) {
345 return std::get<PairingFailure>(random);
346 }
347 Nbi = std::get<PairingRandomView>(random).GetRandomValue();
348
349 Octet16 Cbi2 = crypto_toolbox::f4((uint8_t*)PKb.x.data(), (uint8_t*)PKa.x.data(), Nbi, ri);
350 if (Cbi != Cbi2) {
351 LOG_INFO("Cai != Cbi, aborting!");
352 SendL2capPacket(i, PairingFailedBuilder::Create(PairingFailedReason::CONFIRM_VALUE_FAILED));
353 return PairingFailure("Cai != Cbi");
354 }
355 } else {
356 Nbi = GenerateRandom<16>();
357 // Compute confirm
358 Cbi = crypto_toolbox::f4((uint8_t*)PKb.x.data(), (uint8_t*)PKa.x.data(), Nbi, ri);
359
360 LOG_INFO("Peripheral waits for the Cai");
361 auto confirm = WaitPairingConfirm();
362 if (std::holds_alternative<PairingFailure>(confirm)) {
363 return std::get<PairingFailure>(confirm);
364 }
365 Cai = std::get<PairingConfirmView>(confirm).GetConfirmValue();
366
367 // Send Pairing Confirm
368 LOG_INFO("Peripheral sends confirmation");
369 SendL2capPacket(i, PairingConfirmBuilder::Create(Cbi));
370
371 LOG_INFO("Peripheral waits for random");
372 auto random = WaitPairingRandom();
373 if (std::holds_alternative<PairingFailure>(random)) {
374 return std::get<PairingFailure>(random);
375 }
376 Nai = std::get<PairingRandomView>(random).GetRandomValue();
377
378 Octet16 Cai2 = crypto_toolbox::f4((uint8_t*)PKa.x.data(), (uint8_t*)PKb.x.data(), Nai, ri);
379 if (Cai != Cai2) {
380 LOG_INFO("Cai != Cai2, aborting!");
381 SendL2capPacket(i, PairingFailedBuilder::Create(PairingFailedReason::CONFIRM_VALUE_FAILED));
382 return PairingFailure("Cai != Cai2");
383 }
384
385 // Send Pairing Random
386 SendL2capPacket(i, PairingRandomBuilder::Create(Nbi));
387 }
388
389 if (loop == 19) {
390 Na = Nai;
391 Nb = Nbi;
392 }
393 }
394
395 ra[0] = (uint8_t)(passkey);
396 ra[1] = (uint8_t)(passkey >> 8);
397 ra[2] = (uint8_t)(passkey >> 16);
398 ra[3] = (uint8_t)(passkey >> 24);
399 rb = ra;
400
401 return Stage1Result{Na, Nb, ra, rb};
402 }
403
SecureConnectionsNumericComparison(const InitialInformations & i,const EcdhPublicKey & PKa,const EcdhPublicKey & PKb)404 Stage1ResultOrFailure PairingHandlerLe::SecureConnectionsNumericComparison(const InitialInformations& i,
405 const EcdhPublicKey& PKa,
406 const EcdhPublicKey& PKb) {
407 LOG_INFO("Numeric Comparison start");
408 Stage1ResultOrFailure result = SecureConnectionsJustWorks(i, PKa, PKb);
409 if (std::holds_alternative<PairingFailure>(result)) {
410 return std::get<PairingFailure>(result);
411 }
412
413 const auto [Na, Nb, ra, rb] = std::get<Stage1Result>(result);
414
415 uint32_t number_to_display = crypto_toolbox::g2((uint8_t*)PKa.x.data(), (uint8_t*)PKb.x.data(), Na, Nb);
416
417 ConfirmationData data(i.remote_connection_address, i.remote_name, number_to_display);
418 i.user_interface_handler->Post(
419 common::BindOnce(&UI::DisplayConfirmValue, common::Unretained(i.user_interface), data));
420
421 std::optional<PairingEvent> confirmyesno = WaitUiConfirmYesNo();
422 if (!confirmyesno || confirmyesno->ui_value == 0) {
423 LOG_INFO("Was expecting the user value confirm");
424 return PairingFailure("Was expecting the user value confirm");
425 }
426
427 return result;
428 }
429
SecureConnectionsJustWorks(const InitialInformations & i,const EcdhPublicKey & PKa,const EcdhPublicKey & PKb)430 Stage1ResultOrFailure PairingHandlerLe::SecureConnectionsJustWorks(const InitialInformations& i,
431 const EcdhPublicKey& PKa, const EcdhPublicKey& PKb) {
432 Octet16 Cb, Na, Nb, ra, rb;
433
434 ra = rb = {0};
435
436 if (IAmCentral(i)) {
437 Na = GenerateRandom<16>();
438 LOG_INFO("Central waits for confirmation");
439 auto confirm = WaitPairingConfirm();
440 if (std::holds_alternative<PairingFailure>(confirm)) {
441 return std::get<PairingFailure>(confirm);
442 }
443 Cb = std::get<PairingConfirmView>(confirm).GetConfirmValue();
444
445 // Send Pairing Random
446 SendL2capPacket(i, PairingRandomBuilder::Create(Na));
447
448 LOG_INFO("Central waits for Random");
449 auto random = WaitPairingRandom();
450 if (std::holds_alternative<PairingFailure>(random)) {
451 return std::get<PairingFailure>(random);
452 }
453 Nb = std::get<PairingRandomView>(random).GetRandomValue();
454
455 // Compute Cb locally
456 Octet16 Cb_local = crypto_toolbox::f4((uint8_t*)PKb.x.data(), (uint8_t*)PKa.x.data(), Nb, 0);
457
458 if (Cb_local != Cb) {
459 LOG_INFO("Cb_local != Cb, aborting!");
460 SendL2capPacket(i, PairingFailedBuilder::Create(PairingFailedReason::CONFIRM_VALUE_FAILED));
461 return PairingFailure("Cb_local != Cb");
462 }
463 } else {
464 Nb = GenerateRandom<16>();
465 // Compute confirm
466 Cb = crypto_toolbox::f4((uint8_t*)PKb.x.data(), (uint8_t*)PKa.x.data(), Nb, 0);
467
468 // Send Pairing Confirm
469 LOG_INFO("Peripheral sends confirmation");
470 SendL2capPacket(i, PairingConfirmBuilder::Create(Cb));
471
472 LOG_INFO("Peripheral waits for random");
473 auto random = WaitPairingRandom();
474 if (std::holds_alternative<PairingFailure>(random)) {
475 return std::get<PairingFailure>(random);
476 }
477 Na = std::get<PairingRandomView>(random).GetRandomValue();
478
479 // Send Pairing Random
480 SendL2capPacket(i, PairingRandomBuilder::Create(Nb));
481 }
482
483 return Stage1Result{Na, Nb, ra, rb};
484 }
485
486 } // namespace security
487 } // namespace bluetooth
488