1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "device/fido/cable/fido_cable_handshake_handler.h"
6
7 #include <array>
8 #include <limits>
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/optional.h"
15 #include "base/test/task_environment.h"
16 #include "base/threading/sequenced_task_runner_handle.h"
17 #include "components/cbor/reader.h"
18 #include "components/cbor/values.h"
19 #include "components/cbor/writer.h"
20 #include "crypto/hkdf.h"
21 #include "crypto/hmac.h"
22 #include "device/bluetooth/test/bluetooth_test.h"
23 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
24 #include "device/fido/ble/fido_ble_frames.h"
25 #include "device/fido/ble/mock_fido_ble_connection.h"
26 #include "device/fido/cable/fido_cable_device.h"
27 #include "device/fido/fido_constants.h"
28 #include "device/fido/fido_parsing_utils.h"
29 #include "device/fido/test_callback_receiver.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32
33 namespace device {
34
35 namespace {
36
37 using ::testing::_;
38 using ::testing::Invoke;
39 using ::testing::Test;
40 using TestDeviceCallbackReceiver =
41 test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>;
42 using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>;
43
44 // Sufficiently large test control point length as we are not interested
45 // in testing fragmentations of BLE messages. All Cable messages are encrypted
46 // and decrypted per request frame, not fragment.
47 constexpr auto kControlPointLength = std::numeric_limits<uint16_t>::max();
48
49 constexpr std::array<uint8_t, 16> kAuthenticatorSessionRandom = {{
50 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
51 0x01, 0x02, 0x03, 0x04,
52 }};
53
54 constexpr std::array<uint8_t, 32> kTestSessionPreKey = {{
55 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
56 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
57 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
58 }};
59
60 constexpr std::array<uint8_t, 32> kIncorrectSessionPreKey = {{
61 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
62 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
63 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
64 }};
65
66 constexpr std::array<uint8_t, 8> kTestNonce = {{
67 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08,
68 }};
69
70 constexpr std::array<uint8_t, 8> kIncorrectNonce = {{
71 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
72 }};
73
74 constexpr std::array<uint8_t, 50> kValidAuthenticatorHello = {{
75 // Map(2)
76 0xA2,
77 // Key(0)
78 0x00,
79 // Text(28)
80 0x78, 0x1C,
81 // "caBLE v1 authenticator hello"
82 0x63, 0x61, 0x42, 0x4C, 0x45, 0x20, 0x76, 0x31, 0x20, 0x61, 0x75, 0x74,
83 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x68,
84 0x65, 0x6C, 0x6C, 0x6F,
85 // Key(1)
86 0x01,
87 // Bytes(16)
88 0x50,
89 // Authenticator random session
90 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
91 0x01, 0x02, 0x03, 0x04,
92 }};
93
94 constexpr std::array<uint8_t, 43> kInvalidAuthenticatorHello = {{
95 // Map(2)
96 0xA2,
97 // Key(0)
98 0x00,
99 // Text(21)
100 0x75,
101 // "caBLE INVALID MESSAGE"
102 0x63, 0x61, 0x42, 0x4C, 0x45, 0x20, 0x49, 0x4E, 0x56, 0x41, 0x4C, 0x49,
103 0x44, 0x20, 0x4D, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45,
104 // Key(1)
105 0x01,
106 // Bytes(16)
107 0x50,
108 // Authenticator random session
109 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
110 0x01, 0x02, 0x03, 0x04,
111 }};
112
113 constexpr char kIncorrectHandshakeKey[] = "INCORRECT_HANDSHAKE_KEY_12345678";
114
115 // Returns the expected encryption key that should be constructed given that
116 // the client random nonce is |client_random_nonce| and other determining
117 // factors (i.e. authenticator session random, session pre key, and nonce) are
118 // |kAuthenticatorSessionRandom|, |kTestSessionPreKey|, and |kTestNonce|,
119 // respectively.
GetExpectedEncryptionKey(base::span<const uint8_t> client_random_nonce)120 std::vector<uint8_t> GetExpectedEncryptionKey(
121 base::span<const uint8_t> client_random_nonce) {
122 std::vector<uint8_t> nonce_message =
123 fido_parsing_utils::Materialize(kTestNonce);
124 fido_parsing_utils::Append(&nonce_message, client_random_nonce);
125 fido_parsing_utils::Append(&nonce_message, kAuthenticatorSessionRandom);
126 return crypto::HkdfSha256(kTestSessionPreKey,
127 crypto::SHA256Hash(nonce_message),
128 kCableDeviceEncryptionKeyInfo, 32);
129 }
130
131 // Given a hello message and handshake key from the authenticator, construct
132 // a handshake message by concatenating hello message and its mac message
133 // derived from |handshake_key|.
ConstructAuthenticatorHelloReply(base::span<const uint8_t> hello_msg,base::StringPiece handshake_key)134 std::vector<uint8_t> ConstructAuthenticatorHelloReply(
135 base::span<const uint8_t> hello_msg,
136 base::StringPiece handshake_key) {
137 auto reply = fido_parsing_utils::Materialize(hello_msg);
138 crypto::HMAC hmac(crypto::HMAC::SHA256);
139 if (!hmac.Init(handshake_key))
140 return std::vector<uint8_t>();
141
142 std::array<uint8_t, 32> authenticator_hello_mac;
143 if (!hmac.Sign(fido_parsing_utils::ConvertToStringPiece(hello_msg),
144 authenticator_hello_mac.data(),
145 authenticator_hello_mac.size())) {
146 return std::vector<uint8_t>();
147 }
148
149 fido_parsing_utils::Append(
150 &reply, base::make_span(authenticator_hello_mac).first(16));
151 return reply;
152 }
153
154 // Constructs incoming handshake message from the authenticator into a BLE
155 // control fragment.
ConstructSerializedOutgoingFragment(base::span<const uint8_t> data)156 std::vector<uint8_t> ConstructSerializedOutgoingFragment(
157 base::span<const uint8_t> data) {
158 if (data.empty())
159 return std::vector<uint8_t>();
160
161 FidoBleFrame response_frame(FidoBleDeviceCommand::kControl,
162 fido_parsing_utils::Materialize(data));
163 const auto response_fragment =
164 std::get<0>(response_frame.ToFragments(kControlPointLength));
165
166 std::vector<uint8_t> outgoing_message;
167 response_fragment.Serialize(&outgoing_message);
168 return outgoing_message;
169 }
170
171 // Authenticator abstraction that handles logic related to validating handshake
172 // messages from the client and sending rely handshake message back to the
173 // client. Session key and nonce are assumed to be |kTestSessionPreKey| and
174 // |kTestNonce| respectively.
175 class FakeCableAuthenticator {
176 public:
FakeCableAuthenticator()177 FakeCableAuthenticator() {
178 handshake_key_ = crypto::HkdfSha256(
179 fido_parsing_utils::ConvertToStringPiece(kTestSessionPreKey),
180 fido_parsing_utils::ConvertToStringPiece(kTestNonce),
181 kCableHandshakeKeyInfo, 32);
182 }
183
184 // Receives handshake message from the client, check its validity and if the
185 // handshake message is valid, store |client_session_random| embedded in the
186 // handshake message.
ConfirmClientHandshakeMessage(base::span<const uint8_t> handshake_message)187 bool ConfirmClientHandshakeMessage(
188 base::span<const uint8_t> handshake_message) {
189 if (handshake_message.size() <= 16)
190 return false;
191
192 crypto::HMAC hmac(crypto::HMAC::SHA256);
193 if (!hmac.Init(handshake_key_))
194 return false;
195
196 // Handshake message from client should be concatenation of client hello
197 // message (42 bytes) with message authentication code (16 bytes).
198 if (handshake_message.size() != 58)
199 return false;
200
201 const auto client_hello = handshake_message.first(42);
202 if (!hmac.VerifyTruncated(
203 fido_parsing_utils::ConvertToStringPiece(client_hello),
204 fido_parsing_utils::ConvertToStringPiece(
205 handshake_message.subspan(42)))) {
206 return false;
207 }
208
209 const auto& client_hello_cbor = cbor::Reader::Read(client_hello);
210 if (!client_hello_cbor)
211 return false;
212
213 const auto& message_map = client_hello_cbor->GetMap();
214 auto hello_message_it = message_map.find(cbor::Value(0));
215 auto client_random_nonce_it = message_map.find(cbor::Value(1));
216 if (hello_message_it == message_map.end() ||
217 client_random_nonce_it == message_map.end())
218 return false;
219
220 if (!hello_message_it->second.is_string() ||
221 hello_message_it->second.GetString() != kCableClientHelloMessage) {
222 return false;
223 }
224
225 if (!client_random_nonce_it->second.is_bytestring() ||
226 client_random_nonce_it->second.GetBytestring().size() != 16) {
227 return false;
228 }
229
230 client_session_random_ =
231 std::move(client_random_nonce_it->second.GetBytestring());
232 return true;
233 }
234
RelyWithAuthenticatorHandShakeMessage(base::span<const uint8_t> handshake_message)235 std::vector<uint8_t> RelyWithAuthenticatorHandShakeMessage(
236 base::span<const uint8_t> handshake_message) {
237 if (!ConfirmClientHandshakeMessage(handshake_message))
238 return std::vector<uint8_t>();
239
240 return ConstructAuthenticatorHelloReply(kValidAuthenticatorHello,
241 handshake_key_);
242 }
243
244 private:
245 std::string handshake_key_;
246 std::vector<uint8_t> client_session_random_;
247 std::vector<uint8_t> authenticator_session_random_ =
248 fido_parsing_utils::Materialize(kAuthenticatorSessionRandom);
249 };
250
251 } // namespace
252
253 class FidoCableHandshakeHandlerTest : public Test {
254 public:
FidoCableHandshakeHandlerTest()255 FidoCableHandshakeHandlerTest() {
256 auto connection = std::make_unique<MockFidoBleConnection>(
257 adapter_.get(), BluetoothTestBase::kTestDeviceAddress1);
258 connection_ = connection.get();
259 device_ = std::make_unique<FidoCableDevice>(std::move(connection));
260
261 connection_->read_callback() = device_->GetReadCallbackForTesting();
262 }
263
CreateHandshakeHandler(std::array<uint8_t,8> nonce,std::array<uint8_t,32> session_pre_key)264 std::unique_ptr<FidoCableV1HandshakeHandler> CreateHandshakeHandler(
265 std::array<uint8_t, 8> nonce,
266 std::array<uint8_t, 32> session_pre_key) {
267 return std::make_unique<FidoCableV1HandshakeHandler>(device_.get(), nonce,
268 session_pre_key);
269 }
270
ConnectWithLength(uint16_t length)271 void ConnectWithLength(uint16_t length) {
272 EXPECT_CALL(*connection(), ConnectPtr).WillOnce(Invoke([](auto* callback) {
273 std::move(*callback).Run(true);
274 }));
275
276 EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_))
277 .WillOnce(Invoke([length](auto* cb) { std::move(*cb).Run(length); }));
278
279 device()->Connect();
280 }
281
device()282 FidoCableDevice* device() { return device_.get(); }
connection()283 MockFidoBleConnection* connection() { return connection_; }
authenticator()284 FakeCableAuthenticator* authenticator() { return &authenticator_; }
callback_receiver()285 TestDeviceCallbackReceiver& callback_receiver() { return callback_receiver_; }
286
287 protected:
288 base::test::TaskEnvironment task_environment_;
289
290 private:
291 scoped_refptr<MockBluetoothAdapter> adapter_ =
292 base::MakeRefCounted<NiceMockBluetoothAdapter>();
293 FakeCableAuthenticator authenticator_;
294 MockFidoBleConnection* connection_;
295 std::unique_ptr<FidoCableDevice> device_;
296 TestDeviceCallbackReceiver callback_receiver_;
297 };
298
299 // Checks that outgoing handshake message from the client is a BLE frame with
300 // Control command type.
301 MATCHER(IsControlFrame, "") {
302 return !arg.empty() &&
303 arg[0] == base::strict_cast<uint8_t>(FidoBleDeviceCommand::kControl);
304 }
305
TEST_F(FidoCableHandshakeHandlerTest,HandShakeSuccess)306 TEST_F(FidoCableHandshakeHandlerTest, HandShakeSuccess) {
307 ConnectWithLength(kControlPointLength);
308
309 EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _))
310 .WillOnce(Invoke([this](const auto& data, auto* cb) {
311 base::SequencedTaskRunnerHandle::Get()->PostTask(
312 FROM_HERE, base::BindOnce(std::move(*cb), true));
313
314 const auto client_ble_handshake_message =
315 base::make_span(data).subspan(3);
316 base::SequencedTaskRunnerHandle::Get()->PostTask(
317 FROM_HERE,
318 base::BindOnce(
319 connection()->read_callback(),
320 ConstructSerializedOutgoingFragment(
321 authenticator()->RelyWithAuthenticatorHandShakeMessage(
322 client_ble_handshake_message))));
323 }));
324
325 auto handshake_handler =
326 CreateHandshakeHandler(kTestNonce, kTestSessionPreKey);
327 handshake_handler->InitiateCableHandshake(callback_receiver().callback());
328
329 callback_receiver().WaitForCallback();
330 const auto& value = callback_receiver().value();
331 ASSERT_TRUE(value);
332 EXPECT_TRUE(handshake_handler->ValidateAuthenticatorHandshakeMessage(*value));
333 EXPECT_EQ(GetExpectedEncryptionKey(handshake_handler->client_session_random_),
334 handshake_handler->GetEncryptionKeyAfterSuccessfulHandshake(
335 kAuthenticatorSessionRandom));
336 }
337
TEST_F(FidoCableHandshakeHandlerTest,HandShakeWithIncorrectSessionPreKey)338 TEST_F(FidoCableHandshakeHandlerTest, HandShakeWithIncorrectSessionPreKey) {
339 ConnectWithLength(kControlPointLength);
340
341 EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _))
342 .WillOnce(Invoke([this](const auto& data, auto* cb) {
343 base::SequencedTaskRunnerHandle::Get()->PostTask(
344 FROM_HERE, base::BindOnce(std::move(*cb), true));
345
346 const auto client_ble_handshake_message =
347 base::make_span(data).subspan(3);
348 base::SequencedTaskRunnerHandle::Get()->PostTask(
349 FROM_HERE,
350 base::BindOnce(
351 connection()->read_callback(),
352 ConstructSerializedOutgoingFragment(
353 authenticator()->RelyWithAuthenticatorHandShakeMessage(
354 client_ble_handshake_message))));
355 }));
356
357 auto handshake_handler =
358 CreateHandshakeHandler(kTestNonce, kIncorrectSessionPreKey);
359 handshake_handler->InitiateCableHandshake(callback_receiver().callback());
360
361 callback_receiver().WaitForCallback();
362 EXPECT_FALSE(callback_receiver().value());
363 }
364
TEST_F(FidoCableHandshakeHandlerTest,HandshakeFailWithIncorrectNonce)365 TEST_F(FidoCableHandshakeHandlerTest, HandshakeFailWithIncorrectNonce) {
366 ConnectWithLength(kControlPointLength);
367
368 EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _))
369 .WillOnce(Invoke([this](const auto& data, auto* cb) {
370 base::SequencedTaskRunnerHandle::Get()->PostTask(
371 FROM_HERE, base::BindOnce(std::move(*cb), true));
372
373 const auto client_ble_handshake_message =
374 base::make_span(data).subspan(3);
375 base::SequencedTaskRunnerHandle::Get()->PostTask(
376 FROM_HERE,
377 base::BindOnce(
378 connection()->read_callback(),
379 ConstructSerializedOutgoingFragment(
380 authenticator()->RelyWithAuthenticatorHandShakeMessage(
381 client_ble_handshake_message))));
382 }));
383
384 auto handshake_handler =
385 CreateHandshakeHandler(kIncorrectNonce, kTestSessionPreKey);
386 handshake_handler->InitiateCableHandshake(callback_receiver().callback());
387
388 callback_receiver().WaitForCallback();
389 EXPECT_FALSE(callback_receiver().value());
390 }
391
TEST_F(FidoCableHandshakeHandlerTest,HandshakeFailWithIncorrectAuthenticatorResponse)392 TEST_F(FidoCableHandshakeHandlerTest,
393 HandshakeFailWithIncorrectAuthenticatorResponse) {
394 auto handshake_handler =
395 CreateHandshakeHandler(kTestNonce, kTestSessionPreKey);
396
397 EXPECT_NE(kIncorrectHandshakeKey, handshake_handler->handshake_key_);
398 const auto authenticator_reply_with_invalid_key =
399 ConstructAuthenticatorHelloReply(kValidAuthenticatorHello,
400 kIncorrectHandshakeKey);
401 EXPECT_FALSE(handshake_handler->ValidateAuthenticatorHandshakeMessage(
402 authenticator_reply_with_invalid_key));
403
404 const auto authenticator_reply_with_invalid_hello_msg =
405 ConstructAuthenticatorHelloReply(kInvalidAuthenticatorHello,
406 handshake_handler->handshake_key_);
407 EXPECT_FALSE(handshake_handler->ValidateAuthenticatorHandshakeMessage(
408 authenticator_reply_with_invalid_hello_msg));
409 }
410
411 } // namespace device
412