webauthn: prompt for attestation permission when needed.
This change implements a user consent prompt before returning
attestation information from a device. (Thus making webauthn act like
U2F currently does.) Unlike U2F, however, it is a fatal error if a user
denies consent, as required by the spec.
The attestation behavior is also affected by the
SecurityKeyPermitAttestation[1] enterprise policy. This list can contain
either U2F AppIDs (which are full URLs) or webauthn RP IDs (which are
domains). Its affect on attestation is detailed in the following table:
"attestation" value | RP ID not listed in policy | RP ID listed
--------------------+----------------------------+---------------------
"none" / not given | Empty, "none" attestation | Empty, "none"
| returned. | attesation returned.
--------------------+----------------------------+---------------------
"indirect"/"direct" | User prompted for consent. | Attestation from
| If granted, attestation | device is returned.
| from device is returned. |
| Otherwise a permission |
| error is generated. |
(The behavior of "indirect" attestation in webauthn may change in the
future but, for now, it is identical to "direct".)
[1] https://www.chromium.org/administrators/policy-list-3#SecurityKeyPermitAttestation
Bug: 803829,793985
Change-Id: I4e1d15a93ebc067869df7656016990b29fe12b59
Reviewed-on: https://chromium-review.googlesource.com/900452
Reviewed-by: Timothy Loh <[email protected]>
Reviewed-by: Nasko Oskov <[email protected]>
Reviewed-by: Balazs Engedy <[email protected]>
Reviewed-by: Kim Paulhamus <[email protected]>
Commit-Queue: Adam Langley <[email protected]>
Cr-Commit-Position: refs/heads/master@{#536206}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index befb6fb..f030f5e 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -63,7 +63,9 @@
#include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/payments/payment_request_display_manager_factory.h"
+#include "chrome/browser/permissions/attestation_permission_request.h"
#include "chrome/browser/permissions/permission_context_base.h"
+#include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/plugins/pdf_iframe_navigation_throttle.h"
#include "chrome/browser/prerender/prerender_final_status.h"
@@ -809,6 +811,25 @@
return prerender::PrerenderContents::FromWebContents(web_contents);
}
+// Returns true iff |rp_id| is listed in the SecurityKeyPermitAttestation
+// policy.
+bool IsWebauthnRPIDListedInEnterprisePolicy(
+ content::BrowserContext* browser_context,
+ const std::string& rp_id) {
+#if defined(OS_ANDROID)
+ return false;
+#else
+ const Profile* profile = Profile::FromBrowserContext(browser_context);
+ const PrefService* prefs = profile->GetPrefs();
+ const base::ListValue* permit_attestation =
+ prefs->GetList(prefs::kSecurityKeyPermitAttestation);
+
+ return std::any_of(
+ permit_attestation->begin(), permit_attestation->end(),
+ [&rp_id](const base::Value& v) { return v.GetString() == rp_id; });
+#endif
+}
+
} // namespace
ChromeContentBrowserClient::ChromeContentBrowserClient()
@@ -3914,18 +3935,42 @@
ShouldPermitIndividualAttestationForWebauthnRPID(
content::BrowserContext* browser_context,
const std::string& rp_id) {
-#if defined(OS_ANDROID)
- return false;
-#else
- const Profile* profile = Profile::FromBrowserContext(browser_context);
- const PrefService* prefs = profile->GetPrefs();
- const base::ListValue* permit_attestation =
- prefs->GetList(prefs::kSecurityKeyPermitAttestation);
+ // If the RP ID is listed in the policy, signal that individual attestation is
+ // permitted.
+ return IsWebauthnRPIDListedInEnterprisePolicy(browser_context, rp_id);
+}
- // If the RP ID is listed in the policy, enable individual attestation.
- return std::any_of(
- permit_attestation->begin(), permit_attestation->end(),
- [&rp_id](const base::Value& v) { return v.GetString() == rp_id; });
+void ChromeContentBrowserClient::ShouldReturnAttestationForWebauthnRPID(
+ content::RenderFrameHost* rfh,
+ const std::string& rp_id,
+ const url::Origin& origin,
+ base::OnceCallback<void(bool)> callback) {
+#if defined(OS_ANDROID)
+ // Android is expected to use platform APIs for webauthn which will take care
+ // of prompting.
+ std::move(callback).Run(true);
+#else
+ WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+
+ if (IsWebauthnRPIDListedInEnterprisePolicy(browser_context, rp_id)) {
+ std::move(callback).Run(true);
+ return;
+ }
+
+ // This does not use content::PermissionManager because that only works with
+ // content settings, while this permission is a non-persisted, per-attested-
+ // registration consent.
+ PermissionRequestManager* permission_request_manager =
+ PermissionRequestManager::FromWebContents(web_contents);
+ if (!permission_request_manager) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ // The created AttestationPermissionRequest deletes itself once complete.
+ permission_request_manager->AddRequest(
+ NewAttestationPermissionRequest(origin, std::move(callback)));
#endif
}