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