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
 }