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