Commit 441a5eae authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: require that requests come from a frame in a focused window.

The spec suggests aborting an operation when focus[1] is lost, but
that's advice for site developers. I can't find anything in the spec
about preventing background tabs from triggering operations.

The cryptotoken extension refused to start a registration request, or to
send a registration response to, anything but the active tab in the
focused window. But background tabs could complete an authentication
request.

This change does something similar: it rejects both authentication and
registration requests unless the requesting frame is in a focused
window. It also performs that check before returning responses.

This is slightly different from the cryptotoken behaviour because
cryptotoken could only use what the extensions API exposed. For example,
if the omnibox was focused, cryptotoken would complete a registration
from the foreground tab but this code will reject it. I think this
behaviour is better, and it's certainly far more inline with the content
/ browser separation.

This change has been split from its tests, which will come in a future
CL.

[1] https://w3c.github.io/webauthn/#abortoperation

Bug: 827266
Change-Id: If6e97dd6526e175f40718724eda21e3efd434f7f
Reviewed-on: https://chromium-review.googlesource.com/991073
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550195}
parent 46275d9b
...@@ -4261,6 +4261,10 @@ bool ChromeContentBrowserClient:: ...@@ -4261,6 +4261,10 @@ bool ChromeContentBrowserClient::
return IsWebauthnRPIDListedInEnterprisePolicy(browser_context, rp_id); return IsWebauthnRPIDListedInEnterprisePolicy(browser_context, rp_id);
} }
bool ChromeContentBrowserClient::ShouldEnforceFocusChecksForWebauthn() {
return true;
}
void ChromeContentBrowserClient::ShouldReturnAttestationForWebauthnRPID( void ChromeContentBrowserClient::ShouldReturnAttestationForWebauthnRPID(
content::RenderFrameHost* rfh, content::RenderFrameHost* rfh,
const std::string& rp_id, const std::string& rp_id,
...@@ -4290,8 +4294,17 @@ void ChromeContentBrowserClient::ShouldReturnAttestationForWebauthnRPID( ...@@ -4290,8 +4294,17 @@ void ChromeContentBrowserClient::ShouldReturnAttestationForWebauthnRPID(
} }
// The created AttestationPermissionRequest deletes itself once complete. // The created AttestationPermissionRequest deletes itself once complete.
permission_request_manager->AddRequest( //
NewAttestationPermissionRequest(origin, std::move(callback))); // |callback| is called via the |MessageLoop| because otherwise the
// permissions bubble will have focus and |AuthenticatorImpl| checks that the
// frame still has focus before returning any results.
permission_request_manager->AddRequest(NewAttestationPermissionRequest(
origin, base::BindOnce(
[](base::OnceCallback<void(bool)> callback, bool result) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
},
std::move(callback))));
#endif #endif
} }
......
...@@ -403,6 +403,7 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { ...@@ -403,6 +403,7 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
content::BrowserContext* browser_context, content::BrowserContext* browser_context,
const GURL& url, const GURL& url,
base::OnceCallback<void(bool, int, int)> callback) override; base::OnceCallback<void(bool, int, int)> callback) override;
bool ShouldPermitIndividualAttestationForWebauthnRPID( bool ShouldPermitIndividualAttestationForWebauthnRPID(
content::BrowserContext* browser_context, content::BrowserContext* browser_context,
const std::string& rp_id) override; const std::string& rp_id) override;
...@@ -411,6 +412,8 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { ...@@ -411,6 +412,8 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
const std::string& rp_id, const std::string& rp_id,
const url::Origin& origin, const url::Origin& origin,
base::OnceCallback<void(bool)> callback) override; base::OnceCallback<void(bool)> callback) override;
bool ShouldEnforceFocusChecksForWebauthn() override;
std::unique_ptr<net::ClientCertStore> CreateClientCertStore( std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
content::ResourceContext* resource_context) override; content::ResourceContext* resource_context) override;
scoped_refptr<content::LoginDelegate> CreateLoginDelegate( scoped_refptr<content::LoginDelegate> CreateLoginDelegate(
......
...@@ -92,6 +92,7 @@ bool EnumTraits<password_manager::mojom::CredentialManagerError, ...@@ -92,6 +92,7 @@ bool EnumTraits<password_manager::mojom::CredentialManagerError,
case password_manager::mojom::CredentialManagerError::INVALID_DOMAIN: case password_manager::mojom::CredentialManagerError::INVALID_DOMAIN:
case password_manager::mojom::CredentialManagerError::INVALID_STATE: case password_manager::mojom::CredentialManagerError::INVALID_STATE:
case password_manager::mojom::CredentialManagerError::NOT_IMPLEMENTED: case password_manager::mojom::CredentialManagerError::NOT_IMPLEMENTED:
case password_manager::mojom::CredentialManagerError::NOT_FOCUSED:
case password_manager::mojom::CredentialManagerError::UNKNOWN: case password_manager::mojom::CredentialManagerError::UNKNOWN:
*output = password_manager::CredentialManagerError::UNKNOWN; *output = password_manager::CredentialManagerError::UNKNOWN;
return true; return true;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h" #include "content/public/common/content_client.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
...@@ -426,6 +427,13 @@ void AuthenticatorImpl::MakeCredential( ...@@ -426,6 +427,13 @@ void AuthenticatorImpl::MakeCredential(
return; return;
} }
if (GetContentClient()->browser()->ShouldEnforceFocusChecksForWebauthn() &&
!render_frame_host_->GetView()->HasFocus()) {
std::move(callback).Run(webauth::mojom::AuthenticatorStatus::NOT_FOCUSED,
nullptr);
return;
}
url::Origin caller_origin = render_frame_host_->GetLastCommittedOrigin(); url::Origin caller_origin = render_frame_host_->GetLastCommittedOrigin();
relying_party_id_ = options->relying_party->id; relying_party_id_ = options->relying_party->id;
...@@ -539,6 +547,13 @@ void AuthenticatorImpl::GetAssertion( ...@@ -539,6 +547,13 @@ void AuthenticatorImpl::GetAssertion(
return; return;
} }
if (GetContentClient()->browser()->ShouldEnforceFocusChecksForWebauthn() &&
!render_frame_host_->GetView()->HasFocus()) {
std::move(callback).Run(webauth::mojom::AuthenticatorStatus::NOT_FOCUSED,
nullptr);
return;
}
url::Origin caller_origin = render_frame_host_->GetLastCommittedOrigin(); url::Origin caller_origin = render_frame_host_->GetLastCommittedOrigin();
if (!HasValidEffectiveDomain(caller_origin)) { if (!HasValidEffectiveDomain(caller_origin)) {
...@@ -788,7 +803,14 @@ void AuthenticatorImpl::InvokeCallbackAndCleanup( ...@@ -788,7 +803,14 @@ void AuthenticatorImpl::InvokeCallbackAndCleanup(
MakeCredentialCallback callback, MakeCredentialCallback callback,
webauth::mojom::AuthenticatorStatus status, webauth::mojom::AuthenticatorStatus status,
webauth::mojom::MakeCredentialAuthenticatorResponsePtr response) { webauth::mojom::MakeCredentialAuthenticatorResponsePtr response) {
std::move(callback).Run(status, std::move(response)); if (GetContentClient()->browser()->ShouldEnforceFocusChecksForWebauthn() &&
!render_frame_host_->GetView()->HasFocus()) {
std::move(callback).Run(webauth::mojom::AuthenticatorStatus::NOT_FOCUSED,
nullptr);
} else {
std::move(callback).Run(status, std::move(response));
}
Cleanup(); Cleanup();
} }
...@@ -796,7 +818,14 @@ void AuthenticatorImpl::InvokeCallbackAndCleanup( ...@@ -796,7 +818,14 @@ void AuthenticatorImpl::InvokeCallbackAndCleanup(
GetAssertionCallback callback, GetAssertionCallback callback,
webauth::mojom::AuthenticatorStatus status, webauth::mojom::AuthenticatorStatus status,
webauth::mojom::GetAssertionAuthenticatorResponsePtr response) { webauth::mojom::GetAssertionAuthenticatorResponsePtr response) {
std::move(callback).Run(status, std::move(response)); if (GetContentClient()->browser()->ShouldEnforceFocusChecksForWebauthn() &&
!render_frame_host_->GetView()->HasFocus()) {
std::move(callback).Run(webauth::mojom::AuthenticatorStatus::NOT_FOCUSED,
nullptr);
} else {
std::move(callback).Run(status, std::move(response));
}
Cleanup(); Cleanup();
} }
......
...@@ -683,6 +683,10 @@ void ContentBrowserClient::ShouldReturnAttestationForWebauthnRPID( ...@@ -683,6 +683,10 @@ void ContentBrowserClient::ShouldReturnAttestationForWebauthnRPID(
std::move(callback).Run(true); std::move(callback).Run(true);
} }
bool ContentBrowserClient::ShouldEnforceFocusChecksForWebauthn() {
return false;
}
std::unique_ptr<net::ClientCertStore> std::unique_ptr<net::ClientCertStore>
ContentBrowserClient::CreateClientCertStore(ResourceContext* resource_context) { ContentBrowserClient::CreateClientCertStore(ResourceContext* resource_context) {
return nullptr; return nullptr;
......
...@@ -1120,6 +1120,11 @@ class CONTENT_EXPORT ContentBrowserClient { ...@@ -1120,6 +1120,11 @@ class CONTENT_EXPORT ContentBrowserClient {
const url::Origin& origin, const url::Origin& origin,
base::OnceCallback<void(bool)> callback); base::OnceCallback<void(bool)> callback);
// Returns true if the Webauthn implementation should require that the
// |RenderFrameHost|'s View has focus before processing requests, and before
// sending replies.
virtual bool ShouldEnforceFocusChecksForWebauthn();
// Get platform ClientCertStore. May return nullptr. // Get platform ClientCertStore. May return nullptr.
virtual std::unique_ptr<net::ClientCertStore> CreateClientCertStore( virtual std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
ResourceContext* resource_context); ResourceContext* resource_context);
......
...@@ -29,6 +29,7 @@ enum CredentialManagerError { ...@@ -29,6 +29,7 @@ enum CredentialManagerError {
INVALID_DOMAIN, INVALID_DOMAIN,
INVALID_STATE, INVALID_STATE,
NOT_IMPLEMENTED, NOT_IMPLEMENTED,
NOT_FOCUSED,
UNKNOWN UNKNOWN
}; };
......
...@@ -20,6 +20,7 @@ enum AuthenticatorStatus { ...@@ -20,6 +20,7 @@ enum AuthenticatorStatus {
INVALID_DOMAIN, INVALID_DOMAIN,
INVALID_STATE, INVALID_STATE,
NOT_IMPLEMENTED, NOT_IMPLEMENTED,
NOT_FOCUSED,
UNKNOWN_ERROR, UNKNOWN_ERROR,
}; };
......
...@@ -122,6 +122,8 @@ TypeConverter<CredentialManagerError, AuthenticatorStatus>::Convert( ...@@ -122,6 +122,8 @@ TypeConverter<CredentialManagerError, AuthenticatorStatus>::Convert(
return CredentialManagerError::INVALID_STATE; return CredentialManagerError::INVALID_STATE;
case webauth::mojom::blink::AuthenticatorStatus::NOT_IMPLEMENTED: case webauth::mojom::blink::AuthenticatorStatus::NOT_IMPLEMENTED:
return CredentialManagerError::NOT_IMPLEMENTED; return CredentialManagerError::NOT_IMPLEMENTED;
case webauth::mojom::blink::AuthenticatorStatus::NOT_FOCUSED:
return CredentialManagerError::NOT_FOCUSED;
case webauth::mojom::blink::AuthenticatorStatus::SUCCESS: case webauth::mojom::blink::AuthenticatorStatus::SUCCESS:
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -259,6 +259,10 @@ DOMException* CredentialManagerErrorToDOMException( ...@@ -259,6 +259,10 @@ DOMException* CredentialManagerErrorToDOMException(
"Attempting to register an already-registered key."); "Attempting to register an already-registered key.");
case CredentialManagerError::NOT_IMPLEMENTED: case CredentialManagerError::NOT_IMPLEMENTED:
return DOMException::Create(kNotSupportedError, "Not implemented"); return DOMException::Create(kNotSupportedError, "Not implemented");
case CredentialManagerError::NOT_FOCUSED:
return DOMException::Create(kNotAllowedError,
"The operation is not allowed at this time "
"because the page does not have focus.");
case CredentialManagerError::UNKNOWN: case CredentialManagerError::UNKNOWN:
return DOMException::Create(kNotReadableError, return DOMException::Create(kNotReadableError,
"An unknown error occurred while talking " "An unknown error occurred while talking "
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment