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 "content/browser/webauth/authenticator_common.h"
6
7 #include <array>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/check.h"
14 #include "base/command_line.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/notreached.h"
17 #include "base/rand_util.h"
18 #include "base/strings/string_piece.h"
19 #include "base/strings/utf_string_conversion_utils.h"
20 #include "base/task/task_traits.h"
21 #include "base/task/thread_pool.h"
22 #include "base/timer/timer.h"
23 #include "build/build_config.h"
24 #include "content/browser/bad_message.h"
25 #include "content/browser/webauth/authenticator_environment_impl.h"
26 #include "content/browser/webauth/virtual_authenticator_request_delegate.h"
27 #include "content/browser/webauth/virtual_fido_discovery_factory.h"
28 #include "content/browser/webauth/webauth_request_security_checker.h"
29 #include "content/public/browser/browser_context.h"
30 #include "content/public/browser/content_browser_client.h"
31 #include "content/public/browser/device_service.h"
32 #include "content/public/browser/is_uvpaa.h"
33 #include "content/public/browser/navigation_handle.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/render_process_host.h"
36 #include "content/public/browser/render_widget_host_view.h"
37 #include "content/public/browser/web_contents.h"
38 #include "content/public/common/content_client.h"
39 #include "content/public/common/content_features.h"
40 #include "crypto/sha2.h"
41 #include "device/base/features.h"
42 #include "device/fido/attestation_statement.h"
43 #include "device/fido/ctap_make_credential_request.h"
44 #include "device/fido/features.h"
45 #include "device/fido/fido_authenticator.h"
46 #include "device/fido/fido_constants.h"
47 #include "device/fido/fido_parsing_utils.h"
48 #include "device/fido/fido_transport_protocol.h"
49 #include "device/fido/get_assertion_request_handler.h"
50 #include "device/fido/make_credential_request_handler.h"
51 #include "device/fido/public_key.h"
52 #include "device/fido/public_key_credential_descriptor.h"
53 #include "device/fido/public_key_credential_params.h"
54 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
55 #include "net/cert/asn1_util.h"
56 #include "net/der/input.h"
57 #include "net/der/parse_values.h"
58 #include "net/der/parser.h"
59 #include "services/network/public/cpp/is_potentially_trustworthy.h"
60 #include "third_party/blink/public/common/loader/network_utils.h"
61 #include "url/url_constants.h"
62 #include "url/url_util.h"
63
64 #if defined(OS_MAC)
65 #include "device/fido/mac/authenticator.h"
66 #include "device/fido/mac/credential_metadata.h"
67 #endif
68
69 #if defined(OS_WIN)
70 #include "device/fido/win/authenticator.h"
71 #include "device/fido/win/webauthn_api.h"
72 #endif
73
74 namespace content {
75
76 // RequestExtension is a type of extension in a WebAuthn request that might
77 // yield an extension output in the response.
78 enum class RequestExtension {
79 kAppID,
80 kHMACSecret,
81 kPRF,
82 kCredProps,
83 kLargeBlobEnable,
84 kLargeBlobRead,
85 kLargeBlobWrite,
86 };
87
88 namespace client_data {
89 const char kCreateType[] = "webauthn.create";
90 const char kGetType[] = "webauthn.get";
91 const char kU2fSignType[] = "navigator.id.getAssertion";
92 const char kU2fRegisterType[] = "navigator.id.finishEnrollment";
93 } // namespace client_data
94
95 namespace {
96
97 // AttestationPromptResult enumerates events related to attestation prompts.
98 // These values are recorded in an UMA histogram and so should not be
99 // reassigned.
100 enum class AttestationPromptResult {
101 // kQueried indicates that the embedder was queried in order to determine
102 // whether attestation information should be returned to the origin.
103 kQueried = 0,
104 // kTimeout indicates that a timeout occurred while awaiting the result of an
105 // attestation query.
106 kTimeout = 1,
107 // kAllowed indicates that the query to the embedder was resolved positively.
108 // (E.g. the user clicked to allow, or the embedded allowed immediately by
109 // policy.)
110 kAllowed = 2,
111 // kBlocked indicates that the query to the embedder was resolved negatively.
112 // (E.g. the user clicked to block, or closed the dialog.)
113 kBlocked = 3,
114 // kAbandoned indications that the user closed the tab or navigated away while
115 // the attestation prompt was showing.
116 kAbandoned = 4,
117 kMaxValue = kAbandoned,
118 };
119
120 // Validates whether the given origin is authorized to use the provided App
121 // ID value, mostly according to the rules in
122 // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-appid-and-facets-v1.2-ps-20170411.html#determining-if-a-caller-s-facetid-is-authorized-for-an-appid.
123 //
124 // Returns the App ID to use for the request, or base::nullopt if the origin
125 // is not authorized to use the provided value.
ProcessAppIdExtension(std::string appid,const url::Origin & origin)126 base::Optional<std::string> ProcessAppIdExtension(std::string appid,
127 const url::Origin& origin) {
128 // The CryptoToken U2F extension checks the appid before calling the WebAuthn
129 // API so there is no need to validate it here.
130 if (WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(origin)) {
131 if (!GURL(appid).is_valid()) {
132 DCHECK(false) << "cryptotoken request did not set a valid App ID";
133 return base::nullopt;
134 }
135 return appid;
136 }
137
138 // Step 1: "If the AppID is not an HTTPS URL, and matches the FacetID of the
139 // caller, no additional processing is necessary and the operation may
140 // proceed."
141
142 // Webauthn is only supported on secure origins and |ValidateEffectiveDomain|
143 // has already checked this property of |origin| before this call. Thus this
144 // step is moot.
145 DCHECK(blink::network_utils::IsOriginSecure(origin.GetURL()));
146
147 // Step 2: "If the AppID is null or empty, the client must set the AppID to be
148 // the FacetID of the caller, and the operation may proceed without additional
149 // processing."
150 if (appid.empty()) {
151 // While the U2F spec says to default the App ID to the Facet ID, which is
152 // the origin plus a trailing forward slash [1], cryptotoken and Firefox
153 // just use the site's Origin without trailing slash. We follow their
154 // implementations rather than the spec.
155 //
156 // [1]https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-appid-and-facets-v2.0-id-20180227.html#determining-the-facetid-of-a-calling-application
157 appid = origin.Serialize();
158 }
159
160 // Step 3: "If the caller's FacetID is an https:// Origin sharing the same
161 // host as the AppID, (e.g. if an application hosted at
162 // https://fido.example.com/myApp set an AppID of
163 // https://fido.example.com/myAppId), no additional processing is necessary
164 // and the operation may proceed."
165 GURL appid_url = GURL(appid);
166 if (!appid_url.is_valid() || appid_url.scheme() != url::kHttpsScheme ||
167 appid_url.scheme_piece() != origin.scheme()) {
168 WebAuthRequestSecurityChecker::ReportSecurityCheckFailure(
169 RelyingPartySecurityCheckFailure::kAppIdExtensionInvalid);
170 return base::nullopt;
171 }
172
173 // This check is repeated inside |SameDomainOrHost|, just after this. However
174 // it's cheap and mirrors the structure of the spec.
175 if (appid_url.host_piece() == origin.host()) {
176 return appid;
177 }
178
179 // At this point we diverge from the specification in order to avoid the
180 // complexity of making a network request which isn't believed to be
181 // necessary in practice. See also
182 // https://bugzilla.mozilla.org/show_bug.cgi?id=1244959#c8
183 if (net::registry_controlled_domains::SameDomainOrHost(
184 appid_url, origin,
185 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
186 return appid;
187 }
188
189 // As a compatibility hack, sites within google.com are allowed to assert two
190 // special-case AppIDs. Firefox also does this:
191 // https://groups.google.com/forum/#!msg/mozilla.dev.platform/Uiu3fwnA2xw/201ynAiPAQAJ
192 const GURL kGstatic1 =
193 GURL("https://www.gstatic.com/securitykey/origins.json");
194 const GURL kGstatic2 =
195 GURL("https://www.gstatic.com/securitykey/a/google.com/origins.json");
196 DCHECK(kGstatic1.is_valid() && kGstatic2.is_valid());
197
198 if (origin.DomainIs("google.com") && !appid_url.has_ref() &&
199 (appid_url.EqualsIgnoringRef(kGstatic1) ||
200 appid_url.EqualsIgnoringRef(kGstatic2))) {
201 return appid;
202 }
203
204 WebAuthRequestSecurityChecker::ReportSecurityCheckFailure(
205 RelyingPartySecurityCheckFailure::kAppIdExtensionDomainMismatch);
206
207 return base::nullopt;
208 }
209
210 // The application parameter is the SHA-256 hash of the UTF-8 encoding of
211 // the application identity (i.e. relying_party_id) of the application
212 // requesting the registration.
CreateApplicationParameter(const std::string & relying_party_id)213 std::array<uint8_t, crypto::kSHA256Length> CreateApplicationParameter(
214 const std::string& relying_party_id) {
215 std::array<uint8_t, crypto::kSHA256Length> application_parameter;
216 crypto::SHA256HashString(relying_party_id, application_parameter.data(),
217 application_parameter.size());
218 return application_parameter;
219 }
220
CreateCtapGetAssertionRequest(const std::string & client_data_json,const blink::mojom::PublicKeyCredentialRequestOptionsPtr & options,base::Optional<std::string> app_id,bool is_incognito)221 device::CtapGetAssertionRequest CreateCtapGetAssertionRequest(
222 const std::string& client_data_json,
223 const blink::mojom::PublicKeyCredentialRequestOptionsPtr& options,
224 base::Optional<std::string> app_id,
225 bool is_incognito) {
226 device::CtapGetAssertionRequest request_parameter(options->relying_party_id,
227 client_data_json);
228
229 request_parameter.allow_list = options->allow_credentials;
230
231 request_parameter.user_verification = options->user_verification;
232
233 if (app_id) {
234 request_parameter.alternative_application_parameter =
235 CreateApplicationParameter(*app_id);
236 request_parameter.app_id = std::move(*app_id);
237 }
238
239 if (!options->cable_authentication_data.empty()) {
240 request_parameter.cable_extension = options->cable_authentication_data;
241 }
242 if (options->large_blob_read) {
243 request_parameter.large_blob_read = true;
244 request_parameter.large_blob_key = true;
245 }
246 if (options->large_blob_write) {
247 request_parameter.large_blob_key = true;
248 }
249 request_parameter.is_incognito_mode = is_incognito;
250 return request_parameter;
251 }
252
253 // Parses the FIDO transport types extension from the DER-encoded, X.509
254 // certificate in |der_cert| and appends any unique transport types found to
255 // |out_transports|.
AppendUniqueTransportsFromCertificate(base::span<const uint8_t> der_cert,std::vector<device::FidoTransportProtocol> * out_transports)256 void AppendUniqueTransportsFromCertificate(
257 base::span<const uint8_t> der_cert,
258 std::vector<device::FidoTransportProtocol>* out_transports) {
259 // See
260 // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-authenticator-transports-extension-v1.2-ps-20170411.html#fido-u2f-certificate-transports-extension
261 static constexpr uint8_t kTransportTypesOID[] = {
262 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01};
263 bool present, critical;
264 base::StringPiece contents;
265 if (!net::asn1::ExtractExtensionFromDERCert(
266 base::StringPiece(reinterpret_cast<const char*>(der_cert.data()),
267 der_cert.size()),
268 base::StringPiece(reinterpret_cast<const char*>(kTransportTypesOID),
269 sizeof(kTransportTypesOID)),
270 &present, &critical, &contents) ||
271 !present) {
272 return;
273 }
274
275 const net::der::Input contents_der(contents);
276 net::der::Parser contents_parser(contents_der);
277 net::der::BitString transport_bits;
278 if (!contents_parser.ReadBitString(&transport_bits)) {
279 return;
280 }
281
282 // The certificate extension contains a BIT STRING where different bits
283 // indicate support for different transports. The following array maps
284 // between these bit indexes and the FidoTransportProtocol enum.
285 static constexpr struct {
286 uint8_t bit_index;
287 device::FidoTransportProtocol transport;
288 } kTransportMapping[] = {
289 // Bit 0 is "Bluetooth Classic", not BLE. Since webauthn doesn't define a
290 // transport type for this we ignore it.
291 {1, device::FidoTransportProtocol::kBluetoothLowEnergy},
292 {2, device::FidoTransportProtocol::kUsbHumanInterfaceDevice},
293 {3, device::FidoTransportProtocol::kNearFieldCommunication},
294 {4, device::FidoTransportProtocol::kInternal},
295 };
296
297 for (const auto& mapping : kTransportMapping) {
298 if (transport_bits.AssertsBit(mapping.bit_index) &&
299 !base::Contains(*out_transports, mapping.transport)) {
300 out_transports->push_back(mapping.transport);
301 }
302 }
303 }
304
305 enum class AttestationErasureOption {
306 kIncludeAttestation,
307 kEraseAttestationButIncludeAaguid,
308 kEraseAttestationAndAaguid,
309 };
310
AdjustTimeout(base::Optional<base::TimeDelta> timeout,RenderFrameHost * render_frame_host)311 base::TimeDelta AdjustTimeout(base::Optional<base::TimeDelta> timeout,
312 RenderFrameHost* render_frame_host) {
313 // Time to wait for an authenticator to successfully complete an operation.
314 static constexpr base::TimeDelta kAdjustedTimeoutLower =
315 base::TimeDelta::FromSeconds(10);
316 static constexpr base::TimeDelta kAdjustedTimeoutUpper =
317 base::TimeDelta::FromMinutes(10);
318
319 if (!timeout) {
320 return kAdjustedTimeoutUpper;
321 }
322 const bool testing_api_enabled =
323 AuthenticatorEnvironmentImpl::GetInstance()
324 ->IsVirtualAuthenticatorEnabledFor(
325 static_cast<RenderFrameHostImpl*>(render_frame_host)
326 ->frame_tree_node());
327 if (testing_api_enabled) {
328 return *timeout;
329 }
330 return std::max(kAdjustedTimeoutLower,
331 std::min(kAdjustedTimeoutUpper, *timeout));
332 }
333
334 blink::mojom::MakeCredentialAuthenticatorResponsePtr
CreateMakeCredentialResponse(const std::string & client_data_json,device::AuthenticatorMakeCredentialResponse response_data,AttestationErasureOption attestation_erasure,const base::flat_set<RequestExtension> & requested_extensions)335 CreateMakeCredentialResponse(
336 const std::string& client_data_json,
337 device::AuthenticatorMakeCredentialResponse response_data,
338 AttestationErasureOption attestation_erasure,
339 const base::flat_set<RequestExtension>& requested_extensions) {
340 auto response = blink::mojom::MakeCredentialAuthenticatorResponse::New();
341 auto common_info = blink::mojom::CommonCredentialInfo::New();
342 common_info->client_data_json.assign(client_data_json.begin(),
343 client_data_json.end());
344 common_info->authenticator_data = response_data.attestation_object()
345 .authenticator_data()
346 .SerializeToByteArray();
347 if (response_data.android_client_data_ext()) {
348 DCHECK(base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport));
349 common_info->client_data_json = *response_data.android_client_data_ext();
350 }
351 common_info->raw_id = response_data.raw_credential_id();
352 common_info->id = response_data.GetId();
353 response->info = std::move(common_info);
354
355 // The transport list must not contain duplicates but the order doesn't matter
356 // because Blink will sort the resulting strings before returning them.
357 std::vector<device::FidoTransportProtocol> transports;
358 if (response_data.transport_used()) {
359 transports.push_back(*response_data.transport_used());
360 }
361 // If the attestation certificate specifies that the token supports any other
362 // transports, include them in the list.
363 base::Optional<base::span<const uint8_t>> leaf_cert =
364 response_data.attestation_object()
365 .attestation_statement()
366 .GetLeafCertificate();
367 if (leaf_cert) {
368 AppendUniqueTransportsFromCertificate(*leaf_cert, &transports);
369 }
370
371 response->transports = std::move(transports);
372
373 bool did_create_hmac_secret = false;
374 const base::Optional<cbor::Value>& maybe_extensions =
375 response_data.attestation_object().authenticator_data().extensions();
376 if (maybe_extensions) {
377 DCHECK(maybe_extensions->is_map());
378 const cbor::Value::MapValue& extensions = maybe_extensions->GetMap();
379 const auto hmac_secret_it =
380 extensions.find(cbor::Value(device::kExtensionHmacSecret));
381 if (hmac_secret_it != extensions.end() &&
382 hmac_secret_it->second.is_bool() && hmac_secret_it->second.GetBool()) {
383 did_create_hmac_secret = true;
384 }
385 }
386
387 for (const RequestExtension ext : requested_extensions) {
388 switch (ext) {
389 case RequestExtension::kPRF:
390 response->echo_prf = true;
391 response->prf = did_create_hmac_secret;
392 break;
393 case RequestExtension::kHMACSecret:
394 response->echo_hmac_create_secret = true;
395 response->hmac_create_secret = did_create_hmac_secret;
396 break;
397 case RequestExtension::kCredProps:
398 response->echo_cred_props = true;
399 if (response_data.is_resident_key) {
400 response->has_cred_props_rk = true;
401 response->cred_props_rk = *response_data.is_resident_key;
402 }
403 break;
404 case RequestExtension::kLargeBlobEnable:
405 response->echo_large_blob = true;
406 response->supports_large_blob =
407 response_data.large_blob_key().has_value();
408 break;
409 case RequestExtension::kAppID:
410 case RequestExtension::kLargeBlobRead:
411 case RequestExtension::kLargeBlobWrite:
412 NOTREACHED();
413 break;
414 }
415 }
416
417 switch (attestation_erasure) {
418 case AttestationErasureOption::kIncludeAttestation:
419 break;
420 case AttestationErasureOption::kEraseAttestationButIncludeAaguid:
421 response_data.EraseAttestationStatement(
422 device::AttestationObject::AAGUID::kInclude);
423 break;
424 case AttestationErasureOption::kEraseAttestationAndAaguid:
425 response_data.EraseAttestationStatement(
426 device::AttestationObject::AAGUID::kErase);
427 break;
428 }
429 response->attestation_object =
430 response_data.GetCBOREncodedAttestationObject();
431
432 const device::PublicKey* public_key = response_data.attestation_object()
433 .authenticator_data()
434 .attested_data()
435 ->public_key();
436 response->public_key_algo = public_key->algorithm;
437 const base::Optional<std::vector<uint8_t>>& public_key_der =
438 public_key->der_bytes;
439 if (public_key_der) {
440 response->public_key_der.emplace(public_key_der.value());
441 }
442
443 return response;
444 }
445
CreateGetAssertionResponse(const std::string & client_data_json,device::AuthenticatorGetAssertionResponse response_data,const base::Optional<std::string> & app_id,const base::flat_set<RequestExtension> & requested_extensions)446 blink::mojom::GetAssertionAuthenticatorResponsePtr CreateGetAssertionResponse(
447 const std::string& client_data_json,
448 device::AuthenticatorGetAssertionResponse response_data,
449 const base::Optional<std::string>& app_id,
450 const base::flat_set<RequestExtension>& requested_extensions) {
451 auto response = blink::mojom::GetAssertionAuthenticatorResponse::New();
452 auto common_info = blink::mojom::CommonCredentialInfo::New();
453 common_info->client_data_json.assign(client_data_json.begin(),
454 client_data_json.end());
455 if (response_data.android_client_data_ext()) {
456 DCHECK(base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport));
457 common_info->client_data_json = *response_data.android_client_data_ext();
458 }
459 common_info->raw_id = response_data.raw_credential_id();
460 common_info->id = response_data.GetId();
461 response->info = std::move(common_info);
462 response->info->authenticator_data =
463 response_data.auth_data().SerializeToByteArray();
464 response->signature = response_data.signature();
465 response_data.user_entity()
466 ? response->user_handle.emplace(response_data.user_entity()->id)
467 : response->user_handle.emplace();
468
469 for (RequestExtension ext : requested_extensions) {
470 switch (ext) {
471 case RequestExtension::kAppID:
472 DCHECK(app_id);
473 response->echo_appid_extension = true;
474 if (response_data.GetRpIdHash() ==
475 CreateApplicationParameter(*app_id)) {
476 response->appid_extension = true;
477 }
478 break;
479 case RequestExtension::kPRF: {
480 response->echo_prf = true;
481 base::Optional<base::span<const uint8_t>> hmac_secret =
482 response_data.hmac_secret();
483 if (hmac_secret) {
484 auto prf_values = blink::mojom::PRFValues::New();
485 DCHECK(hmac_secret->size() == 32 || hmac_secret->size() == 64);
486 prf_values->first = device::fido_parsing_utils::Materialize(
487 hmac_secret->subspan(0, 32));
488 if (hmac_secret->size() == 64) {
489 prf_values->second = device::fido_parsing_utils::Materialize(
490 hmac_secret->subspan(32, 32));
491 }
492 response->prf_results = std::move(prf_values);
493 } else {
494 response->prf_not_evaluated =
495 response_data.hmac_secret_not_evaluated();
496 }
497 break;
498 }
499 case RequestExtension::kLargeBlobRead:
500 response->echo_large_blob = true;
501 response->large_blob = response_data.large_blob();
502 break;
503 case RequestExtension::kLargeBlobWrite:
504 response->echo_large_blob = true;
505 response->echo_large_blob_written = true;
506 response->large_blob_written = response_data.large_blob_written();
507 break;
508 case RequestExtension::kHMACSecret:
509 case RequestExtension::kCredProps:
510 case RequestExtension::kLargeBlobEnable:
511 NOTREACHED();
512 break;
513 }
514 }
515
516 return response;
517 }
518
IsUserVerifyingPlatformAuthenticatorAvailableImpl(AuthenticatorRequestClientDelegate * delegate,device::FidoDiscoveryFactory * discovery_factory,BrowserContext * browser_context,blink::mojom::Authenticator::IsUserVerifyingPlatformAuthenticatorAvailableCallback callback)519 void IsUserVerifyingPlatformAuthenticatorAvailableImpl(
520 AuthenticatorRequestClientDelegate* delegate,
521 device::FidoDiscoveryFactory* discovery_factory,
522 BrowserContext* browser_context,
523 blink::mojom::Authenticator::
524 IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) {
525 if (!delegate) {
526 // TODO(crbug/1110081): Investigate why this can be nullptr.
527 NOTREACHED();
528 std::move(callback).Run(false);
529 return;
530 }
531
532 base::Optional<bool> is_uvpaa_override =
533 delegate->IsUserVerifyingPlatformAuthenticatorAvailableOverride();
534 if (is_uvpaa_override) {
535 std::move(callback).Run(*is_uvpaa_override);
536 return;
537 }
538
539 #if defined(OS_MAC)
540 const base::Optional<device::fido::mac::AuthenticatorConfig> config =
541 delegate->GetTouchIdAuthenticatorConfig();
542 std::move(callback).Run(config &&
543 IsUVPlatformAuthenticatorAvailable(*config));
544 return;
545 #elif defined(OS_WIN)
546 if (browser_context->IsOffTheRecord()) {
547 std::move(callback).Run(false);
548 return;
549 }
550
551 std::move(callback).Run(IsUVPlatformAuthenticatorAvailable(
552 discovery_factory->win_webauthn_api()));
553 return;
554 #elif defined(OS_CHROMEOS)
555 if (browser_context->IsOffTheRecord()) {
556 std::move(callback).Run(false);
557 return;
558 }
559
560 // ChromeOS needs to do a dbus call to determine platform authenticator
561 // availability. The call is fast in practice, but nonetheless may
562 // theoretically block.
563 base::ThreadPool::PostTaskAndReplyWithResult(
564 FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
565 base::BindOnce(&IsUVPlatformAuthenticatorAvailable), std::move(callback));
566 return;
567 #else
568 std::move(callback).Run(false);
569 return;
570 #endif
571 }
572
573 // GetAvailableTransports returns the set of transports that should be passed to
574 // a FidoRequestHandler for the current request. This determines for which
575 // transports the request handler will attempt to obtain FidoDiscovery
576 // instances.
GetAvailableTransports(RenderFrameHost * render_frame_host,AuthenticatorRequestClientDelegate * delegate,device::FidoDiscoveryFactory * discovery_factory,const url::Origin & caller_origin)577 base::flat_set<device::FidoTransportProtocol> GetAvailableTransports(
578 RenderFrameHost* render_frame_host,
579 AuthenticatorRequestClientDelegate* delegate,
580 device::FidoDiscoveryFactory* discovery_factory,
581 const url::Origin& caller_origin) {
582 // U2F requests proxied from the cryptotoken extension are limited to USB
583 // devices.
584 if (WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
585 caller_origin)) {
586 return base::flat_set<device::FidoTransportProtocol>(
587 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
588 }
589
590 base::flat_set<device::FidoTransportProtocol> transports;
591 transports.insert(device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
592
593 #if BUILDFLAG(IS_CHROMEOS_ASH)
594 // TODO(crbug.com/1157651): Work around CrOS platform authenticator being
595 // unavailable in Incognito.
596 if (!content::WebContents::FromRenderFrameHost(render_frame_host)
597 ->GetBrowserContext()
598 ->IsOffTheRecord()) {
599 transports.insert(device::FidoTransportProtocol::kInternal);
600 }
601 #else
602 transports.insert(device::FidoTransportProtocol::kInternal);
603 #endif
604
605 if (discovery_factory->IsTestOverride()) {
606 // The desktop implementation does not support BLE or NFC, but we emulate
607 // them if the testing API is enabled.
608 transports.insert(device::FidoTransportProtocol::kBluetoothLowEnergy);
609 transports.insert(device::FidoTransportProtocol::kNearFieldCommunication);
610 }
611
612 if (base::FeatureList::IsEnabled(features::kWebAuthCable) ||
613 base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
614 transports.insert(
615 device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy);
616 }
617
618 if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
619 transports.insert(device::FidoTransportProtocol::kAndroidAccessory);
620 }
621
622 return transports;
623 }
624
625 // Returns a new FidoDiscoveryFactory for the current request. This may be
626 // a factory for virtual authenticators if the testing API is enabled for the
627 // given frame.
MakeDiscoveryFactory(RenderFrameHost * render_frame_host,AuthenticatorRequestClientDelegate * request_delegate,bool is_u2f_api_request)628 std::unique_ptr<device::FidoDiscoveryFactory> MakeDiscoveryFactory(
629 RenderFrameHost* render_frame_host,
630 AuthenticatorRequestClientDelegate* request_delegate,
631 bool is_u2f_api_request) {
632 VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
633 AuthenticatorEnvironmentImpl::GetInstance()
634 ->MaybeGetVirtualAuthenticatorManager(
635 static_cast<RenderFrameHostImpl*>(render_frame_host)
636 ->frame_tree_node());
637 if (virtual_authenticator_manager) {
638 return virtual_authenticator_manager->MakeDiscoveryFactory();
639 }
640
641 auto discovery_factory = std::make_unique<device::FidoDiscoveryFactory>();
642 #if defined(OS_MAC)
643 discovery_factory->set_mac_touch_id_info(
644 request_delegate->GetTouchIdAuthenticatorConfig());
645 #endif // defined(OS_MAC)
646
647 #if defined(OS_WIN)
648 if (base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi)) {
649 discovery_factory->set_win_webauthn_api(
650 device::WinWebAuthnApi::GetDefault());
651 }
652 #endif // defined(OS_WIN)
653
654 #if defined(OS_CHROMEOS)
655 if (base::FeatureList::IsEnabled(device::kWebAuthCrosPlatformAuthenticator)) {
656 discovery_factory->set_generate_request_id_callback(
657 request_delegate->GetGenerateRequestIdCallback(render_frame_host));
658 }
659 #endif // defined(OS_CHROMEOS)
660
661 return discovery_factory;
662 }
663
664 } // namespace
665
AuthenticatorCommon(RenderFrameHost * render_frame_host)666 AuthenticatorCommon::AuthenticatorCommon(RenderFrameHost* render_frame_host)
667 : render_frame_host_(render_frame_host),
668 security_checker_(static_cast<RenderFrameHostImpl*>(render_frame_host)
669 ->GetWebAuthRequestSecurityChecker()) {
670 DCHECK(render_frame_host_);
671 // Disable the back-forward cache for any document that makes WebAuthn
672 // requests. Pages using privacy-sensitive APIs are generally exempt from
673 // back-forward cache for now as a precaution.
674 BackForwardCache::DisableForRenderFrameHost(render_frame_host,
675 "WebAuthenticationAPI");
676 }
677
~AuthenticatorCommon()678 AuthenticatorCommon::~AuthenticatorCommon() {
679 // This call exists to assert that |render_frame_host_| outlives this object.
680 // If this is violated, ASAN should notice.
681 render_frame_host_->GetRoutingID();
682 }
683
684 std::unique_ptr<AuthenticatorRequestClientDelegate>
CreateRequestDelegate()685 AuthenticatorCommon::CreateRequestDelegate() {
686 auto* frame_tree_node =
687 static_cast<RenderFrameHostImpl*>(render_frame_host_)->frame_tree_node();
688 if (AuthenticatorEnvironmentImpl::GetInstance()
689 ->IsVirtualAuthenticatorEnabledFor(frame_tree_node)) {
690 return std::make_unique<VirtualAuthenticatorRequestDelegate>(
691 frame_tree_node);
692 }
693 return GetContentClient()->browser()->GetWebAuthenticationRequestDelegate(
694 render_frame_host_);
695 }
696
StartMakeCredentialRequest(bool allow_skipping_pin_touch)697 void AuthenticatorCommon::StartMakeCredentialRequest(
698 bool allow_skipping_pin_touch) {
699 InitDiscoveryFactory();
700
701 request_delegate_->ConfigureCable(
702 caller_origin_, base::span<const device::CableDiscoveryData>(),
703 discovery_factory());
704
705 make_credential_options_->allow_skipping_pin_touch = allow_skipping_pin_touch;
706
707 request_ = std::make_unique<device::MakeCredentialRequestHandler>(
708 discovery_factory(),
709 GetAvailableTransports(render_frame_host_, request_delegate_.get(),
710 discovery_factory(), caller_origin_),
711 *ctap_make_credential_request_, *make_credential_options_,
712 base::BindOnce(&AuthenticatorCommon::OnRegisterResponse,
713 weak_factory_.GetWeakPtr()));
714
715 request_delegate_->RegisterActionCallbacks(
716 base::BindOnce(&AuthenticatorCommon::OnCancelFromUI,
717 weak_factory_.GetWeakPtr()) /* cancel_callback */,
718 base::BindRepeating(
719 &AuthenticatorCommon::StartMakeCredentialRequest,
720 weak_factory_.GetWeakPtr(),
721 /*allow_skipping_pin_touch=*/false) /* start_over_callback */,
722 base::BindRepeating(
723 &device::FidoRequestHandlerBase::StartAuthenticatorRequest,
724 request_->GetWeakPtr()) /* request_callback */,
725 base::BindRepeating(
726 &device::FidoRequestHandlerBase::PowerOnBluetoothAdapter,
727 request_->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */);
728 if (make_credential_options_->resident_key !=
729 device::ResidentKeyRequirement::kDiscouraged) {
730 request_delegate_->SetMightCreateResidentCredential(true);
731 }
732 request_->set_observer(request_delegate_.get());
733 }
734
StartGetAssertionRequest(bool allow_skipping_pin_touch)735 void AuthenticatorCommon::StartGetAssertionRequest(
736 bool allow_skipping_pin_touch) {
737 InitDiscoveryFactory();
738
739 base::span<const device::CableDiscoveryData> cable_pairings;
740 if (ctap_get_assertion_request_->cable_extension && IsFocused()) {
741 cable_pairings = *ctap_get_assertion_request_->cable_extension;
742 }
743 request_delegate_->ConfigureCable(caller_origin_, cable_pairings,
744 discovery_factory());
745
746 request_ = std::make_unique<device::GetAssertionRequestHandler>(
747 discovery_factory(),
748 GetAvailableTransports(render_frame_host_, request_delegate_.get(),
749 discovery_factory(), caller_origin_),
750 *ctap_get_assertion_request_, *ctap_get_assertion_options_,
751 allow_skipping_pin_touch,
752 base::BindOnce(&AuthenticatorCommon::OnSignResponse,
753 weak_factory_.GetWeakPtr()));
754
755 request_delegate_->RegisterActionCallbacks(
756 base::BindOnce(&AuthenticatorCommon::OnCancelFromUI,
757 weak_factory_.GetWeakPtr()) /* cancel_callback */,
758 base::BindRepeating(
759 &AuthenticatorCommon::StartGetAssertionRequest,
760 weak_factory_.GetWeakPtr(),
761 /*allow_skipping_pin_touch=*/false) /* start_over_callback */,
762 base::BindRepeating(
763 &device::FidoRequestHandlerBase::StartAuthenticatorRequest,
764 request_->GetWeakPtr()) /* request_callback */,
765 base::BindRepeating(
766 &device::FidoRequestHandlerBase::PowerOnBluetoothAdapter,
767 request_->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */);
768
769 request_->set_observer(request_delegate_.get());
770 }
771
IsFocused() const772 bool AuthenticatorCommon::IsFocused() const {
773 return render_frame_host_->IsCurrent() && request_delegate_->IsFocused();
774 }
775
OnLargeBlobCompressed(data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result)776 void AuthenticatorCommon::OnLargeBlobCompressed(
777 data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result) {
778 ctap_get_assertion_request_->large_blob_write =
779 device::fido_parsing_utils::MaterializeOrNull(result.value);
780 StartGetAssertionRequest(/*allow_skipping_pin_touch=*/true);
781 }
782
OnLargeBlobUncompressed(device::AuthenticatorGetAssertionResponse response,data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result)783 void AuthenticatorCommon::OnLargeBlobUncompressed(
784 device::AuthenticatorGetAssertionResponse response,
785 data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result) {
786 response.set_large_blob(
787 device::fido_parsing_utils::MaterializeOrNull(result.value));
788 InvokeCallbackAndCleanup(
789 std::move(get_assertion_response_callback_),
790 blink::mojom::AuthenticatorStatus::SUCCESS,
791 CreateGetAssertionResponse(client_data_json_, std::move(response),
792 app_id_, requested_extensions_));
793 }
794
795 // static
796 // mojom::Authenticator
MakeCredential(url::Origin caller_origin,blink::mojom::PublicKeyCredentialCreationOptionsPtr options,blink::mojom::Authenticator::MakeCredentialCallback callback)797 void AuthenticatorCommon::MakeCredential(
798 url::Origin caller_origin,
799 blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
800 blink::mojom::Authenticator::MakeCredentialCallback callback) {
801 if (request_) {
802 if (WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
803 caller_origin)) {
804 // Requests originating from cryptotoken will generally outlive any
805 // navigation events on the tab of the request's sender. Evict pending
806 // requests if cryptotoken sends a new one such that requests from before
807 // a navigation event do not prevent new requests. See
808 // https://crbug.com/935480.
809 CancelWithStatus(blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
810 } else {
811 std::move(callback).Run(
812 blink::mojom::AuthenticatorStatus::PENDING_REQUEST, nullptr);
813 return;
814 }
815 }
816 DCHECK(!request_);
817
818 bool is_cross_origin;
819 blink::mojom::AuthenticatorStatus status =
820 security_checker_->ValidateAncestorOrigins(
821 caller_origin,
822 WebAuthRequestSecurityChecker::RequestType::kMakeCredential,
823 &is_cross_origin);
824 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
825 InvokeCallbackAndCleanup(std::move(callback), status);
826 return;
827 }
828
829 request_delegate_ = CreateRequestDelegate();
830 if (!request_delegate_) {
831 InvokeCallbackAndCleanup(std::move(callback),
832 blink::mojom::AuthenticatorStatus::PENDING_REQUEST,
833 nullptr, Focus::kDontCheck);
834 return;
835 }
836
837 base::Optional<std::string> rp_id =
838 request_delegate_->MaybeGetRelyingPartyIdOverride(
839 options->relying_party.id, caller_origin);
840
841 if (!rp_id) {
842 // If the delegate didn't override RP ID selection then apply standard
843 // rules.
844 rp_id = std::move(options->relying_party.id);
845 status = security_checker_->ValidateDomainAndRelyingPartyID(caller_origin,
846 *rp_id);
847 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
848 InvokeCallbackAndCleanup(std::move(callback), status, nullptr,
849 Focus::kDontCheck);
850 return;
851 }
852 }
853
854 caller_origin_ = caller_origin;
855 relying_party_id_ = *rp_id;
856 options->relying_party.id = std::move(*rp_id);
857 request_delegate_->SetRelyingPartyId(relying_party_id_);
858
859 base::Optional<std::string> appid_exclude;
860 if (options->appid_exclude) {
861 appid_exclude =
862 ProcessAppIdExtension(*options->appid_exclude, caller_origin);
863 if (!appid_exclude) {
864 InvokeCallbackAndCleanup(
865 std::move(callback),
866 blink::mojom::AuthenticatorStatus::INVALID_DOMAIN, nullptr,
867 Focus::kDontCheck);
868 return;
869 }
870 }
871
872 if (options->user.icon_url) {
873 status = security_checker_->ValidateAPrioriAuthenticatedUrl(
874 *options->user.icon_url);
875 }
876 if (status == blink::mojom::AuthenticatorStatus::SUCCESS &&
877 options->relying_party.icon_url) {
878 status = security_checker_->ValidateAPrioriAuthenticatedUrl(
879 *options->relying_party.icon_url);
880 }
881 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
882 bad_message::ReceivedBadMessage(render_frame_host_->GetProcess(),
883 bad_message::AUTH_INVALID_ICON_URL);
884 InvokeCallbackAndCleanup(std::move(callback), status, nullptr,
885 Focus::kDontCheck);
886 return;
887 }
888
889 if (!IsFocused()) {
890 InvokeCallbackAndCleanup(std::move(callback),
891 blink::mojom::AuthenticatorStatus::NOT_FOCUSED);
892 return;
893 }
894
895 const device::AuthenticatorSelectionCriteria
896 authenticator_selection_criteria =
897 options->authenticator_selection
898 ? *options->authenticator_selection
899 : device::AuthenticatorSelectionCriteria();
900 make_credential_options_ = device::MakeCredentialRequestHandler::Options(
901 authenticator_selection_criteria);
902
903 const bool might_create_resident_key =
904 make_credential_options_->resident_key !=
905 device::ResidentKeyRequirement::kDiscouraged;
906 if (might_create_resident_key && !request_delegate_->SupportsResidentKeys()) {
907 if (make_credential_options_->resident_key ==
908 device::ResidentKeyRequirement::kRequired) {
909 InvokeCallbackAndCleanup(
910 std::move(callback),
911 blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
912 return;
913 }
914 // Downgrade 'preferred' to 'discouraged'.
915 make_credential_options_->resident_key =
916 device::ResidentKeyRequirement::kDiscouraged;
917 }
918
919 // Reject any non-sensical credProtect extension values.
920 if ( // Can't require the default policy (or no policy).
921 (options->enforce_protection_policy &&
922 (options->protection_policy ==
923 blink::mojom::ProtectionPolicy::UNSPECIFIED ||
924 options->protection_policy == blink::mojom::ProtectionPolicy::NONE)) ||
925 // For non-resident keys, NONE doesn't make sense. (UV_OR_CRED_ID_REQUIRED
926 // does because, with CTAP 2.0, just because a resident key isn't
927 // _required_ doesn't mean that one won't be created and an RP might want
928 // credProtect to take effect if that happens.)
929 (!might_create_resident_key &&
930 options->protection_policy == blink::mojom::ProtectionPolicy::NONE) ||
931 // UV_REQUIRED only makes sense if UV is required overall.
932 (options->protection_policy ==
933 blink::mojom::ProtectionPolicy::UV_REQUIRED &&
934 authenticator_selection_criteria.user_verification_requirement() !=
935 device::UserVerificationRequirement::kRequired)) {
936 InvokeCallbackAndCleanup(
937 std::move(callback),
938 blink::mojom::AuthenticatorStatus::PROTECTION_POLICY_INCONSISTENT);
939 return;
940 }
941
942 base::Optional<device::CredProtectRequest> cred_protect_request;
943 switch (options->protection_policy) {
944 case blink::mojom::ProtectionPolicy::UNSPECIFIED:
945 if (might_create_resident_key) {
946 // If not specified, kUVOrCredIDRequired is made the default unless
947 // the authenticator defaults to something better.
948 cred_protect_request =
949 device::CredProtectRequest::kUVOrCredIDRequiredOrBetter;
950 }
951 break;
952 case blink::mojom::ProtectionPolicy::NONE:
953 cred_protect_request = device::CredProtectRequest::kUVOptional;
954 break;
955 case blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED:
956 cred_protect_request = device::CredProtectRequest::kUVOrCredIDRequired;
957 break;
958 case blink::mojom::ProtectionPolicy::UV_REQUIRED:
959 cred_protect_request = device::CredProtectRequest::kUVRequired;
960 break;
961 }
962
963 if (cred_protect_request) {
964 make_credential_options_->cred_protect_request = {
965 {*cred_protect_request, options->enforce_protection_policy}};
966 }
967
968 DCHECK(make_credential_response_callback_.is_null());
969 make_credential_response_callback_ = std::move(callback);
970
971 timer_->Start(
972 FROM_HERE, AdjustTimeout(options->timeout, render_frame_host_),
973 base::BindOnce(&AuthenticatorCommon::OnTimeout, base::Unretained(this)));
974
975 const bool origin_is_crypto_token_extension =
976 WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
977 caller_origin_);
978
979 // Cryptotoken provides the sender origin for register requests in the
980 // |relying_party| |name| attribute. (The |id| attribute contains the AppID.)
981 client_data_json_ =
982 origin_is_crypto_token_extension
983 ? device::SerializeCollectedClientDataToJson(
984 client_data::kU2fRegisterType, *options->relying_party.name,
985 options->challenge, /*is_cross_origin=*/false,
986 /*use_legacy_u2f_type_key=*/true)
987 : device::SerializeCollectedClientDataToJson(
988 client_data::kCreateType, caller_origin_.Serialize(),
989 options->challenge, is_cross_origin);
990
991 // Cryptotoken requests should be proxied without UI.
992 if (origin_is_crypto_token_extension || disable_ui_)
993 request_delegate_->DisableUI();
994
995 UMA_HISTOGRAM_COUNTS_100(
996 "WebAuthentication.MakeCredentialExcludeCredentialsCount",
997 options->exclude_credentials.size());
998
999 ctap_make_credential_request_ = device::CtapMakeCredentialRequest(
1000 client_data_json_, options->relying_party, options->user,
1001 device::PublicKeyCredentialParams(options->public_key_parameters));
1002 ctap_make_credential_request_->exclude_list = options->exclude_credentials;
1003 if (options->prf_enable) {
1004 requested_extensions_.insert(RequestExtension::kPRF);
1005 ctap_make_credential_request_->hmac_secret = true;
1006 }
1007 if (options->hmac_create_secret) {
1008 requested_extensions_.insert(RequestExtension::kHMACSecret);
1009 ctap_make_credential_request_->hmac_secret = true;
1010 }
1011 if (options->cred_props) {
1012 requested_extensions_.insert(RequestExtension::kCredProps);
1013 }
1014 if (options->large_blob_enable != device::LargeBlobSupport::kNotRequested) {
1015 requested_extensions_.insert(RequestExtension::kLargeBlobEnable);
1016 }
1017 make_credential_options_->large_blob_support = options->large_blob_enable;
1018 ctap_make_credential_request_->app_id = std::move(appid_exclude);
1019 ctap_make_credential_request_->is_incognito_mode =
1020 browser_context()->IsOffTheRecord();
1021 // On dual protocol CTAP2/U2F devices, force credential creation over U2F.
1022 ctap_make_credential_request_->is_u2f_only = origin_is_crypto_token_extension;
1023
1024 if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) &&
1025 !origin_is_crypto_token_extension && !is_cross_origin) {
1026 // Send the unhashed origin and challenge to caBLEv2 authenticators, because
1027 // the Android API requires them. It does not accept clientDataJSON or its
1028 // hash.
1029 // NOTE: Because Android has no way of building a clientDataJSON for
1030 // cross-origin requests, we don't create the extension for those. This
1031 // problem will go away once we add clientDataHash inputs to Android.
1032 make_credential_options_->android_client_data_ext.emplace(
1033 client_data::kCreateType, caller_origin_, options->challenge);
1034 }
1035
1036 // Compute the effective attestation conveyance preference.
1037 device::AttestationConveyancePreference attestation = options->attestation;
1038 // Enterprise attestation should not have been approved by this point.
1039 DCHECK(attestation !=
1040 device::AttestationConveyancePreference::kEnterpriseApprovedByBrowser);
1041 if (attestation == device::AttestationConveyancePreference::
1042 kEnterpriseIfRPListedOnAuthenticator &&
1043 request_delegate_->ShouldPermitIndividualAttestation(relying_party_id_)) {
1044 attestation =
1045 device::AttestationConveyancePreference::kEnterpriseApprovedByBrowser;
1046 }
1047 ctap_make_credential_request_->attestation_preference = attestation;
1048
1049 StartMakeCredentialRequest(/*allow_skipping_pin_touch=*/true);
1050 }
1051
1052 // mojom:Authenticator
GetAssertion(url::Origin caller_origin,blink::mojom::PublicKeyCredentialRequestOptionsPtr options,blink::mojom::Authenticator::GetAssertionCallback callback)1053 void AuthenticatorCommon::GetAssertion(
1054 url::Origin caller_origin,
1055 blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
1056 blink::mojom::Authenticator::GetAssertionCallback callback) {
1057 if (request_) {
1058 if (WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
1059 caller_origin)) {
1060 // Requests originating from cryptotoken will generally outlive any
1061 // navigation events on the tab of the request's sender. Evict pending
1062 // requests if cryptotoken sends a new one such that requests from before
1063 // a navigation event do not prevent new requests. See
1064 // https://crbug.com/935480.
1065 CancelWithStatus(blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1066 } else {
1067 std::move(callback).Run(
1068 blink::mojom::AuthenticatorStatus::PENDING_REQUEST, nullptr);
1069 return;
1070 }
1071 }
1072 DCHECK(!request_);
1073
1074 bool is_cross_origin;
1075 blink::mojom::AuthenticatorStatus status =
1076 security_checker_->ValidateAncestorOrigins(
1077 caller_origin,
1078 WebAuthRequestSecurityChecker::RequestType::kGetAssertion,
1079 &is_cross_origin);
1080 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
1081 InvokeCallbackAndCleanup(std::move(callback), status);
1082 return;
1083 }
1084
1085 request_delegate_ = CreateRequestDelegate();
1086 if (!request_delegate_) {
1087 InvokeCallbackAndCleanup(std::move(callback),
1088 blink::mojom::AuthenticatorStatus::PENDING_REQUEST,
1089 nullptr);
1090 return;
1091 }
1092
1093 base::Optional<std::string> rp_id =
1094 request_delegate_->MaybeGetRelyingPartyIdOverride(
1095 options->relying_party_id, caller_origin);
1096
1097 if (!rp_id) {
1098 // If the delegate didn't override RP ID selection then apply standard
1099 // rules.
1100 status = security_checker_->ValidateDomainAndRelyingPartyID(
1101 caller_origin, options->relying_party_id);
1102 if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
1103 InvokeCallbackAndCleanup(std::move(callback), status, nullptr);
1104 return;
1105 }
1106
1107 rp_id = std::move(options->relying_party_id);
1108 }
1109
1110 caller_origin_ = caller_origin;
1111 relying_party_id_ = *rp_id;
1112 options->relying_party_id = std::move(*rp_id);
1113 request_delegate_->SetRelyingPartyId(relying_party_id_);
1114
1115 const bool origin_is_crypto_token_extension =
1116 WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
1117 caller_origin_);
1118
1119 // Cryptotoken provides the sender origin for U2F sign requests in the
1120 // |relying_party_id| attribute.
1121 client_data_json_ =
1122 origin_is_crypto_token_extension
1123 ? device::SerializeCollectedClientDataToJson(
1124 client_data::kU2fSignType, options->relying_party_id,
1125 options->challenge, /*is_cross_origin=*/false,
1126 /*use_legacy_u2f_type_key=*/true)
1127 : device::SerializeCollectedClientDataToJson(
1128 client_data::kGetType, caller_origin_.Serialize(),
1129 options->challenge, is_cross_origin);
1130
1131 // Cryptotoken requests should be proxied without UI.
1132 if (origin_is_crypto_token_extension || disable_ui_)
1133 request_delegate_->DisableUI();
1134
1135 if (options->allow_credentials.empty()) {
1136 if (!request_delegate_->SupportsResidentKeys()) {
1137 InvokeCallbackAndCleanup(
1138 std::move(callback),
1139 blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
1140 return;
1141 }
1142 empty_allow_list_ = true;
1143 }
1144
1145 if (options->appid) {
1146 requested_extensions_.insert(RequestExtension::kAppID);
1147 app_id_ = ProcessAppIdExtension(*options->appid, caller_origin_);
1148 if (!app_id_) {
1149 InvokeCallbackAndCleanup(
1150 std::move(callback),
1151 blink::mojom::AuthenticatorStatus::INVALID_DOMAIN);
1152 return;
1153 }
1154 }
1155
1156 if (options->large_blob_read && options->large_blob_write) {
1157 InvokeCallbackAndCleanup(
1158 std::move(callback),
1159 blink::mojom::AuthenticatorStatus::CANNOT_READ_AND_WRITE_LARGE_BLOB);
1160 return;
1161 }
1162
1163 if (options->large_blob_read) {
1164 requested_extensions_.insert(RequestExtension::kLargeBlobRead);
1165 } else if (options->large_blob_write) {
1166 if (options->allow_credentials.size() != 1) {
1167 InvokeCallbackAndCleanup(std::move(callback),
1168 blink::mojom::AuthenticatorStatus::
1169 INVALID_ALLOW_CREDENTIALS_FOR_LARGE_BLOB);
1170 return;
1171 }
1172 requested_extensions_.insert(RequestExtension::kLargeBlobWrite);
1173 }
1174
1175 UMA_HISTOGRAM_COUNTS_100(
1176 "WebAuthentication.CredentialRequestAllowCredentialsCount",
1177 options->allow_credentials.size());
1178
1179 DCHECK(get_assertion_response_callback_.is_null());
1180 get_assertion_response_callback_ = std::move(callback);
1181
1182 timer_->Start(
1183 FROM_HERE, AdjustTimeout(options->timeout, render_frame_host_),
1184 base::BindOnce(&AuthenticatorCommon::OnTimeout, base::Unretained(this)));
1185
1186 ctap_get_assertion_request_ = CreateCtapGetAssertionRequest(
1187 client_data_json_, options, app_id_, browser_context()->IsOffTheRecord());
1188 ctap_get_assertion_options_.emplace();
1189
1190 bool is_first = true;
1191 base::Optional<std::vector<uint8_t>> last_id;
1192 if (options->prf) {
1193 requested_extensions_.insert(RequestExtension::kPRF);
1194 for (const auto& prf_input_from_renderer : options->prf_inputs) {
1195 device::CtapGetAssertionOptions::PRFInput prf_input;
1196
1197 // This statement enforces invariants that should be established by the
1198 // renderer.
1199 if (
1200 // Only the first element in the vector may be the default.
1201 (!is_first && !prf_input_from_renderer->id) ||
1202 // The PRF inputs must be sorted by credential ID to show that there
1203 // are no duplicates.
1204 (last_id.has_value() && prf_input_from_renderer->id.has_value() &&
1205 *last_id >= *prf_input_from_renderer->id) ||
1206 // The lengths are specified in authenticator.mojom, so hopefully Mojo
1207 // enforces them too.
1208 prf_input_from_renderer->first.size() != prf_input.salt1.size() ||
1209 (prf_input_from_renderer->second &&
1210 prf_input_from_renderer->second->size() != prf_input.salt1.size())) {
1211 NOTREACHED();
1212
1213 InvokeCallbackAndCleanup(
1214 std::move(get_assertion_response_callback_),
1215 blink::mojom::AuthenticatorStatus::UNKNOWN_ERROR);
1216 return;
1217 }
1218 is_first = false;
1219 last_id = prf_input_from_renderer->id;
1220
1221 if (prf_input_from_renderer->id) {
1222 prf_input.credential_id = std::move(*prf_input_from_renderer->id);
1223 }
1224
1225 memcpy(prf_input.salt1.data(), prf_input_from_renderer->first.data(),
1226 prf_input.salt1.size());
1227 if (prf_input_from_renderer->second) {
1228 prf_input.salt2.emplace();
1229 memcpy(prf_input.salt2->data(), prf_input_from_renderer->second->data(),
1230 prf_input.salt2->size());
1231 }
1232
1233 ctap_get_assertion_options_->prf_inputs.emplace_back(
1234 std::move(prf_input));
1235 }
1236 }
1237
1238 ctap_get_assertion_request_->is_u2f_only = origin_is_crypto_token_extension;
1239 if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) &&
1240 !origin_is_crypto_token_extension && !is_cross_origin) {
1241 // Send the unhashed origin and challenge to caBLEv2 authenticators, because
1242 // the Android API requires them. It does not accept clientDataJSON or its
1243 // hash.
1244 // NOTE: Because Android has no way of building a clientDataJSON for
1245 // cross-origin requests, we don't create the extension for those. This
1246 // problem will go away once we add clientDataHash inputs to Android.
1247 ctap_get_assertion_request_->android_client_data_ext.emplace(
1248 client_data::kGetType, caller_origin_, options->challenge);
1249 }
1250
1251 if (options->large_blob_write) {
1252 data_decoder_.GzipCompress(
1253 *options->large_blob_write,
1254 base::BindOnce(&AuthenticatorCommon::OnLargeBlobCompressed,
1255 weak_factory_.GetWeakPtr()));
1256 return;
1257 }
1258
1259 StartGetAssertionRequest(/*allow_skipping_pin_touch=*/true);
1260 }
1261
IsUserVerifyingPlatformAuthenticatorAvailable(blink::mojom::Authenticator::IsUserVerifyingPlatformAuthenticatorAvailableCallback callback)1262 void AuthenticatorCommon::IsUserVerifyingPlatformAuthenticatorAvailable(
1263 blink::mojom::Authenticator::
1264 IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) {
1265 // Use |request_delegate_| if a request is currently in progress; or create a
1266 // temporary request delegate otherwise. Note that CreateRequestDelegate() may
1267 // return nullptr if there is an active |request_delegate_| already.
1268 std::unique_ptr<AuthenticatorRequestClientDelegate> maybe_request_delegate =
1269 request_delegate_ ? nullptr : CreateRequestDelegate();
1270 AuthenticatorRequestClientDelegate* request_delegate_ptr =
1271 request_delegate_ ? request_delegate_.get()
1272 : maybe_request_delegate.get();
1273
1274 std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory =
1275 MakeDiscoveryFactory(render_frame_host_, request_delegate_ptr,
1276 /*is_u2f_api_request=*/false);
1277 device::FidoDiscoveryFactory* discovery_factory_testing_override =
1278 AuthenticatorEnvironmentImpl::GetInstance()
1279 ->MaybeGetDiscoveryFactoryTestOverride();
1280 device::FidoDiscoveryFactory* discovery_factory_ptr =
1281 discovery_factory_testing_override ? discovery_factory_testing_override
1282 : discovery_factory.get();
1283
1284 auto post_done_callback = base::BindOnce(
1285 [](blink::mojom::Authenticator::
1286 IsUserVerifyingPlatformAuthenticatorAvailableCallback callback,
1287 bool is_available) {
1288 base::SequencedTaskRunnerHandle::Get()->PostTask(
1289 FROM_HERE, base::BindOnce(std::move(callback), is_available));
1290 },
1291 std::move(callback));
1292
1293 IsUserVerifyingPlatformAuthenticatorAvailableImpl(
1294 request_delegate_ptr, discovery_factory_ptr, browser_context(),
1295 std::move(post_done_callback));
1296 }
1297
Cancel()1298 void AuthenticatorCommon::Cancel() {
1299 CancelWithStatus(blink::mojom::AuthenticatorStatus::ABORT_ERROR);
1300 }
1301
1302 // Callback to handle the async registration response from a U2fDevice.
OnRegisterResponse(device::MakeCredentialStatus status_code,base::Optional<device::AuthenticatorMakeCredentialResponse> response_data,const device::FidoAuthenticator * authenticator)1303 void AuthenticatorCommon::OnRegisterResponse(
1304 device::MakeCredentialStatus status_code,
1305 base::Optional<device::AuthenticatorMakeCredentialResponse> response_data,
1306 const device::FidoAuthenticator* authenticator) {
1307 if (!request_) {
1308 // Either the callback was called immediately and |request_| has not yet
1309 // been assigned (this is a bug), or a navigation caused the request to be
1310 // canceled while a callback was enqueued.
1311 return;
1312 }
1313
1314 switch (status_code) {
1315 case device::MakeCredentialStatus::kUserConsentButCredentialExcluded:
1316 // Duplicate registration: the new credential would be created on an
1317 // authenticator that already contains one of the credentials in
1318 // |exclude_credentials|.
1319 SignalFailureToRequestDelegate(
1320 authenticator,
1321 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1322 kKeyAlreadyRegistered,
1323 blink::mojom::AuthenticatorStatus::CREDENTIAL_EXCLUDED);
1324 return;
1325 case device::MakeCredentialStatus::kAuthenticatorResponseInvalid:
1326 // The response from the authenticator was corrupted.
1327 InvokeCallbackAndCleanup(
1328 std::move(make_credential_response_callback_),
1329 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
1330 Focus::kDoCheck);
1331 return;
1332 case device::MakeCredentialStatus::kUserConsentDenied:
1333 SignalFailureToRequestDelegate(
1334 authenticator,
1335 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1336 kUserConsentDenied,
1337 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1338 return;
1339 case device::MakeCredentialStatus::kSoftPINBlock:
1340 SignalFailureToRequestDelegate(
1341 authenticator,
1342 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1343 kSoftPINBlock,
1344 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1345 return;
1346 case device::MakeCredentialStatus::kHardPINBlock:
1347 SignalFailureToRequestDelegate(
1348 authenticator,
1349 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1350 kHardPINBlock,
1351 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1352 return;
1353 case device::MakeCredentialStatus::kAuthenticatorRemovedDuringPINEntry:
1354 SignalFailureToRequestDelegate(
1355 authenticator,
1356 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1357 kAuthenticatorRemovedDuringPINEntry,
1358 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1359 return;
1360 case device::MakeCredentialStatus::kAuthenticatorMissingResidentKeys:
1361 SignalFailureToRequestDelegate(
1362 authenticator,
1363 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1364 kAuthenticatorMissingResidentKeys,
1365 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1366 return;
1367 case device::MakeCredentialStatus::kAuthenticatorMissingUserVerification:
1368 SignalFailureToRequestDelegate(
1369 authenticator,
1370 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1371 kAuthenticatorMissingUserVerification,
1372 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1373 return;
1374 case device::MakeCredentialStatus::kAuthenticatorMissingLargeBlob:
1375 SignalFailureToRequestDelegate(
1376 authenticator,
1377 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1378 kAuthenticatorMissingLargeBlob,
1379 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1380 return;
1381 case device::MakeCredentialStatus::kNoCommonAlgorithms:
1382 SignalFailureToRequestDelegate(
1383 authenticator,
1384 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1385 kNoCommonAlgorithms,
1386 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1387 return;
1388 case device::MakeCredentialStatus::kStorageFull:
1389 SignalFailureToRequestDelegate(
1390 authenticator,
1391 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1392 kStorageFull,
1393 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1394 return;
1395 case device::MakeCredentialStatus::kWinInvalidStateError:
1396 InvokeCallbackAndCleanup(
1397 std::move(make_credential_response_callback_),
1398 blink::mojom::AuthenticatorStatus::CREDENTIAL_EXCLUDED, nullptr,
1399 Focus::kDoCheck);
1400 return;
1401 case device::MakeCredentialStatus::kWinNotAllowedError:
1402 SignalFailureToRequestDelegate(
1403 authenticator,
1404 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1405 kWinUserCancelled,
1406 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1407 return;
1408 case device::MakeCredentialStatus::kSuccess:
1409 DCHECK(response_data.has_value());
1410 DCHECK(authenticator);
1411
1412 auto transport_used = authenticator->AuthenticatorTransport();
1413 if (transport_used) {
1414 request_delegate_->UpdateLastTransportUsed(*transport_used);
1415 }
1416 bool is_transport_used_internal =
1417 transport_used &&
1418 *transport_used == device::FidoTransportProtocol::kInternal;
1419
1420 const auto attestation =
1421 ctap_make_credential_request_->attestation_preference;
1422 base::Optional<AttestationErasureOption> attestation_erasure;
1423 const bool origin_is_crypto_token_extension =
1424 WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
1425 caller_origin_);
1426
1427 // cryptotoken checks the attestation blocklist itself.
1428 if (!origin_is_crypto_token_extension &&
1429 device::DoesMatchWebAuthAttestationBlockedDomains(caller_origin_) &&
1430 !request_delegate_->ShouldPermitIndividualAttestation(
1431 relying_party_id_)) {
1432 attestation_erasure =
1433 AttestationErasureOption::kEraseAttestationAndAaguid;
1434 } else if (origin_is_crypto_token_extension &&
1435 attestation !=
1436 device::AttestationConveyancePreference::kNone) {
1437 // Cryptotoken requests may bypass the attestation prompt because the
1438 // extension implements its own. Invoking the attestation prompt code
1439 // here would not work anyway, because the WebContents associated with
1440 // the extension is not associated with any tab and therefore cannot
1441 // draw modal dialogs for the UI.
1442 //
1443 // Note that for AttestationConveyancePreference::kNone, attestation
1444 // erasure is still performed as usual.
1445 attestation_erasure = AttestationErasureOption::kIncludeAttestation;
1446 } else if (attestation == device::AttestationConveyancePreference::
1447 kEnterpriseApprovedByBrowser) {
1448 // If enterprise attestation was approved by policy then it can be
1449 // returned immediately.
1450 attestation_erasure = AttestationErasureOption::kIncludeAttestation;
1451 } else if (attestation == device::AttestationConveyancePreference::
1452 kEnterpriseIfRPListedOnAuthenticator &&
1453 !response_data->enterprise_attestation_returned) {
1454 // If enterprise attestation was requested, not approved by policy, and
1455 // not approved by the authenticator, then any attestation is stripped.
1456 attestation_erasure =
1457 AttestationErasureOption::kEraseAttestationAndAaguid;
1458 } else if (attestation !=
1459 device::AttestationConveyancePreference::kNone) {
1460 UMA_HISTOGRAM_ENUMERATION("WebAuthentication.AttestationPromptResult",
1461 AttestationPromptResult::kQueried);
1462 awaiting_attestation_response_ = true;
1463 request_delegate_->ShouldReturnAttestation(
1464 relying_party_id_, authenticator,
1465 response_data->enterprise_attestation_returned,
1466 base::BindOnce(
1467 &AuthenticatorCommon::OnRegisterResponseAttestationDecided,
1468 weak_factory_.GetWeakPtr(), std::move(*response_data),
1469 is_transport_used_internal));
1470 } else if (response_data->IsSelfAttestation()) {
1471 attestation_erasure = AttestationErasureOption::kIncludeAttestation;
1472 } else if (is_transport_used_internal) {
1473 // Contrary to what the WebAuthn spec says, for internal (platform)
1474 // authenticators we do not erase the AAGUID from authenticatorData,
1475 // even if requested attestationConveyancePreference is "none".
1476 attestation_erasure =
1477 AttestationErasureOption::kEraseAttestationButIncludeAaguid;
1478 } else {
1479 attestation_erasure =
1480 AttestationErasureOption::kEraseAttestationAndAaguid;
1481 }
1482
1483 if (attestation_erasure.has_value()) {
1484 InvokeCallbackAndCleanup(
1485 std::move(make_credential_response_callback_),
1486 blink::mojom::AuthenticatorStatus::SUCCESS,
1487 CreateMakeCredentialResponse(
1488 client_data_json_, std::move(*response_data),
1489 *attestation_erasure, requested_extensions_),
1490 Focus::kDoCheck);
1491 }
1492
1493 return;
1494 }
1495 NOTREACHED();
1496 }
1497
OnRegisterResponseAttestationDecided(device::AuthenticatorMakeCredentialResponse response_data,bool is_transport_used_internal,bool attestation_permitted)1498 void AuthenticatorCommon::OnRegisterResponseAttestationDecided(
1499 device::AuthenticatorMakeCredentialResponse response_data,
1500 bool is_transport_used_internal,
1501 bool attestation_permitted) {
1502 awaiting_attestation_response_ = false;
1503 if (!request_) {
1504 // The request has already been cleaned up, probably because a navigation
1505 // occurred while the permissions prompt was pending.
1506 return;
1507 }
1508
1509 AttestationErasureOption attestation_erasure;
1510 if (!attestation_permitted) {
1511 UMA_HISTOGRAM_ENUMERATION("WebAuthentication.AttestationPromptResult",
1512 AttestationPromptResult::kBlocked);
1513 if (is_transport_used_internal) {
1514 // For internal (platform) authenticators, we do not erase the
1515 // AAGUID from authenticatorData even if the user declines to
1516 // share attestation.
1517 attestation_erasure =
1518 AttestationErasureOption::kEraseAttestationButIncludeAaguid;
1519 } else {
1520 attestation_erasure =
1521 AttestationErasureOption::kEraseAttestationAndAaguid;
1522 }
1523 } else {
1524 UMA_HISTOGRAM_ENUMERATION("WebAuthentication.AttestationPromptResult",
1525 AttestationPromptResult::kAllowed);
1526 attestation_erasure = AttestationErasureOption::kIncludeAttestation;
1527 }
1528
1529 // The check for IsAttestationCertificateInappropriatelyIdentifying is
1530 // performed after the permissions prompt, even though we know the answer
1531 // before, because this still effectively discloses the make & model of
1532 // the authenticator: If an RP sees a "none" attestation from Chrome after
1533 // requesting direct attestation then it knows that it was one of the
1534 // tokens with inappropriate certs.
1535 if (response_data.IsAttestationCertificateInappropriatelyIdentifying() &&
1536 !request_delegate_->ShouldPermitIndividualAttestation(
1537 relying_party_id_)) {
1538 // The attestation response is incorrectly individually identifiable, but
1539 // the consent is for make & model information about a token, not for
1540 // individually-identifiable information. Erase the attestation to stop it
1541 // begin a tracking signal.
1542
1543 // The only way to get the underlying attestation will be to list the RP ID
1544 // in the enterprise policy, because that enables the individual attestation
1545 // bit in the register request and permits individual attestation generally.
1546 attestation_erasure = AttestationErasureOption::kEraseAttestationAndAaguid;
1547 }
1548
1549 InvokeCallbackAndCleanup(
1550 std::move(make_credential_response_callback_),
1551 blink::mojom::AuthenticatorStatus::SUCCESS,
1552 CreateMakeCredentialResponse(client_data_json_, std::move(response_data),
1553 attestation_erasure, requested_extensions_),
1554 Focus::kDoCheck);
1555 }
1556
OnSignResponse(device::GetAssertionStatus status_code,base::Optional<std::vector<device::AuthenticatorGetAssertionResponse>> response_data,const device::FidoAuthenticator * authenticator)1557 void AuthenticatorCommon::OnSignResponse(
1558 device::GetAssertionStatus status_code,
1559 base::Optional<std::vector<device::AuthenticatorGetAssertionResponse>>
1560 response_data,
1561 const device::FidoAuthenticator* authenticator) {
1562 DCHECK(!response_data || !response_data->empty()); // empty vector is invalid
1563
1564 if (!request_) {
1565 // Either the callback was called immediately and |request_| has not yet
1566 // been assigned (this is a bug), or a navigation caused the request to be
1567 // canceled while a callback was enqueued.
1568 return;
1569 }
1570
1571 switch (status_code) {
1572 case device::GetAssertionStatus::kUserConsentButCredentialNotRecognized:
1573 SignalFailureToRequestDelegate(
1574 authenticator,
1575 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1576 kKeyNotRegistered,
1577 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1578 return;
1579 case device::GetAssertionStatus::kAuthenticatorResponseInvalid:
1580 // The response from the authenticator was corrupted.
1581 InvokeCallbackAndCleanup(
1582 std::move(get_assertion_response_callback_),
1583 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1584 return;
1585 case device::GetAssertionStatus::kUserConsentDenied:
1586 SignalFailureToRequestDelegate(
1587 authenticator,
1588 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1589 kUserConsentDenied,
1590 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1591 return;
1592 case device::GetAssertionStatus::kSoftPINBlock:
1593 SignalFailureToRequestDelegate(
1594 authenticator,
1595 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1596 kSoftPINBlock,
1597 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1598 return;
1599 case device::GetAssertionStatus::kHardPINBlock:
1600 SignalFailureToRequestDelegate(
1601 authenticator,
1602 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1603 kHardPINBlock,
1604 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1605 return;
1606 case device::GetAssertionStatus::kAuthenticatorRemovedDuringPINEntry:
1607 SignalFailureToRequestDelegate(
1608 authenticator,
1609 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1610 kAuthenticatorRemovedDuringPINEntry,
1611 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1612 return;
1613 case device::GetAssertionStatus::kAuthenticatorMissingResidentKeys:
1614 SignalFailureToRequestDelegate(
1615 authenticator,
1616 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1617 kAuthenticatorMissingResidentKeys,
1618 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1619 return;
1620 case device::GetAssertionStatus::kAuthenticatorMissingUserVerification:
1621 SignalFailureToRequestDelegate(
1622 authenticator,
1623 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1624 kAuthenticatorMissingUserVerification,
1625 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1626 return;
1627 case device::GetAssertionStatus::kWinNotAllowedError:
1628 SignalFailureToRequestDelegate(
1629 authenticator,
1630 AuthenticatorRequestClientDelegate::InterestingFailureReason::
1631 kWinUserCancelled,
1632 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1633 return;
1634 case device::GetAssertionStatus::kSuccess:
1635 DCHECK(response_data.has_value());
1636 DCHECK(authenticator);
1637
1638 if (authenticator->AuthenticatorTransport()) {
1639 request_delegate_->UpdateLastTransportUsed(
1640 *authenticator->AuthenticatorTransport());
1641 }
1642
1643 // Show an account picker for requests with empty allow lists.
1644 // Authenticators may omit the identifying information in the user entity
1645 // if only one credential matches, or if they have account selection UI
1646 // built-in. In that case, consider that credential pre-selected.
1647 if (empty_allow_list_ &&
1648 (response_data->size() > 1 ||
1649 (response_data->at(0).user_entity() &&
1650 (response_data->at(0).user_entity()->name ||
1651 response_data->at(0).user_entity()->display_name)))) {
1652 request_delegate_->SelectAccount(
1653 std::move(*response_data),
1654 base::BindOnce(&AuthenticatorCommon::OnAccountSelected,
1655 weak_factory_.GetWeakPtr()));
1656 } else {
1657 OnAccountSelected(std::move(response_data->at(0)));
1658 }
1659 return;
1660 }
1661 NOTREACHED();
1662 }
1663
OnAccountSelected(device::AuthenticatorGetAssertionResponse response)1664 void AuthenticatorCommon::OnAccountSelected(
1665 device::AuthenticatorGetAssertionResponse response) {
1666 if (response.large_blob()) {
1667 std::vector<uint8_t> blob = std::move(*response.large_blob());
1668 data_decoder_.GzipUncompress(
1669 blob, base::BindOnce(&AuthenticatorCommon::OnLargeBlobUncompressed,
1670 weak_factory_.GetWeakPtr(), std::move(response)));
1671 return;
1672 }
1673 InvokeCallbackAndCleanup(
1674 std::move(get_assertion_response_callback_),
1675 blink::mojom::AuthenticatorStatus::SUCCESS,
1676 CreateGetAssertionResponse(client_data_json_, std::move(response),
1677 app_id_, requested_extensions_));
1678 return;
1679 }
1680
SignalFailureToRequestDelegate(const::device::FidoAuthenticator * authenticator,AuthenticatorRequestClientDelegate::InterestingFailureReason reason,blink::mojom::AuthenticatorStatus status)1681 void AuthenticatorCommon::SignalFailureToRequestDelegate(
1682 const ::device::FidoAuthenticator* authenticator,
1683 AuthenticatorRequestClientDelegate::InterestingFailureReason reason,
1684 blink::mojom::AuthenticatorStatus status) {
1685 error_awaiting_user_acknowledgement_ = status;
1686
1687 // The request has failed, but the UI may delay resolution of the request
1688 // callback and cleanup of the FidoRequestHandler and its associated
1689 // discoveries and authenticators. Tell them to stop processing the request in
1690 // the meantime.
1691 request_->StopDiscoveries();
1692 request_->CancelActiveAuthenticators();
1693
1694 // If WebAuthnUi is enabled, this error blocks until after receiving user
1695 // acknowledgement. Otherwise, the error is returned right away.
1696 if (request_delegate_->DoesBlockRequestOnFailure(reason)) {
1697 return;
1698 }
1699 CancelWithStatus(error_awaiting_user_acknowledgement_);
1700 } // namespace content
1701
1702 // TODO(crbug.com/814418): Add web tests to verify timeouts are
1703 // indistinguishable from NOT_ALLOWED_ERROR cases.
OnTimeout()1704 void AuthenticatorCommon::OnTimeout() {
1705 DCHECK(request_delegate_);
1706 if (awaiting_attestation_response_) {
1707 UMA_HISTOGRAM_ENUMERATION("WebAuthentication.AttestationPromptResult",
1708 AttestationPromptResult::kTimeout);
1709 awaiting_attestation_response_ = false;
1710 }
1711
1712 SignalFailureToRequestDelegate(
1713 /*authenticator=*/nullptr,
1714 AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout,
1715 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR);
1716 }
1717
CancelWithStatus(blink::mojom::AuthenticatorStatus status)1718 void AuthenticatorCommon::CancelWithStatus(
1719 blink::mojom::AuthenticatorStatus status) {
1720 // If response callback is invoked already, then ignore cancel request.
1721 if (!make_credential_response_callback_ && !get_assertion_response_callback_)
1722 return;
1723 if (make_credential_response_callback_) {
1724 InvokeCallbackAndCleanup(std::move(make_credential_response_callback_),
1725 status);
1726 } else if (get_assertion_response_callback_) {
1727 InvokeCallbackAndCleanup(std::move(get_assertion_response_callback_),
1728 status);
1729 }
1730 }
1731
OnCancelFromUI()1732 void AuthenticatorCommon::OnCancelFromUI() {
1733 CancelWithStatus(error_awaiting_user_acknowledgement_);
1734 }
1735
InvokeCallbackAndCleanup(blink::mojom::Authenticator::MakeCredentialCallback callback,blink::mojom::AuthenticatorStatus status,blink::mojom::MakeCredentialAuthenticatorResponsePtr response,Focus check_focus)1736 void AuthenticatorCommon::InvokeCallbackAndCleanup(
1737 blink::mojom::Authenticator::MakeCredentialCallback callback,
1738 blink::mojom::AuthenticatorStatus status,
1739 blink::mojom::MakeCredentialAuthenticatorResponsePtr response,
1740 Focus check_focus) {
1741 if (check_focus != Focus::kDontCheck && !(request_delegate_ && IsFocused())) {
1742 std::move(callback).Run(blink::mojom::AuthenticatorStatus::NOT_FOCUSED,
1743 nullptr);
1744 } else {
1745 std::move(callback).Run(status, std::move(response));
1746 }
1747
1748 Cleanup();
1749 }
1750
InvokeCallbackAndCleanup(blink::mojom::Authenticator::GetAssertionCallback callback,blink::mojom::AuthenticatorStatus status,blink::mojom::GetAssertionAuthenticatorResponsePtr response)1751 void AuthenticatorCommon::InvokeCallbackAndCleanup(
1752 blink::mojom::Authenticator::GetAssertionCallback callback,
1753 blink::mojom::AuthenticatorStatus status,
1754 blink::mojom::GetAssertionAuthenticatorResponsePtr response) {
1755 std::move(callback).Run(status, std::move(response));
1756 Cleanup();
1757 }
1758
Cleanup()1759 void AuthenticatorCommon::Cleanup() {
1760 if (awaiting_attestation_response_) {
1761 UMA_HISTOGRAM_ENUMERATION("WebAuthentication.AttestationPromptResult",
1762 AttestationPromptResult::kAbandoned);
1763 awaiting_attestation_response_ = false;
1764 }
1765
1766 timer_->Stop();
1767 request_.reset();
1768 discovery_factory_.reset();
1769 discovery_factory_testing_override_ = nullptr;
1770 ctap_make_credential_request_.reset();
1771 make_credential_options_.reset();
1772 ctap_get_assertion_request_.reset();
1773 ctap_get_assertion_options_.reset();
1774 request_delegate_.reset();
1775 make_credential_response_callback_.Reset();
1776 get_assertion_response_callback_.Reset();
1777 client_data_json_.clear();
1778 app_id_.reset();
1779 caller_origin_ = url::Origin();
1780 relying_party_id_.clear();
1781 empty_allow_list_ = false;
1782 error_awaiting_user_acknowledgement_ =
1783 blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
1784 requested_extensions_.clear();
1785 }
1786
DisableUI()1787 void AuthenticatorCommon::DisableUI() {
1788 disable_ui_ = true;
1789 }
1790
browser_context() const1791 BrowserContext* AuthenticatorCommon::browser_context() const {
1792 return content::WebContents::FromRenderFrameHost(render_frame_host_)
1793 ->GetBrowserContext();
1794 }
1795
discovery_factory()1796 device::FidoDiscoveryFactory* AuthenticatorCommon::discovery_factory() {
1797 DCHECK(discovery_factory_);
1798 return discovery_factory_testing_override_
1799 ? discovery_factory_testing_override_
1800 : discovery_factory_.get();
1801 }
1802
InitDiscoveryFactory()1803 void AuthenticatorCommon::InitDiscoveryFactory() {
1804 const bool is_u2f_api_request =
1805 WebAuthRequestSecurityChecker::OriginIsCryptoTokenExtension(
1806 caller_origin_);
1807 discovery_factory_ = MakeDiscoveryFactory(
1808 render_frame_host_, request_delegate_.get(), is_u2f_api_request);
1809 // TODO(martinkr): |discovery_factory_testing_override_| is a long-lived
1810 // VirtualFidoDeviceDiscovery so that tests can maintain and alter virtual
1811 // authenticator state in between requests. We should extract a longer-lived
1812 // configuration object from VirtualFidoDeviceDiscovery, so we can simply
1813 // stick a short-lived instance into |discovery_factory_| and eliminate
1814 // |discovery_factory_testing_override_|.
1815 discovery_factory_testing_override_ =
1816 AuthenticatorEnvironmentImpl::GetInstance()
1817 ->MaybeGetDiscoveryFactoryTestOverride();
1818 }
1819
1820 } // namespace content
1821