1 // Copyright 2019 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 <vector>
6
7 #include "testing/gmock/include/gmock/gmock.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/test/task_environment.h"
13 #include "device/fido/bio/enrollment_handler.h"
14 #include "device/fido/fido_transport_protocol.h"
15 #include "device/fido/test_callback_receiver.h"
16 #include "device/fido/virtual_fido_device_factory.h"
17
18 namespace device {
19 namespace {
20
21 constexpr char kPIN[] = "1477";
22
23 class BioEnrollmentHandlerTest : public ::testing::Test {
SetUp()24 void SetUp() override {
25 virtual_device_factory_.SetSupportedProtocol(ProtocolVersion::kCtap2);
26 virtual_device_factory_.mutable_state()->pin = kPIN;
27 virtual_device_factory_.mutable_state()->pin_retries =
28 device::kMaxPinRetries;
29 }
30
31 public:
OnEnroll(BioEnrollmentSampleStatus status,uint8_t remaining_samples)32 void OnEnroll(BioEnrollmentSampleStatus status, uint8_t remaining_samples) {
33 if (status != BioEnrollmentSampleStatus::kGood) {
34 sample_failures_++;
35 return;
36 }
37 if (!sampling_) {
38 sampling_ = true;
39 remaining_samples_ = remaining_samples;
40 return;
41 }
42 EXPECT_EQ(remaining_samples, --remaining_samples_);
43 }
44
45 protected:
MakeHandler()46 std::unique_ptr<BioEnrollmentHandler> MakeHandler() {
47 return std::make_unique<BioEnrollmentHandler>(
48 base::flat_set<FidoTransportProtocol>{
49 FidoTransportProtocol::kUsbHumanInterfaceDevice},
50 ready_callback_.callback(), error_callback_.callback(),
51 base::BindRepeating(&BioEnrollmentHandlerTest::GetPIN,
52 base::Unretained(this)),
53 &virtual_device_factory_);
54 }
55
56 std::pair<CtapDeviceResponseCode, BioEnrollmentHandler::TemplateId>
EnrollTemplate(BioEnrollmentHandler * handler)57 EnrollTemplate(BioEnrollmentHandler* handler) {
58 test::StatusAndValueCallbackReceiver<CtapDeviceResponseCode,
59 BioEnrollmentHandler::TemplateId>
60 cb;
61 handler->EnrollTemplate(MakeSampleCallback(), cb.callback());
62
63 cb.WaitForCallback();
64 return {cb.status(), cb.value()};
65 }
66
GetPIN(int64_t attempts,base::OnceCallback<void (std::string)> provide_pin)67 void GetPIN(int64_t attempts,
68 base::OnceCallback<void(std::string)> provide_pin) {
69 std::move(provide_pin).Run(kPIN);
70 }
71
MakeSampleCallback()72 BioEnrollmentHandler::SampleCallback MakeSampleCallback() {
73 remaining_samples_ = 0;
74 sample_failures_ = 0;
75 sampling_ = false;
76 return base::BindRepeating(&BioEnrollmentHandlerTest::OnEnroll,
77 base::Unretained(this));
78 }
79
80 uint8_t remaining_samples_;
81 size_t sample_failures_;
82 bool sampling_;
83 base::test::TaskEnvironment task_environment_;
84 test::TestCallbackReceiver<> ready_callback_;
85 test::ValueCallbackReceiver<BioEnrollmentStatus> error_callback_;
86 test::VirtualFidoDeviceFactory virtual_device_factory_;
87 };
88
89 // Tests bio enrollment handler against device without PIN support.
TEST_F(BioEnrollmentHandlerTest,NoPINSupport)90 TEST_F(BioEnrollmentHandlerTest, NoPINSupport) {
91 VirtualCtap2Device::Config config;
92 config.pin_support = false;
93 config.bio_enrollment_preview_support = true;
94
95 virtual_device_factory_.SetCtap2Config(config);
96
97 auto handler = MakeHandler();
98 error_callback_.WaitForCallback();
99
100 EXPECT_EQ(error_callback_.value(), BioEnrollmentStatus::kNoPINSet);
101 }
102
103 // Tests enrollment handler PIN soft block.
TEST_F(BioEnrollmentHandlerTest,SoftPINBlock)104 TEST_F(BioEnrollmentHandlerTest, SoftPINBlock) {
105 VirtualCtap2Device::Config config;
106 config.pin_support = true;
107 config.bio_enrollment_preview_support = true;
108
109 virtual_device_factory_.mutable_state()->pin = "1234";
110 virtual_device_factory_.SetCtap2Config(config);
111
112 auto handler = MakeHandler();
113 error_callback_.WaitForCallback();
114
115 EXPECT_EQ(error_callback_.value(), BioEnrollmentStatus::kSoftPINBlock);
116 }
117
118 // Tests bio enrollment commands against an authenticator lacking support.
TEST_F(BioEnrollmentHandlerTest,NoBioEnrollmentSupport)119 TEST_F(BioEnrollmentHandlerTest, NoBioEnrollmentSupport) {
120 VirtualCtap2Device::Config config;
121 config.pin_support = true;
122
123 virtual_device_factory_.SetCtap2Config(config);
124
125 auto handler = MakeHandler();
126 error_callback_.WaitForCallback();
127 EXPECT_EQ(error_callback_.value(),
128 BioEnrollmentStatus::kAuthenticatorMissingBioEnrollment);
129 }
130
131 // Tests fingerprint enrollment lifecycle.
TEST_F(BioEnrollmentHandlerTest,Enroll)132 TEST_F(BioEnrollmentHandlerTest, Enroll) {
133 VirtualCtap2Device::Config config;
134 config.pin_support = true;
135 config.bio_enrollment_preview_support = true;
136
137 virtual_device_factory_.SetCtap2Config(config);
138
139 auto handler = MakeHandler();
140 ready_callback_.WaitForCallback();
141
142 CtapDeviceResponseCode status;
143 BioEnrollmentHandler::TemplateId template_id;
144 std::tie(status, template_id) = EnrollTemplate(handler.get());
145 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
146 EXPECT_FALSE(template_id.empty());
147 }
148
149 // Tests enrolling multiple fingerprints.
TEST_F(BioEnrollmentHandlerTest,EnrollMultiple)150 TEST_F(BioEnrollmentHandlerTest, EnrollMultiple) {
151 VirtualCtap2Device::Config config;
152 config.pin_support = true;
153 config.bio_enrollment_preview_support = true;
154
155 virtual_device_factory_.SetCtap2Config(config);
156
157 auto handler = MakeHandler();
158 ready_callback_.WaitForCallback();
159
160 // Multiple enrollments
161 for (auto i = 0; i < 4; i++) {
162 CtapDeviceResponseCode status;
163 BioEnrollmentHandler::TemplateId template_id;
164 std::tie(status, template_id) = EnrollTemplate(handler.get());
165 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
166 EXPECT_FALSE(template_id.empty());
167 }
168
169 // Enumerate to check enrollments.
170 test::StatusAndValueCallbackReceiver<
171 CtapDeviceResponseCode,
172 base::Optional<std::map<std::vector<uint8_t>, std::string>>>
173 cb;
174 handler->EnumerateTemplates(cb.callback());
175 cb.WaitForCallback();
176 EXPECT_EQ(cb.status(), CtapDeviceResponseCode::kSuccess);
177 EXPECT_EQ(cb.value(),
178 (std::map<std::vector<uint8_t>, std::string>{{{1}, "Template1"},
179 {{2}, "Template2"},
180 {{3}, "Template3"},
181 {{4}, "Template4"}}));
182 }
183
184 // Tests enrolling beyond maximum capacity.
TEST_F(BioEnrollmentHandlerTest,EnrollMax)185 TEST_F(BioEnrollmentHandlerTest, EnrollMax) {
186 VirtualCtap2Device::Config config;
187 config.pin_support = true;
188 config.bio_enrollment_preview_support = true;
189
190 virtual_device_factory_.SetCtap2Config(config);
191
192 auto handler = MakeHandler();
193 ready_callback_.WaitForCallback();
194
195 // Enroll until full.
196 CtapDeviceResponseCode status;
197 BioEnrollmentHandler::TemplateId template_id;
198 for (;;) {
199 std::tie(status, template_id) = EnrollTemplate(handler.get());
200 if (status != CtapDeviceResponseCode::kSuccess)
201 break;
202 }
203
204 EXPECT_EQ(status, CtapDeviceResponseCode::kCtap2ErrKeyStoreFull);
205 EXPECT_TRUE(template_id.empty());
206 }
207
208 // Tests enumerating with no enrollments.
TEST_F(BioEnrollmentHandlerTest,EnumerateNone)209 TEST_F(BioEnrollmentHandlerTest, EnumerateNone) {
210 VirtualCtap2Device::Config config;
211 config.pin_support = true;
212 config.bio_enrollment_preview_support = true;
213
214 virtual_device_factory_.SetCtap2Config(config);
215
216 auto handler = MakeHandler();
217 ready_callback_.WaitForCallback();
218
219 test::StatusAndValueCallbackReceiver<
220 CtapDeviceResponseCode,
221 base::Optional<std::map<std::vector<uint8_t>, std::string>>>
222 cb;
223 handler->EnumerateTemplates(cb.callback());
224 cb.WaitForCallback();
225 EXPECT_EQ(cb.status(), CtapDeviceResponseCode::kCtap2ErrInvalidOption);
226 EXPECT_EQ(cb.value(), base::nullopt);
227 }
228
229 // Tests enumerating with one enrollment.
TEST_F(BioEnrollmentHandlerTest,EnumerateOne)230 TEST_F(BioEnrollmentHandlerTest, EnumerateOne) {
231 VirtualCtap2Device::Config config;
232 config.pin_support = true;
233 config.bio_enrollment_preview_support = true;
234
235 virtual_device_factory_.SetCtap2Config(config);
236
237 auto handler = MakeHandler();
238 ready_callback_.WaitForCallback();
239
240 // Enroll - skip response validation
241 CtapDeviceResponseCode status;
242 BioEnrollmentHandler::TemplateId template_id;
243 std::tie(status, template_id) = EnrollTemplate(handler.get());
244 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
245 EXPECT_FALSE(template_id.empty());
246
247 // Enumerate
248 test::StatusAndValueCallbackReceiver<
249 CtapDeviceResponseCode,
250 base::Optional<std::map<std::vector<uint8_t>, std::string>>>
251 cb1;
252 handler->EnumerateTemplates(cb1.callback());
253 cb1.WaitForCallback();
254 EXPECT_EQ(cb1.status(), CtapDeviceResponseCode::kSuccess);
255 EXPECT_EQ(cb1.value(),
256 (std::map<std::vector<uint8_t>, std::string>{{{1}, "Template1"}}));
257 }
258
259 // Tests renaming an enrollment (success and failure).
TEST_F(BioEnrollmentHandlerTest,Rename)260 TEST_F(BioEnrollmentHandlerTest, Rename) {
261 VirtualCtap2Device::Config config;
262 config.pin_support = true;
263 config.bio_enrollment_preview_support = true;
264
265 virtual_device_factory_.SetCtap2Config(config);
266
267 auto handler = MakeHandler();
268 ready_callback_.WaitForCallback();
269
270 // Rename non-existent enrollment.
271 test::ValueCallbackReceiver<CtapDeviceResponseCode> cb0;
272 handler->RenameTemplate({1}, "OtherFingerprint1", cb0.callback());
273 cb0.WaitForCallback();
274 EXPECT_EQ(cb0.value(), CtapDeviceResponseCode::kCtap2ErrInvalidOption);
275
276 // Enroll - skip response validation.
277 CtapDeviceResponseCode status;
278 BioEnrollmentHandler::TemplateId template_id;
279 std::tie(status, template_id) = EnrollTemplate(handler.get());
280 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
281 EXPECT_FALSE(template_id.empty());
282
283 // Rename non-existent enrollment.
284 test::ValueCallbackReceiver<CtapDeviceResponseCode> cb2;
285 handler->RenameTemplate(template_id, "OtherFingerprint1", cb2.callback());
286 cb2.WaitForCallback();
287 EXPECT_EQ(cb2.value(), CtapDeviceResponseCode::kSuccess);
288
289 // Enumerate to validate renaming.
290 test::StatusAndValueCallbackReceiver<
291 CtapDeviceResponseCode,
292 base::Optional<std::map<std::vector<uint8_t>, std::string>>>
293 cb3;
294 handler->EnumerateTemplates(cb3.callback());
295 cb3.WaitForCallback();
296 EXPECT_EQ(cb3.status(), CtapDeviceResponseCode::kSuccess);
297 EXPECT_EQ(cb3.value(), (std::map<std::vector<uint8_t>, std::string>{
298 {template_id, "OtherFingerprint1"}}));
299 }
300
301 // Tests deleting an enrollment (success and failure).
TEST_F(BioEnrollmentHandlerTest,Delete)302 TEST_F(BioEnrollmentHandlerTest, Delete) {
303 VirtualCtap2Device::Config config;
304 config.pin_support = true;
305 config.bio_enrollment_preview_support = true;
306
307 virtual_device_factory_.SetCtap2Config(config);
308
309 auto handler = MakeHandler();
310 ready_callback_.WaitForCallback();
311
312 // Delete non-existent enrollment.
313 test::ValueCallbackReceiver<CtapDeviceResponseCode> cb0;
314 handler->DeleteTemplate({1}, cb0.callback());
315 cb0.WaitForCallback();
316 EXPECT_EQ(cb0.value(), CtapDeviceResponseCode::kCtap2ErrInvalidOption);
317
318 // Enroll - skip response validation.
319 CtapDeviceResponseCode status;
320 BioEnrollmentHandler::TemplateId template_id;
321 std::tie(status, template_id) = EnrollTemplate(handler.get());
322 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
323 EXPECT_FALSE(template_id.empty());
324
325 // Delete existing enrollment.
326 test::ValueCallbackReceiver<CtapDeviceResponseCode> cb2;
327 handler->DeleteTemplate({1}, cb2.callback());
328 cb2.WaitForCallback();
329 EXPECT_EQ(cb2.value(), CtapDeviceResponseCode::kSuccess);
330
331 // Attempt to delete again to prove enrollment is gone.
332 test::ValueCallbackReceiver<CtapDeviceResponseCode> cb3;
333 handler->DeleteTemplate({1}, cb3.callback());
334 cb3.WaitForCallback();
335 EXPECT_EQ(cb3.value(), CtapDeviceResponseCode::kCtap2ErrInvalidOption);
336 }
337
338 // Test that enrollment succeeds even if one of the samples yields an error
339 // status. The error status should be propagated into the SampleCallback.
TEST_F(BioEnrollmentHandlerTest,SampleError)340 TEST_F(BioEnrollmentHandlerTest, SampleError) {
341 VirtualCtap2Device::Config config;
342 config.pin_support = true;
343 config.bio_enrollment_preview_support = true;
344
345 virtual_device_factory_.SetCtap2Config(config);
346 virtual_device_factory_.mutable_state()->bio_enrollment_next_sample_error =
347 true;
348
349 auto handler = MakeHandler();
350 ready_callback_.WaitForCallback();
351
352 CtapDeviceResponseCode status;
353 BioEnrollmentHandler::TemplateId template_id;
354 std::tie(status, template_id) = EnrollTemplate(handler.get());
355 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
356 EXPECT_EQ(sample_failures_, 1u);
357 }
358
359 // Test that enrollment succeeds even if one of the samples yields a timeout
360 // status. The timeout status should not propagate into the SampleCallback.
TEST_F(BioEnrollmentHandlerTest,SampleNoUserActivity)361 TEST_F(BioEnrollmentHandlerTest, SampleNoUserActivity) {
362 VirtualCtap2Device::Config config;
363 config.pin_support = true;
364 config.bio_enrollment_preview_support = true;
365
366 virtual_device_factory_.SetCtap2Config(config);
367 virtual_device_factory_.mutable_state()->bio_enrollment_next_sample_timeout =
368 true;
369
370 auto handler = MakeHandler();
371 ready_callback_.WaitForCallback();
372
373 CtapDeviceResponseCode status;
374 BioEnrollmentHandler::TemplateId template_id;
375 std::tie(status, template_id) = EnrollTemplate(handler.get());
376 EXPECT_EQ(status, CtapDeviceResponseCode::kSuccess);
377 EXPECT_EQ(sample_failures_, 0u);
378 }
379
380 } // namespace
381 } // namespace device
382