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