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