Commit e2c6c8b6 authored by Kim Paulhamus's avatar Kim Paulhamus Committed by Commit Bot

Disable resident key credential creation and usage.

Prevents create() calls where requireResidentKey = true.

Also disables resident credentials in getAssertion calls entirely until
they can be fully and properly supported. (Previously, if an empty list
was passed to getAssertion() and resident credentials for
that RP existed, then a random single credential would be passed back.)


Bug: 896404
Change-Id: Ic6efcb99891825f929efba616ff5cf5788236a7d
Reviewed-on: https://chromium-review.googlesource.com/c/1287089
Commit-Queue: Kim Paulhamus <kpaulhamus@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#600858}
parent 29a5178c
...@@ -95,6 +95,7 @@ bool EnumTraits<blink::mojom::CredentialManagerError, ...@@ -95,6 +95,7 @@ bool EnumTraits<blink::mojom::CredentialManagerError,
case blink::mojom::CredentialManagerError::CREDENTIAL_NOT_RECOGNIZED: case blink::mojom::CredentialManagerError::CREDENTIAL_NOT_RECOGNIZED:
case blink::mojom::CredentialManagerError::NOT_IMPLEMENTED: case blink::mojom::CredentialManagerError::NOT_IMPLEMENTED:
case blink::mojom::CredentialManagerError::NOT_FOCUSED: case blink::mojom::CredentialManagerError::NOT_FOCUSED:
case blink::mojom::CredentialManagerError::RESIDENT_CREDENTIALS_UNSUPPORTED:
case blink::mojom::CredentialManagerError::UNKNOWN: case blink::mojom::CredentialManagerError::UNKNOWN:
*output = password_manager::CredentialManagerError::UNKNOWN; *output = password_manager::CredentialManagerError::UNKNOWN;
return true; return true;
......
...@@ -583,6 +583,16 @@ void AuthenticatorImpl::MakeCredential( ...@@ -583,6 +583,16 @@ void AuthenticatorImpl::MakeCredential(
return; return;
} }
if (options->authenticator_selection &&
options->authenticator_selection->require_resident_key) {
// Disallow the creation of resident credentials.
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED,
nullptr, Focus::kDontCheck);
return;
}
DCHECK(make_credential_response_callback_.is_null()); DCHECK(make_credential_response_callback_.is_null());
make_credential_response_callback_ = std::move(callback); make_credential_response_callback_ = std::move(callback);
...@@ -676,6 +686,15 @@ void AuthenticatorImpl::GetAssertion( ...@@ -676,6 +686,15 @@ void AuthenticatorImpl::GetAssertion(
return; return;
} }
if (options->allow_credentials.empty()) {
// Chrome currently does not support any resident keys.
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED,
nullptr);
return;
}
if (options->appid) { if (options->appid) {
alternative_application_parameter_ = alternative_application_parameter_ =
ProcessAppIdExtension(*options->appid, caller_origin); ProcessAppIdExtension(*options->appid, caller_origin);
......
...@@ -527,8 +527,8 @@ TEST_F(AuthenticatorImplTest, MakeCredentialUserVerification) { ...@@ -527,8 +527,8 @@ TEST_F(AuthenticatorImplTest, MakeCredentialUserVerification) {
EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status()); EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
} }
// Test that MakeCredential request times out with NOT_ALLOWED_ERROR if resident // Test that MakeCredential request returns if resident
// key is requested for U2F devices on create(). // key is requested on create().
TEST_F(AuthenticatorImplTest, MakeCredentialResidentKey) { TEST_F(AuthenticatorImplTest, MakeCredentialResidentKey) {
SimulateNavigation(GURL(kTestOrigin1)); SimulateNavigation(GURL(kTestOrigin1));
device::test::ScopedVirtualFidoDevice scoped_virtual_device; device::test::ScopedVirtualFidoDevice scoped_virtual_device;
...@@ -547,7 +547,10 @@ TEST_F(AuthenticatorImplTest, MakeCredentialResidentKey) { ...@@ -547,7 +547,10 @@ TEST_F(AuthenticatorImplTest, MakeCredentialResidentKey) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1)); task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1));
callback_receiver.WaitForCallback(); callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status()); EXPECT_EQ(AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED,
callback_receiver.status());
// TODO add CTAP device
} }
// Test that MakeCredential request times out with NOT_ALLOWED_ERROR if a // Test that MakeCredential request times out with NOT_ALLOWED_ERROR if a
...@@ -932,7 +935,7 @@ TEST_F(AuthenticatorImplTest, GetAssertionWithEmptyAllowCredentials) { ...@@ -932,7 +935,7 @@ TEST_F(AuthenticatorImplTest, GetAssertionWithEmptyAllowCredentials) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1)); task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1));
callback_receiver.WaitForCallback(); callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED, EXPECT_EQ(AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED,
callback_receiver.status()); callback_receiver.status());
} }
......
...@@ -67,9 +67,9 @@ constexpr char kTimeoutErrorMessage[] = ...@@ -67,9 +67,9 @@ constexpr char kTimeoutErrorMessage[] =
"webauth: NotAllowedError: The operation either timed out or was not " "webauth: NotAllowedError: The operation either timed out or was not "
"allowed. See: https://w3c.github.io/webauthn/#sec-assertion-privacy."; "allowed. See: https://w3c.github.io/webauthn/#sec-assertion-privacy.";
constexpr char kInvalidStateErrorMessage[] = constexpr char kResidentCredentialsErrorMessage[] =
"webauth: InvalidStateError: The user attempted to use an authenticator " "webauth: NotSupportedError: Resident credentials or empty "
"that recognized none of the provided credentials."; "'allowCredentials' lists are not supported at this time.";
constexpr char kRelyingPartySecurityErrorMessage[] = constexpr char kRelyingPartySecurityErrorMessage[] =
"webauth: SecurityError: The relying party ID 'localhost' is not a " "webauth: SecurityError: The relying party ID 'localhost' is not a "
...@@ -736,7 +736,7 @@ IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, ...@@ -736,7 +736,7 @@ IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
shell()->web_contents()->GetMainFrame(), shell()->web_contents()->GetMainFrame(),
BuildCreateCallWithParameters(parameters), &result)); BuildCreateCallWithParameters(parameters), &result));
ASSERT_EQ(kTimeoutErrorMessage, result); ASSERT_EQ(kResidentCredentialsErrorMessage, result);
} }
} }
...@@ -807,7 +807,7 @@ IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, ...@@ -807,7 +807,7 @@ IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
ASSERT_TRUE(content::ExecuteScriptAndExtractString( ASSERT_TRUE(content::ExecuteScriptAndExtractString(
shell()->web_contents()->GetMainFrame(), shell()->web_contents()->GetMainFrame(),
BuildGetCallWithParameters(parameters), &result)); BuildGetCallWithParameters(parameters), &result));
ASSERT_EQ(kInvalidStateErrorMessage, result); ASSERT_EQ(kResidentCredentialsErrorMessage, result);
} }
// WebAuthBrowserBleDisabledTest // WebAuthBrowserBleDisabledTest
......
...@@ -41,6 +41,13 @@ promise_test(async t => { ...@@ -41,6 +41,13 @@ promise_test(async t => {
navigator.credentials.create({ publicKey : customMakeCredOptions})); navigator.credentials.create({ publicKey : customMakeCredOptions}));
}, "navigator.credentials.create() with empty rpId returns SecurityError"); }, "navigator.credentials.create() with empty rpId returns SecurityError");
promise_test(async t => {
var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
return promise_rejects(t, "NotSupportedError",
navigator.credentials.create({ publicKey : customMakeCredOptions}));
}, "navigator.credentials.create() with resident keys is disabled");
promise_test(t => { promise_test(t => {
return navigator.credentials.test.clearAuthenticators(); return navigator.credentials.test.clearAuthenticators();
}, "Clean up testing environment."); }, "Clean up testing environment.");
......
...@@ -120,9 +120,9 @@ promise_test(async t => { ...@@ -120,9 +120,9 @@ promise_test(async t => {
someOtherCredential.id = new TextEncoder().encode("someOtherCredential"); someOtherCredential.id = new TextEncoder().encode("someOtherCredential");
delete customGetAssertionOptions.allowCredentials; delete customGetAssertionOptions.allowCredentials;
return promise_rejects(t, "InvalidStateError", return promise_rejects(t, "NotSupportedError",
navigator.credentials.get({ publicKey : customGetAssertionOptions})); navigator.credentials.get({ publicKey : customGetAssertionOptions}));
}, "navigator.credentials.get() with empty allowCredentials returns InvalidStateError if the user has provided user presence"); }, "navigator.credentials.get() with empty allowCredentials is disabled");
promise_test(t => { promise_test(t => {
return navigator.credentials.test.clearAuthenticators(); return navigator.credentials.test.clearAuthenticators();
......
...@@ -43,9 +43,9 @@ promise_test(async t => { ...@@ -43,9 +43,9 @@ promise_test(async t => {
someOtherCredential.id = new TextEncoder().encode("someOtherCredential"); someOtherCredential.id = new TextEncoder().encode("someOtherCredential");
delete customGetAssertionOptions.allowCredentials; delete customGetAssertionOptions.allowCredentials;
return promise_rejects(t, "InvalidStateError", return promise_rejects(t, "NotSupportedError",
navigator.credentials.get({ publicKey : customGetAssertionOptions})); navigator.credentials.get({ publicKey : customGetAssertionOptions}));
}, "navigator.credentials.get() with empty allowCredentials returns InvalidStateError if the user has provided user presence"); }, "navigator.credentials.get() with empty allowCredentials returns NotSupportedError");
promise_test(t => { promise_test(t => {
return navigator.credentials.test.clearAuthenticators(); return navigator.credentials.test.clearAuthenticators();
......
...@@ -30,6 +30,7 @@ enum CredentialManagerError { ...@@ -30,6 +30,7 @@ enum CredentialManagerError {
CREDENTIAL_NOT_RECOGNIZED, CREDENTIAL_NOT_RECOGNIZED,
NOT_IMPLEMENTED, NOT_IMPLEMENTED,
NOT_FOCUSED, NOT_FOCUSED,
RESIDENT_CREDENTIALS_UNSUPPORTED,
ANDROID_ALGORITHM_UNSUPPORTED, ANDROID_ALGORITHM_UNSUPPORTED,
ANDROID_EMPTY_ALLOW_CREDENTIALS, ANDROID_EMPTY_ALLOW_CREDENTIALS,
ANDROID_NOT_SUPPORTED_ERROR, ANDROID_NOT_SUPPORTED_ERROR,
......
...@@ -21,6 +21,7 @@ enum AuthenticatorStatus { ...@@ -21,6 +21,7 @@ enum AuthenticatorStatus {
CREDENTIAL_NOT_RECOGNIZED, CREDENTIAL_NOT_RECOGNIZED,
NOT_IMPLEMENTED, NOT_IMPLEMENTED,
NOT_FOCUSED, NOT_FOCUSED,
RESIDENT_CREDENTIALS_UNSUPPORTED,
USER_VERIFICATION_UNSUPPORTED, USER_VERIFICATION_UNSUPPORTED,
ALGORITHM_UNSUPPORTED, ALGORITHM_UNSUPPORTED,
EMPTY_ALLOW_CREDENTIALS, EMPTY_ALLOW_CREDENTIALS,
......
...@@ -130,6 +130,9 @@ TypeConverter<CredentialManagerError, AuthenticatorStatus>::Convert( ...@@ -130,6 +130,9 @@ TypeConverter<CredentialManagerError, AuthenticatorStatus>::Convert(
return CredentialManagerError::NOT_IMPLEMENTED; return CredentialManagerError::NOT_IMPLEMENTED;
case blink::mojom::blink::AuthenticatorStatus::NOT_FOCUSED: case blink::mojom::blink::AuthenticatorStatus::NOT_FOCUSED:
return CredentialManagerError::NOT_FOCUSED; return CredentialManagerError::NOT_FOCUSED;
case blink::mojom::blink::AuthenticatorStatus::
RESIDENT_CREDENTIALS_UNSUPPORTED:
return CredentialManagerError::RESIDENT_CREDENTIALS_UNSUPPORTED;
case blink::mojom::blink::AuthenticatorStatus::ALGORITHM_UNSUPPORTED: case blink::mojom::blink::AuthenticatorStatus::ALGORITHM_UNSUPPORTED:
return CredentialManagerError::ANDROID_ALGORITHM_UNSUPPORTED; return CredentialManagerError::ANDROID_ALGORITHM_UNSUPPORTED;
case blink::mojom::blink::AuthenticatorStatus::EMPTY_ALLOW_CREDENTIALS: case blink::mojom::blink::AuthenticatorStatus::EMPTY_ALLOW_CREDENTIALS:
......
...@@ -241,6 +241,11 @@ DOMException* CredentialManagerErrorToDOMException( ...@@ -241,6 +241,11 @@ DOMException* CredentialManagerErrorToDOMException(
return DOMException::Create(DOMExceptionCode::kNotAllowedError, return DOMException::Create(DOMExceptionCode::kNotAllowedError,
"The operation is not allowed at this time " "The operation is not allowed at this time "
"because the page does not have focus."); "because the page does not have focus.");
case CredentialManagerError::RESIDENT_CREDENTIALS_UNSUPPORTED:
return DOMException::Create(DOMExceptionCode::kNotSupportedError,
"Resident credentials or empty "
"'allowCredentials' lists are not supported "
"at this time.");
case CredentialManagerError::ANDROID_ALGORITHM_UNSUPPORTED: case CredentialManagerError::ANDROID_ALGORITHM_UNSUPPORTED:
return DOMException::Create(DOMExceptionCode::kNotSupportedError, return DOMException::Create(DOMExceptionCode::kNotSupportedError,
"None of the algorithms specified in " "None of the algorithms specified in "
......
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