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