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/get_assertion_task.h"
6 
7 #include <iterator>
8 #include <memory>
9 #include <string>
10 #include <utility>
11 #include <vector>
12 
13 #include "base/bind.h"
14 #include "base/test/task_environment.h"
15 #include "crypto/ec_private_key.h"
16 #include "device/base/features.h"
17 #include "device/fido/authenticator_get_assertion_response.h"
18 #include "device/fido/ctap_get_assertion_request.h"
19 #include "device/fido/device_response_converter.h"
20 #include "device/fido/fido_constants.h"
21 #include "device/fido/fido_parsing_utils.h"
22 #include "device/fido/fido_test_data.h"
23 #include "device/fido/mock_fido_device.h"
24 #include "device/fido/test_callback_receiver.h"
25 #include "device/fido/virtual_ctap2_device.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 
29 using ::testing::_;
30 
31 namespace device {
32 namespace {
33 
34 using TestGetAssertionTaskCallbackReceiver =
35     ::device::test::StatusAndValueCallbackReceiver<
36         CtapDeviceResponseCode,
37         base::Optional<AuthenticatorGetAssertionResponse>>;
38 
39 class FidoGetAssertionTaskTest : public testing::Test {
40  public:
FidoGetAssertionTaskTest()41   FidoGetAssertionTaskTest() {}
42 
get_assertion_callback_receiver()43   TestGetAssertionTaskCallbackReceiver& get_assertion_callback_receiver() {
44     return cb_;
45   }
46 
47  private:
48   base::test::TaskEnvironment task_environment_;
49   TestGetAssertionTaskCallbackReceiver cb_;
50 };
51 
TEST_F(FidoGetAssertionTaskTest,TestGetAssertionSuccess)52 TEST_F(FidoGetAssertionTaskTest, TestGetAssertionSuccess) {
53   auto device = MockFidoDevice::MakeCtap();
54   device->ExpectCtap2CommandAndRespondWith(
55       CtapRequestCommand::kAuthenticatorGetAssertion,
56       test_data::kTestGetAssertionResponse);
57 
58   CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
59                                         test_data::kClientDataJson);
60   request_param.allow_list.emplace_back(PublicKeyCredentialDescriptor(
61       CredentialType::kPublicKey,
62       fido_parsing_utils::Materialize(
63           test_data::kTestGetAssertionCredentialId)));
64 
65   auto task = std::make_unique<GetAssertionTask>(
66       device.get(), std::move(request_param),
67       get_assertion_callback_receiver().callback());
68 
69   get_assertion_callback_receiver().WaitForCallback();
70   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
71             get_assertion_callback_receiver().status());
72   EXPECT_TRUE(get_assertion_callback_receiver().value());
73 }
74 
TEST_F(FidoGetAssertionTaskTest,TestU2fSignSuccess)75 TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) {
76   auto device = MockFidoDevice::MakeU2f();
77   device->ExpectWinkedAtLeastOnce();
78   device->ExpectRequestAndRespondWith(
79       test_data::kU2fSignCommandApdu,
80       test_data::kApduEncodedNoErrorSignResponse);
81 
82   CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
83                                         test_data::kClientDataJson);
84   request_param.allow_list.emplace_back(PublicKeyCredentialDescriptor(
85       CredentialType::kPublicKey,
86       fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
87 
88   auto task = std::make_unique<GetAssertionTask>(
89       device.get(), std::move(request_param),
90       get_assertion_callback_receiver().callback());
91 
92   get_assertion_callback_receiver().WaitForCallback();
93   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
94             get_assertion_callback_receiver().status());
95   EXPECT_TRUE(get_assertion_callback_receiver().value());
96 }
97 
TEST_F(FidoGetAssertionTaskTest,TestSignSuccessWithFake)98 TEST_F(FidoGetAssertionTaskTest, TestSignSuccessWithFake) {
99   auto private_key = crypto::ECPrivateKey::Create();
100   std::string public_key;
101   private_key->ExportRawPublicKey(&public_key);
102   auto hash = fido_parsing_utils::CreateSHA256Hash(public_key);
103   std::vector<uint8_t> key_handle(hash.begin(), hash.end());
104   CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
105                                         test_data::kClientDataJson);
106   request_param.allow_list.emplace_back(
107       PublicKeyCredentialDescriptor(CredentialType::kPublicKey, key_handle));
108   ;
109 
110   auto device = std::make_unique<VirtualCtap2Device>();
111   device->mutable_state()->registrations.emplace(
112       key_handle,
113       VirtualFidoDevice::RegistrationData(
114           std::move(private_key),
115           fido_parsing_utils::CreateSHA256Hash(test_data::kRelyingPartyId),
116           42 /* counter */));
117   test::TestCallbackReceiver<> done;
118   device->DiscoverSupportedProtocolAndDeviceInfo(done.callback());
119   done.WaitForCallback();
120 
121   auto task = std::make_unique<GetAssertionTask>(
122       device.get(), std::move(request_param),
123       get_assertion_callback_receiver().callback());
124 
125   get_assertion_callback_receiver().WaitForCallback();
126   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
127             get_assertion_callback_receiver().status());
128 
129   // Just a sanity check, we don't verify the actual signature.
130   ASSERT_GE(32u + 1u + 4u + 8u,  // Minimal ECDSA signature is 8 bytes
131             get_assertion_callback_receiver()
132                 .value()
133                 ->auth_data()
134                 .SerializeToByteArray()
135                 .size());
136   EXPECT_EQ(0x01,
137             get_assertion_callback_receiver()
138                 .value()
139                 ->auth_data()
140                 .SerializeToByteArray()[32]);  // UP flag
141   // Counter is incremented for every sign request.
142   EXPECT_EQ(43, get_assertion_callback_receiver()
143                     .value()
144                     ->auth_data()
145                     .SerializeToByteArray()[36]);  // counter
146 }
147 
TEST_F(FidoGetAssertionTaskTest,TestIncorrectGetAssertionResponse)148 TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) {
149   auto device = MockFidoDevice::MakeCtap();
150   device->ExpectCtap2CommandAndRespondWith(
151       CtapRequestCommand::kAuthenticatorGetAssertion, base::nullopt);
152 
153   auto task = std::make_unique<GetAssertionTask>(
154       device.get(),
155       CtapGetAssertionRequest(test_data::kRelyingPartyId,
156                               test_data::kClientDataJson),
157       get_assertion_callback_receiver().callback());
158 
159   get_assertion_callback_receiver().WaitForCallback();
160   EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
161             get_assertion_callback_receiver().status());
162   EXPECT_FALSE(get_assertion_callback_receiver().value());
163 }
164 
TEST_F(FidoGetAssertionTaskTest,TestU2fSignRequestWithEmptyAllowedList)165 TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) {
166   auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
167                                          test_data::kClientDataJson);
168 
169   auto device = MockFidoDevice::MakeU2f();
170   device->ExpectWinkedAtLeastOnce();
171   device->ExpectRequestAndRespondWith(
172       test_data::kU2fFakeRegisterCommand,
173       test_data::kApduEncodedNoErrorSignResponse);
174 
175   auto task = std::make_unique<GetAssertionTask>(
176       device.get(), std::move(request),
177       get_assertion_callback_receiver().callback());
178 
179   get_assertion_callback_receiver().WaitForCallback();
180   EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
181             get_assertion_callback_receiver().status());
182   EXPECT_FALSE(get_assertion_callback_receiver().value());
183 }
184 
185 // Checks that when device supports both CTAP2 and U2F protocol and when
186 // appId extension parameter is present, the browser first checks presence
187 // of valid credentials via silent authentication.
TEST_F(FidoGetAssertionTaskTest,TestSilentSignInWhenAppIdExtensionPresent)188 TEST_F(FidoGetAssertionTaskTest, TestSilentSignInWhenAppIdExtensionPresent) {
189   CtapGetAssertionRequest request(test_data::kRelyingPartyId,
190                                   test_data::kClientDataJson);
191 
192   std::vector<PublicKeyCredentialDescriptor> allowed_list;
193   allowed_list.push_back(PublicKeyCredentialDescriptor(
194       CredentialType::kPublicKey,
195       fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
196   request.app_id = test_data::kAppId;
197   request.alternative_application_parameter =
198       fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
199           test_data::kAlternativeApplicationParameter));
200   request.allow_list = std::move(allowed_list);
201 
202   auto device = MockFidoDevice::MakeCtap();
203   device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest,
204                                       test_data::kTestGetAssertionResponse);
205   device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest,
206                                       test_data::kTestGetAssertionResponse);
207 
208   auto task = std::make_unique<GetAssertionTask>(
209       device.get(), std::move(request),
210       get_assertion_callback_receiver().callback());
211 
212   get_assertion_callback_receiver().WaitForCallback();
213   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
214             get_assertion_callback_receiver().status());
215 }
216 
TEST_F(FidoGetAssertionTaskTest,TestU2fFallbackForAppIdExtension)217 TEST_F(FidoGetAssertionTaskTest, TestU2fFallbackForAppIdExtension) {
218   CtapGetAssertionRequest request(test_data::kRelyingPartyId,
219                                   test_data::kClientDataJson);
220 
221   std::vector<PublicKeyCredentialDescriptor> allowed_list;
222   allowed_list.push_back(PublicKeyCredentialDescriptor(
223       CredentialType::kPublicKey,
224       fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
225   request.app_id = test_data::kAppId;
226   request.alternative_application_parameter =
227       fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
228           test_data::kAlternativeApplicationParameter));
229   request.allow_list = std::move(allowed_list);
230 
231   ::testing::InSequence s;
232   auto device = MockFidoDevice::MakeCtap();
233   std::array<uint8_t, 1> error{{base::strict_cast<uint8_t>(
234       CtapDeviceResponseCode::kCtap2ErrNoCredentials)}};
235   // First, as the device supports both CTAP2 and U2F, the browser will attempt
236   // a CTAP2 GetAssertion.
237   device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest,
238                                       error);
239   // After falling back to U2F the request will use the alternative app_param,
240   // which will be rejected.
241   device->ExpectWinkedAtLeastOnce();
242   device->ExpectRequestAndRespondWith(
243       test_data::kU2fSignCommandApduWithAlternativeApplicationParameter,
244       test_data::kU2fWrongDataApduResponse);
245   // After the rejection, the U2F sign request with the primary application
246   // parameter should be tried.
247   device->ExpectWinkedAtLeastOnce();
248   device->ExpectRequestAndRespondWith(
249       test_data::kU2fSignCommandApdu,
250       test_data::kApduEncodedNoErrorSignResponse);
251 
252   auto task = std::make_unique<GetAssertionTask>(
253       device.get(), std::move(request),
254       get_assertion_callback_receiver().callback());
255   get_assertion_callback_receiver().WaitForCallback();
256   EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
257             get_assertion_callback_receiver().status());
258 }
259 
TEST_F(FidoGetAssertionTaskTest,TestAvoidSilentSignInForCtapOnlyDevice)260 TEST_F(FidoGetAssertionTaskTest, TestAvoidSilentSignInForCtapOnlyDevice) {
261   CtapGetAssertionRequest request(test_data::kRelyingPartyId,
262                                   test_data::kClientDataJson);
263 
264   std::vector<PublicKeyCredentialDescriptor> allowed_list;
265   allowed_list.push_back(PublicKeyCredentialDescriptor(
266       CredentialType::kPublicKey,
267       fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
268 
269   request.app_id = test_data::kAppId;
270   request.alternative_application_parameter =
271       fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
272           test_data::kAlternativeApplicationParameter));
273   request.allow_list = std::move(allowed_list);
274 
275   auto device = MockFidoDevice::MakeCtap(ReadCTAPGetInfoResponse(
276       test_data::kTestCtap2OnlyAuthenticatorGetInfoResponse));
277   std::array<uint8_t, 1> error{
278       {base::strict_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrOther)}};
279   device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest,
280                                       error);
281 
282   auto task = std::make_unique<GetAssertionTask>(
283       device.get(), std::move(request),
284       get_assertion_callback_receiver().callback());
285   get_assertion_callback_receiver().WaitForCallback();
286   EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
287             get_assertion_callback_receiver().status());
288 }
289 
290 }  // namespace
291 }  // namespace device
292