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 <utility>
8 
9 #include "base/bind.h"
10 #include "device/base/features.h"
11 #include "device/fido/authenticator_get_assertion_response.h"
12 #include "device/fido/ctap2_device_operation.h"
13 #include "device/fido/make_credential_task.h"
14 #include "device/fido/u2f_sign_operation.h"
15 
16 namespace device {
17 
18 namespace {
19 
MayFallbackToU2fWithAppIdExtension(const FidoDevice & device,const CtapGetAssertionRequest & request)20 bool MayFallbackToU2fWithAppIdExtension(
21     const FidoDevice& device,
22     const CtapGetAssertionRequest& request) {
23   bool ctap2_device_supports_u2f =
24       device.device_info() &&
25       base::Contains(device.device_info()->versions, ProtocolVersion::kU2f);
26   return request.alternative_application_parameter &&
27          ctap2_device_supports_u2f && !request.allow_list.empty();
28 }
29 
30 }  // namespace
31 
GetAssertionTask(FidoDevice * device,CtapGetAssertionRequest request,GetAssertionTaskCallback callback)32 GetAssertionTask::GetAssertionTask(FidoDevice* device,
33                                    CtapGetAssertionRequest request,
34                                    GetAssertionTaskCallback callback)
35     : FidoTask(device),
36       request_(std::move(request)),
37       callback_(std::move(callback)) {
38   // This code assumes that user-presence is requested in order to implement
39   // possible U2F-fallback.
40   DCHECK(request_.user_presence_required);
41 
42   // The UV parameter should have been made binary by this point because CTAP2
43   // only takes a binary value.
44   DCHECK_NE(request_.user_verification,
45             UserVerificationRequirement::kPreferred);
46 }
47 
48 GetAssertionTask::~GetAssertionTask() = default;
49 
Cancel()50 void GetAssertionTask::Cancel() {
51   canceled_ = true;
52 
53   if (sign_operation_) {
54     sign_operation_->Cancel();
55   }
56   if (dummy_register_operation_) {
57     dummy_register_operation_->Cancel();
58   }
59 }
60 
61 // static
StringFixupPredicate(const std::vector<const cbor::Value * > & path)62 bool GetAssertionTask::StringFixupPredicate(
63     const std::vector<const cbor::Value*>& path) {
64   if (path.size() != 2 || !path[0]->is_unsigned() ||
65       path[0]->GetUnsigned() != 4 || !path[1]->is_string()) {
66     return false;
67   }
68 
69   const std::string& user_key = path[1]->GetString();
70   return user_key == "name" || user_key == "displayName";
71 }
72 
StartTask()73 void GetAssertionTask::StartTask() {
74   if (device()->supported_protocol() == ProtocolVersion::kCtap2) {
75     GetAssertion();
76   } else {
77     U2fSign();
78   }
79 }
80 
NextSilentRequest()81 CtapGetAssertionRequest GetAssertionTask::NextSilentRequest() {
82   DCHECK(current_allow_list_batch_ < allow_list_batches_.size());
83   CtapGetAssertionRequest request = request_;
84   request.allow_list = allow_list_batches_.at(current_allow_list_batch_++);
85   request.user_presence_required = false;
86   request.user_verification = UserVerificationRequirement::kDiscouraged;
87   return request;
88 }
89 
GetAssertion()90 void GetAssertionTask::GetAssertion() {
91   if (request_.allow_list.empty()) {
92     sign_operation_ = std::make_unique<Ctap2DeviceOperation<
93         CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
94         device(), request_,
95         base::BindOnce(&GetAssertionTask::HandleResponse,
96                        weak_factory_.GetWeakPtr()),
97         base::BindOnce(&ReadCTAPGetAssertionResponse), StringFixupPredicate);
98     sign_operation_->Start();
99     return;
100   }
101 
102   // Most authenticators can only process allowList parameters up to a certain
103   // size. Batch the list into chunks according to what the device can handle
104   // and filter out IDs that are too large to originate from this device.
105   allow_list_batches_ =
106       FilterAndBatchCredentialDescriptors(request_.allow_list, *device());
107 
108   // If filtering eliminated all entries from the allowList, just collect a
109   // dummy touch, then fail the request.
110   if (allow_list_batches_.empty()) {
111     dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
112         CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
113         device(), MakeCredentialTask::GetTouchRequest(device()),
114         base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
115                        weak_factory_.GetWeakPtr()),
116         base::BindOnce(&ReadCTAPMakeCredentialResponse,
117                        device()->DeviceTransport()),
118         /*string_fixup_predicate=*/nullptr);
119     dummy_register_operation_->Start();
120     return;
121   }
122 
123   // If the filtered allowList is small enough to be sent in a single request,
124   // do so.
125   if (allow_list_batches_.size() == 1 &&
126       !MayFallbackToU2fWithAppIdExtension(*device(), request_)) {
127     CtapGetAssertionRequest request = request_;
128     request.allow_list = allow_list_batches_.front();
129     sign_operation_ = std::make_unique<Ctap2DeviceOperation<
130         CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
131         device(), std::move(request),
132         base::BindOnce(&GetAssertionTask::HandleResponse,
133                        weak_factory_.GetWeakPtr()),
134         base::BindOnce(&ReadCTAPGetAssertionResponse), StringFixupPredicate);
135     sign_operation_->Start();
136     return;
137   }
138 
139   // If the filtered list is too large to be sent at once, or if an App ID might
140   // need to be tested because the site used the appid extension, probe the
141   // credential IDs silently.
142   sign_operation_ =
143       std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest,
144                                             AuthenticatorGetAssertionResponse>>(
145           device(), NextSilentRequest(),
146           base::BindOnce(&GetAssertionTask::HandleResponseToSilentRequest,
147                          weak_factory_.GetWeakPtr()),
148           base::BindOnce(&ReadCTAPGetAssertionResponse),
149           /*string_fixup_predicate=*/nullptr);
150   sign_operation_->Start();
151 }
152 
U2fSign()153 void GetAssertionTask::U2fSign() {
154   DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
155 
156   sign_operation_ = std::make_unique<U2fSignOperation>(device(), request_,
157                                                        std::move(callback_));
158   sign_operation_->Start();
159 }
160 
HandleResponse(CtapDeviceResponseCode response_code,base::Optional<AuthenticatorGetAssertionResponse> response_data)161 void GetAssertionTask::HandleResponse(
162     CtapDeviceResponseCode response_code,
163     base::Optional<AuthenticatorGetAssertionResponse> response_data) {
164   if (canceled_) {
165     return;
166   }
167 
168   // Some authenticators will return this error before waiting for a touch if
169   // they don't recognise a credential. In other cases the result can be
170   // returned immediately.
171   if (response_code != CtapDeviceResponseCode::kCtap2ErrInvalidCredential) {
172     std::move(callback_).Run(response_code, std::move(response_data));
173     return;
174   }
175 
176   // The request failed in a way that didn't request a touch. Simulate it.
177   dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
178       CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
179       device(), MakeCredentialTask::GetTouchRequest(device()),
180       base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
181                      weak_factory_.GetWeakPtr()),
182       base::BindOnce(&ReadCTAPMakeCredentialResponse,
183                      device()->DeviceTransport()),
184       /*string_fixup_predicate=*/nullptr);
185   dummy_register_operation_->Start();
186 }
187 
HandleResponseToSilentRequest(CtapDeviceResponseCode response_code,base::Optional<AuthenticatorGetAssertionResponse> response_data)188 void GetAssertionTask::HandleResponseToSilentRequest(
189     CtapDeviceResponseCode response_code,
190     base::Optional<AuthenticatorGetAssertionResponse> response_data) {
191   DCHECK(request_.allow_list.size() > 0);
192   DCHECK(allow_list_batches_.size() > 0);
193   DCHECK(0 < current_allow_list_batch_ &&
194          current_allow_list_batch_ <= allow_list_batches_.size());
195 
196   if (canceled_) {
197     return;
198   }
199 
200   // One credential from the previous batch was recognized by the device. As
201   // this authentication was a silent authentication (i.e. user touch was not
202   // provided), try again with only that batch, user presence enforced and with
203   // the original user verification configuration.
204   // TODO(martinkr): We could get the exact credential ID that was recognized
205   // from |response_data| and send only that.
206   if (response_code == CtapDeviceResponseCode::kSuccess) {
207     CtapGetAssertionRequest request = request_;
208     request.allow_list = allow_list_batches_.at(current_allow_list_batch_ - 1);
209     sign_operation_ = std::make_unique<Ctap2DeviceOperation<
210         CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
211         device(), std::move(request),
212         base::BindOnce(&GetAssertionTask::HandleResponse,
213                        weak_factory_.GetWeakPtr()),
214         base::BindOnce(&ReadCTAPGetAssertionResponse),
215         /*string_fixup_predicate=*/nullptr);
216     sign_operation_->Start();
217     return;
218   }
219 
220   // Credential was not recognized or an error occurred. Probe the next
221   // credential.
222   if (current_allow_list_batch_ < allow_list_batches_.size()) {
223     sign_operation_ = std::make_unique<Ctap2DeviceOperation<
224         CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
225         device(), NextSilentRequest(),
226         base::BindOnce(&GetAssertionTask::HandleResponseToSilentRequest,
227                        weak_factory_.GetWeakPtr()),
228         base::BindOnce(&ReadCTAPGetAssertionResponse),
229         /*string_fixup_predicate=*/nullptr);
230     sign_operation_->Start();
231     return;
232   }
233 
234   // None of the credentials were recognized. Fall back to U2F or collect a
235   // dummy touch.
236   if (MayFallbackToU2fWithAppIdExtension(*device(), request_)) {
237     device()->set_supported_protocol(ProtocolVersion::kU2f);
238     U2fSign();
239     return;
240   }
241   dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
242       CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
243       device(), MakeCredentialTask::GetTouchRequest(device()),
244       base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
245                      weak_factory_.GetWeakPtr()),
246       base::BindOnce(&ReadCTAPMakeCredentialResponse,
247                      device()->DeviceTransport()),
248       /*string_fixup_predicate=*/nullptr);
249   dummy_register_operation_->Start();
250 }
251 
HandleDummyMakeCredentialComplete(CtapDeviceResponseCode response_code,base::Optional<AuthenticatorMakeCredentialResponse> response_data)252 void GetAssertionTask::HandleDummyMakeCredentialComplete(
253     CtapDeviceResponseCode response_code,
254     base::Optional<AuthenticatorMakeCredentialResponse> response_data) {
255   std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
256                            base::nullopt);
257 }
258 
259 }  // namespace device
260