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