0

[webauthn] Fix PRF extension for multiple creds

Fix evaluating the WebAuthn PRF extension for credentials returned by
GetNextAssertion.

Fixed: 1520646
Change-Id: Iad653f0b84d47149b598efcbe78177eef9acceea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5239134
Auto-Submit: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: Adam Langley <agl@chromium.org>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1255690}
This commit is contained in:
Nina Satragno
2024-02-02 19:13:34 +00:00
committed by Chromium LUCI CQ
parent da56b501bf
commit 8d73f1974b
6 changed files with 266 additions and 143 deletions

@ -8182,6 +8182,99 @@ TEST_F(ResidentKeyAuthenticatorImplTest, PRFExtension) {
}
}
// Tests that the PRF function is evaluated for all credentials in an empty
// allow-list request. Regression test for crbug.com/1520646.
TEST_F(ResidentKeyAuthenticatorImplTest, PRFEvaluationForMultipleCreds) {
NavigateAndCommit(GURL(kTestOrigin1));
device::PublicKeyCredentialDescriptor cred1;
device::PublicKeyCredentialDescriptor cred2;
device::VirtualCtap2Device::Config config;
config.prf_support = false;
config.hmac_secret_support = true;
config.pin_support = true;
config.resident_key_support = true;
virtual_device_factory_->SetCtap2Config(config);
{
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->prf_enable = true;
options->authenticator_selection->resident_key =
device::ResidentKeyRequirement::kRequired;
options->user.id = {1};
options->user.name = "noah";
options->user.display_name = "Noah";
MakeCredentialResult result =
AuthenticatorMakeCredential(std::move(options));
EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
ASSERT_TRUE(result.response->echo_prf);
ASSERT_EQ(result.response->prf, true);
device::AuthenticatorData auth_data =
AuthDataFromMakeCredentialResponse(result.response);
cred1 = device::PublicKeyCredentialDescriptor(
device::CredentialType::kPublicKey, auth_data.GetCredentialId());
}
{
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->prf_enable = true;
options->authenticator_selection->resident_key =
device::ResidentKeyRequirement::kRequired;
options->user.id = {2};
options->user.name = "mio";
options->user.display_name = "Mio";
MakeCredentialResult result =
AuthenticatorMakeCredential(std::move(options));
EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
ASSERT_TRUE(result.response->echo_prf);
ASSERT_EQ(result.response->prf, true);
device::AuthenticatorData auth_data =
AuthDataFromMakeCredentialResponse(result.response);
cred2 = device::PublicKeyCredentialDescriptor(
device::CredentialType::kPublicKey, auth_data.GetCredentialId());
}
const std::vector<uint8_t> salt(32, 1);
std::vector<uint8_t> salt1_eval;
std::vector<uint8_t> salt2_eval;
{
PublicKeyCredentialRequestOptionsPtr options =
GetTestPublicKeyCredentialRequestOptions();
options->extensions->prf = true;
auto prf_value = blink::mojom::PRFValues::New();
prf_value->first = salt;
std::vector<blink::mojom::PRFValuesPtr> inputs;
inputs.emplace_back(std::move(prf_value));
options->extensions->prf_inputs = std::move(inputs);
options->allow_credentials.clear();
test_client_.delegate_config.expected_accounts = "01:noah:Noah/02:mio:Mio";
test_client_.delegate_config.selected_user_id = {1};
GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
ASSERT_TRUE(result.response->extensions->prf_results);
ASSERT_FALSE(result.response->extensions->prf_results->id);
salt1_eval = result.response->extensions->prf_results->first;
}
{
PublicKeyCredentialRequestOptionsPtr options =
GetTestPublicKeyCredentialRequestOptions();
options->extensions->prf = true;
auto prf_value = blink::mojom::PRFValues::New();
prf_value->first = salt;
std::vector<blink::mojom::PRFValuesPtr> inputs;
inputs.emplace_back(std::move(prf_value));
options->extensions->prf_inputs = std::move(inputs);
options->allow_credentials.clear();
test_client_.delegate_config.expected_accounts = "01:noah:Noah/02:mio:Mio";
test_client_.delegate_config.selected_user_id = {2};
GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
ASSERT_TRUE(result.response->extensions->prf_results);
ASSERT_FALSE(result.response->extensions->prf_results->id);
salt2_eval = result.response->extensions->prf_results->first;
}
EXPECT_NE(salt1_eval, salt2_eval);
}
TEST_F(ResidentKeyAuthenticatorImplTest, PRFEvaluationDuringMakeCredential) {
// The WebAuthn "prf" extension supports evaluating the PRF when making a
// credential. The hmac-secret extension does not support this, but hybrid

@ -121,7 +121,7 @@ void FidoDeviceAuthenticator::InitializeAuthenticatorDone(
void FidoDeviceAuthenticator::ExcludeAppIdCredentialsBeforeMakeCredential(
CtapMakeCredentialRequest request,
MakeCredentialOptions options,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<bool>)>
base::OnceCallback<void(CtapDeviceResponseCode, std::optional<bool>)>
callback) {
// If the device (or request) is U2F-only then |MakeCredential| will handle
// the AppID-excluded credentials, if any. There's no interaction with PUATs
@ -131,7 +131,7 @@ void FidoDeviceAuthenticator::ExcludeAppIdCredentialsBeforeMakeCredential(
// without a PUAT, so they aren't excluded here.)
if (!MakeCredentialTask::WillUseCTAP2(device_.get(), request, options) ||
device_->NoSilentRequests()) {
std::move(callback).Run(CtapDeviceResponseCode::kSuccess, absl::nullopt);
std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::nullopt);
return;
}
@ -140,7 +140,7 @@ void FidoDeviceAuthenticator::ExcludeAppIdCredentialsBeforeMakeCredential(
// Therefore appidExclude probing has to happen before the PUAT is obtained.
// For CTAP 2.0 devices we follow the same pattern, even though a PIN token
// doesn't have that issue.
RunTask<AppIdExcludeProbeTask, bool, CtapMakeCredentialRequest,
RunTask<AppIdExcludeProbeTask, std::optional<bool>, CtapMakeCredentialRequest,
MakeCredentialOptions>(std::move(request), std::move(options),
std::move(callback));
}
@ -161,7 +161,8 @@ void FidoDeviceAuthenticator::MakeCredential(
request.user_verification = UserVerificationRequirement::kDiscouraged;
}
RunTask<MakeCredentialTask, AuthenticatorMakeCredentialResponse,
RunTask<MakeCredentialTask,
std::optional<AuthenticatorMakeCredentialResponse>,
CtapMakeCredentialRequest, MakeCredentialOptions>(
std::move(request), std::move(request_options), std::move(callback));
}
@ -216,7 +217,7 @@ void FidoDeviceAuthenticator::OnHaveCompressedLargeBlobForGetAssertion(
// sent directly in the request. Otherwise it's saved in `large_blob_` to
// be written after the request, using the result of the `largeBlobKey`
// extension.
absl::optional<LargeBlob>* destination;
std::optional<LargeBlob>* destination;
if (options_.large_blob_type == LargeBlobSupportType::kExtension) {
destination = &request.large_blob_extension_write;
} else {
@ -251,7 +252,7 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetAssertion(
CtapGetAssertionOptions options,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
std::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, {});
return;
@ -280,49 +281,28 @@ void FidoDeviceAuthenticator::DoGetAssertion(CtapGetAssertionRequest request,
CtapGetAssertionRequest request_copy(request);
CtapGetAssertionOptions options_copy(options);
RunTask<GetAssertionTask, AuthenticatorGetAssertionResponse,
RunTask<GetAssertionTask, std::vector<AuthenticatorGetAssertionResponse>,
CtapGetAssertionRequest, CtapGetAssertionOptions>(
std::move(request), std::move(options),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveNextAssertion,
base::BindOnce(&FidoDeviceAuthenticator::OnHaveAssertion,
weak_factory_.GetWeakPtr(), std::move(request_copy),
std::move(options_copy),
std::vector<AuthenticatorGetAssertionResponse>{},
std::move(callback)));
std::move(options_copy), std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveNextAssertion(
void FidoDeviceAuthenticator::OnHaveAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<AuthenticatorGetAssertionResponse> response) {
std::vector<AuthenticatorGetAssertionResponse> responses) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, {});
return;
}
responses.emplace_back(std::move(*response));
uint8_t num_responses = responses.at(0).num_credentials.value_or(1u);
if (num_responses == 0 ||
(num_responses > 1 && !request.allow_list.empty())) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, {});
return;
}
if (responses.size() >= num_responses) {
PerformGetAssertionLargeBlobOperation(
std::move(request), std::move(options), std::move(responses),
std::move(callback));
return;
}
// Read the next response.
RunOperation<CtapGetNextAssertionRequest, AuthenticatorGetAssertionResponse>(
CtapGetNextAssertionRequest(),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveNextAssertion,
weak_factory_.GetWeakPtr(), std::move(request),
std::move(options), std::move(responses),
std::move(callback)),
base::BindOnce(&ReadCTAPGetAssertionResponse, device_->DeviceTransport()),
GetAssertionTask::StringFixupPredicate);
PerformGetAssertionLargeBlobOperation(std::move(request), std::move(options),
std::move(responses),
std::move(callback));
return;
}
void FidoDeviceAuthenticator::PerformGetAssertionLargeBlobOperation(
@ -406,7 +386,7 @@ void FidoDeviceAuthenticator::GetTouch(base::OnceClosure callback) {
base::BindOnce(
[](std::string authenticator_id, base::OnceClosure callback,
CtapDeviceResponseCode status,
absl::optional<pin::EmptyResponse> _) {
std::optional<pin::EmptyResponse> _) {
if (status == CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run();
return;
@ -423,7 +403,7 @@ void FidoDeviceAuthenticator::GetTouch(base::OnceClosure callback) {
base::BindOnce(
[](std::string authenticator_id, base::OnceCallback<void()> callback,
CtapDeviceResponseCode status,
absl::optional<AuthenticatorMakeCredentialResponse>) {
std::optional<AuthenticatorMakeCredentialResponse>) {
// If the device didn't understand/process the request it may
// fail immediately. Rather than count that as a touch, ignore
// those cases completely.
@ -466,7 +446,7 @@ void FidoDeviceAuthenticator::GetEphemeralKey(
void FidoDeviceAuthenticator::GetPINToken(
std::string pin,
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
std::optional<std::string> rp_id,
GetTokenCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported);
@ -484,12 +464,12 @@ void FidoDeviceAuthenticator::GetPINToken(
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken(
std::string pin,
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
std::optional<std::string> rp_id,
GetTokenCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
std::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}
@ -526,9 +506,9 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN(
std::string pin,
SetPINCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
std::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}
@ -554,9 +534,9 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN(
std::string new_pin,
SetPINCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
std::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}
@ -761,8 +741,7 @@ void FidoDeviceAuthenticator::OperationClearProxy(
template <typename Task, typename Response, typename... RequestArgs>
void FidoDeviceAuthenticator::RunTask(
RequestArgs&&... request_args,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<Response>)>
callback) {
base::OnceCallback<void(CtapDeviceResponseCode, Response)> callback) {
DCHECK(!task_);
DCHECK(!operation_);
DCHECK(initialized_);
@ -771,7 +750,7 @@ void FidoDeviceAuthenticator::RunTask(
device_.get(), std::forward<RequestArgs>(request_args)...,
base::BindOnce(
&FidoDeviceAuthenticator::TaskClearProxy<CtapDeviceResponseCode,
absl::optional<Response>>,
Response>,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
@ -780,10 +759,10 @@ void FidoDeviceAuthenticator::RunTask(
template <typename Request, typename Response>
void FidoDeviceAuthenticator::RunOperation(
Request request,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<Response>)>
base::OnceCallback<void(CtapDeviceResponseCode, std::optional<Response>)>
callback,
base::OnceCallback<
absl::optional<Response>(const absl::optional<cbor::Value>&)> parser,
std::optional<Response>(const absl::optional<cbor::Value>&)> parser,
bool (*string_fixup_predicate)(const std::vector<const cbor::Value*>&)) {
DCHECK(!task_);
DCHECK(!operation_);
@ -792,7 +771,7 @@ void FidoDeviceAuthenticator::RunOperation(
operation_ = std::make_unique<Ctap2DeviceOperation<Request, Response>>(
device_.get(), std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OperationClearProxy<
CtapDeviceResponseCode, absl::optional<Response>>,
CtapDeviceResponseCode, std::optional<Response>>,
weak_factory_.GetWeakPtr(), std::move(callback)),
std::move(parser), string_fixup_predicate);
operation_->Start();
@ -801,12 +780,12 @@ void FidoDeviceAuthenticator::RunOperation(
void FidoDeviceAuthenticator::OnEnumerateRPsDone(
EnumerateCredentialsState state,
CtapDeviceResponseCode status,
absl::optional<EnumerateRPsResponse> response) {
std::optional<EnumerateRPsResponse> response) {
DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size());
DCHECK_LE(state.rp_id_hashes.size(), state.rp_count);
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(state.callback).Run(status, absl::nullopt);
std::move(state.callback).Run(status, std::nullopt);
return;
}
if (state.rp_count == 0) {
@ -849,13 +828,13 @@ void FidoDeviceAuthenticator::OnEnumerateRPsDone(
void FidoDeviceAuthenticator::OnEnumerateCredentialsDone(
EnumerateCredentialsState state,
CtapDeviceResponseCode status,
absl::optional<EnumerateCredentialsResponse> response) {
std::optional<EnumerateCredentialsResponse> response) {
DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size());
DCHECK_EQ(state.rp_id_hashes.size(), state.rp_count);
DCHECK_LT(state.current_rp, state.rp_count);
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(state.callback).Run(status, absl::nullopt);
std::move(state.callback).Run(status, std::nullopt);
return;
}
@ -958,7 +937,7 @@ void FidoDeviceAuthenticator::GetSensorInfo(BioEnrollmentCallback callback) {
void FidoDeviceAuthenticator::BioEnrollFingerprint(
const pin::TokenResponse& pin_token,
absl::optional<std::vector<uint8_t>> template_id,
std::optional<std::vector<uint8_t>> template_id,
BioEnrollmentCallback callback) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
@ -1029,7 +1008,7 @@ void FidoDeviceAuthenticator::OnReadLargeBlobForGetAssertion(
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<std::vector<std::pair<LargeBlobKey, LargeBlob>>> blobs) {
std::optional<std::vector<std::pair<LargeBlobKey, LargeBlob>>> blobs) {
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "Reading large blob failed with code "
<< static_cast<int>(status);
@ -1127,7 +1106,7 @@ void FidoDeviceAuthenticator::GarbageCollectLargeBlob(
void FidoDeviceAuthenticator::FetchLargeBlobArray(
LargeBlobArrayReader large_blob_array_reader,
base::OnceCallback<void(CtapDeviceResponseCode,
absl::optional<LargeBlobArrayReader>)> callback) {
std::optional<LargeBlobArrayReader>)> callback) {
size_t bytes_to_read = max_large_blob_fragment_length();
LargeBlobsRequest request =
LargeBlobsRequest::ForRead(bytes_to_read, large_blob_array_reader.size());
@ -1143,11 +1122,11 @@ void FidoDeviceAuthenticator::OnReadLargeBlobFragment(
const size_t bytes_requested,
LargeBlobArrayReader large_blob_array_reader,
base::OnceCallback<void(CtapDeviceResponseCode,
absl::optional<LargeBlobArrayReader>)> callback,
std::optional<LargeBlobArrayReader>)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobsResponse> response) {
std::optional<LargeBlobsResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}
@ -1167,16 +1146,16 @@ void FidoDeviceAuthenticator::OnReadLargeBlobFragment(
void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite(
const LargeBlobKey& large_blob_key,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
const std::optional<pin::TokenResponse> pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
std::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
std::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
FIDO_LOG(ERROR) << "Large blob array corrupted. Replacing with a new one";
@ -1185,7 +1164,7 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite(
auto existing_large_blob = base::ranges::find_if(
*large_blob_array, [&large_blob_key](const cbor::Value& blob_cbor) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
return blob && blob->Decrypt(large_blob_key).has_value();
});
@ -1212,7 +1191,7 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite(
}
void FidoDeviceAuthenticator::WriteLargeBlobArray(
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
const std::optional<pin::TokenResponse> pin_uv_auth_token,
LargeBlobArrayWriter large_blob_array_writer,
base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
LargeBlobArrayFragment fragment =
@ -1235,10 +1214,10 @@ void FidoDeviceAuthenticator::WriteLargeBlobArray(
void FidoDeviceAuthenticator::OnWriteLargeBlobFragment(
LargeBlobArrayWriter large_blob_array_writer,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
const std::optional<pin::TokenResponse> pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobsResponse> response) {
std::optional<LargeBlobsResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
@ -1258,28 +1237,28 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead(
const std::vector<LargeBlobKey>& large_blob_keys,
LargeBlobReadCallback callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
std::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
std::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrIntegrityFailure,
absl::nullopt);
std::nullopt);
return;
}
std::vector<std::pair<LargeBlobKey, LargeBlob>> result;
for (const cbor::Value& blob_cbor : *large_blob_array) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
if (!blob.has_value()) {
continue;
}
for (const LargeBlobKey& key : large_blob_keys) {
absl::optional<LargeBlob> plaintext = blob->Decrypt(key);
std::optional<LargeBlob> plaintext = blob->Decrypt(key);
if (plaintext) {
result.emplace_back(key, std::move(*plaintext));
break;
@ -1294,7 +1273,7 @@ void FidoDeviceAuthenticator::OnCredentialsEnumeratedForGarbageCollect(
const pin::TokenResponse& pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<std::vector<AggregatedEnumerateCredentialsResponse>>
std::optional<std::vector<AggregatedEnumerateCredentialsResponse>>
credentials) {
if (status == CtapDeviceResponseCode::kCtap2ErrNoCredentials) {
credentials.emplace();
@ -1316,13 +1295,13 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForGarbageCollect(
const pin::TokenResponse& pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
std::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
std::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
FIDO_LOG(ERROR) << "Large blob array corrupted. Replacing with a new one";
@ -1341,7 +1320,7 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForGarbageCollect(
}
bool did_erase = base::EraseIf(
*large_blob_array, [&large_blob_keys](const cbor::Value& blob_cbor) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
return blob &&
base::ranges::none_of(
large_blob_keys,
@ -1365,7 +1344,7 @@ void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForGarbageCollect(
std::move(callback));
}
absl::optional<base::span<const int32_t>>
std::optional<base::span<const int32_t>>
FidoDeviceAuthenticator::GetAlgorithms() {
if (device_->supported_protocol() == ProtocolVersion::kU2f) {
static constexpr int32_t kU2fAlgorithms[1] = {
@ -1373,12 +1352,12 @@ FidoDeviceAuthenticator::GetAlgorithms() {
return kU2fAlgorithms;
}
const absl::optional<AuthenticatorGetInfoResponse>& get_info_response =
const std::optional<AuthenticatorGetInfoResponse>& get_info_response =
device_->device_info();
if (get_info_response) {
return get_info_response->algorithms;
}
return absl::nullopt;
return std::nullopt;
}
bool FidoDeviceAuthenticator::DiscoverableCredentialStorageFull() const {
@ -1425,7 +1404,7 @@ const AuthenticatorSupportedOptions& FidoDeviceAuthenticator::Options() const {
return options_;
}
absl::optional<FidoTransportProtocol>
std::optional<FidoTransportProtocol>
FidoDeviceAuthenticator::AuthenticatorTransport() const {
return device_->DeviceTransport();
}
@ -1454,7 +1433,7 @@ bool FidoDeviceAuthenticator::CanGetUvToken() {
void FidoDeviceAuthenticator::GetUvToken(
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
std::optional<std::string> rp_id,
GetTokenCallback callback) {
GetEphemeralKey(
base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken,
@ -1475,13 +1454,13 @@ bool FidoDeviceAuthenticator::ForcePINChange() {
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken(
absl::optional<std::string> rp_id,
std::optional<std::string> rp_id,
std::vector<pin::Permissions> permissions,
GetTokenCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
std::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
std::move(callback).Run(status, std::nullopt);
return;
}

@ -158,13 +158,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
void MaybeGetEphemeralKeyForGetAssertion(CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback);
void OnHaveNextAssertion(
void OnHaveAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<AuthenticatorGetAssertionResponse> response);
std::vector<AuthenticatorGetAssertionResponse> responses);
void PerformGetAssertionLargeBlobOperation(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
@ -276,9 +275,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
void OperationClearProxy(base::OnceCallback<void(Args...)> callback,
Args... args);
template <typename Task, typename Response, typename... RequestArgs>
void RunTask(RequestArgs&&... request_args,
base::OnceCallback<void(CtapDeviceResponseCode,
absl::optional<Response>)> callback);
void RunTask(
RequestArgs&&... request_args,
base::OnceCallback<void(CtapDeviceResponseCode, Response)> callback);
template <typename Request, typename Response>
void RunOperation(Request request,
base::OnceCallback<void(CtapDeviceResponseCode,

@ -268,45 +268,79 @@ void GetAssertionTask::HandleResponse(
return;
}
if (response_code == CtapDeviceResponseCode::kSuccess) {
if (response_data->user_selected && !allow_list.empty()) {
// The userSelected signal is only valid if the request had an empty
// allowList.
return LogAndFail(
"Assertion response has userSelected for non-empty allowList");
}
if (response_code != CtapDeviceResponseCode::kSuccess) {
std::move(callback_).Run(response_code, {});
return;
}
if (!SetResponseCredential(&response_data.value(), allow_list)) {
return LogAndFail(
"Assertion response has invalid credential information");
}
if (response_data->user_selected && !allow_list.empty()) {
// The userSelected signal is only valid if the request had an empty
// allowList.
return LogAndFail(
"Assertion response has userSelected for non-empty allowList");
}
// Extract any hmac-secret or prf response.
const absl::optional<cbor::Value>& extensions_cbor =
response_data->authenticator_data.extensions();
if (extensions_cbor) {
// Parsing has already checked that |extensions_cbor| is a map.
const cbor::Value::MapValue& extensions = extensions_cbor->GetMap();
auto it = extensions.find(cbor::Value(kExtensionHmacSecret));
if (it != extensions.end()) {
if (!hmac_secret_request_ || !it->second.is_bytestring()) {
return LogAndFail("Unexpected or invalid hmac-secret extension");
}
if (response_data->hmac_secret.has_value()) {
return LogAndFail(
"Assertion response has both hmac-secret and prf extensions");
}
absl::optional<std::vector<uint8_t>> plaintext =
hmac_secret_request_->Decrypt(it->second.GetBytestring());
if (!plaintext) {
return LogAndFail("Failed to decrypt hmac-secret extension");
}
response_data->hmac_secret = std::move(plaintext.value());
if (!SetResponseCredential(&response_data.value(), allow_list)) {
return LogAndFail("Assertion response has invalid credential information");
}
uint8_t num_responses = response_data->num_credentials.value_or(1u);
if (num_responses == 0 || (num_responses > 1 && !allow_list.empty())) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, {});
return;
}
HandleNextResponse(num_responses, response_code, std::move(response_data));
}
void GetAssertionTask::HandleNextResponse(
uint8_t num_responses,
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorGetAssertionResponse> response_data) {
if (response_code != CtapDeviceResponseCode::kSuccess) {
std::move(callback_).Run(response_code, {});
}
// Extract any hmac-secret or prf response.
const absl::optional<cbor::Value>& extensions_cbor =
response_data->authenticator_data.extensions();
if (extensions_cbor) {
// Parsing has already checked that |extensions_cbor| is a map.
const cbor::Value::MapValue& extensions = extensions_cbor->GetMap();
auto it = extensions.find(cbor::Value(kExtensionHmacSecret));
if (it != extensions.end()) {
if (!hmac_secret_request_ || !it->second.is_bytestring()) {
return LogAndFail("Unexpected or invalid hmac-secret extension");
}
if (response_data->hmac_secret.has_value()) {
return LogAndFail(
"Assertion response has both hmac-secret and prf extensions");
}
absl::optional<std::vector<uint8_t>> plaintext =
hmac_secret_request_->Decrypt(it->second.GetBytestring());
if (!plaintext) {
return LogAndFail("Failed to decrypt hmac-secret extension");
}
response_data->hmac_secret = std::move(plaintext.value());
}
}
std::move(callback_).Run(response_code, std::move(response_data));
responses_.emplace_back(std::move(*response_data));
if (responses_.size() < num_responses) {
// Read the next response.
next_assertion_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapGetNextAssertionRequest, AuthenticatorGetAssertionResponse>>(
device(), CtapGetNextAssertionRequest(),
base::BindOnce(&GetAssertionTask::HandleNextResponse,
weak_factory_.GetWeakPtr(), num_responses),
base::BindOnce(&ReadCTAPGetAssertionResponse,
device()->DeviceTransport()),
GetAssertionTask::StringFixupPredicate);
next_assertion_operation_->Start();
return;
}
std::move(callback_).Run(response_code, std::move(responses_));
}
void GetAssertionTask::HandleResponseToSilentRequest(
@ -384,8 +418,7 @@ void GetAssertionTask::HandleResponseToSilentRequest(
void GetAssertionTask::HandleDummyMakeCredentialComplete(
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorMakeCredentialResponse> response_data) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
absl::nullopt);
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, {});
}
void GetAssertionTask::MaybeSetPRFParameters(CtapGetAssertionRequest* request,
@ -414,13 +447,17 @@ void GetAssertionTask::MaybeRevertU2fFallbackAndInvokeCallback(
// in order to execute a sign command.
device()->set_supported_protocol(ProtocolVersion::kCtap2);
}
std::move(callback_).Run(status, std::move(response));
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback_).Run(status, {});
return;
}
responses_.emplace_back(std::move(*response));
std::move(callback_).Run(status, std::move(responses_));
}
void GetAssertionTask::LogAndFail(const char* error) {
FIDO_LOG(DEBUG) << error;
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
absl::nullopt);
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, {});
}
} // namespace device

@ -34,9 +34,12 @@ class AuthenticatorMakeCredentialResponse;
// https://fidoalliance.org/specs/fido-v2.0-rd-20161004/fido-client-to-authenticator-protocol-v2.0-rd-20161004.html#authenticatorgetassertion
class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask {
public:
using GetAssertionTaskCallback = base::OnceCallback<void(
CtapDeviceResponseCode,
absl::optional<AuthenticatorGetAssertionResponse>)>;
using GetAssertionTaskCallback =
base::OnceCallback<void(CtapDeviceResponseCode,
std::vector<AuthenticatorGetAssertionResponse>)>;
using GetNextAssertionOperation =
DeviceOperation<CtapGetNextAssertionRequest,
AuthenticatorGetAssertionResponse>;
using SignOperation = DeviceOperation<CtapGetAssertionRequest,
AuthenticatorGetAssertionResponse>;
using RegisterOperation =
@ -77,6 +80,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask {
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorGetAssertionResponse> response_data);
// HandleNextResponse processes an assertion and requests the next one if
// necessary.
void HandleNextResponse(
uint8_t num_responses,
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorGetAssertionResponse> response_data);
// HandleResponseToSilentRequest is a callback to a request without user-
// presence requested used to silently probe credentials from the allow list.
void HandleResponseToSilentRequest(
@ -103,10 +113,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask {
std::vector<std::vector<PublicKeyCredentialDescriptor>> allow_list_batches_;
size_t current_allow_list_batch_ = 0;
std::unique_ptr<GetNextAssertionOperation> next_assertion_operation_;
std::unique_ptr<SignOperation> sign_operation_;
std::unique_ptr<RegisterOperation> dummy_register_operation_;
GetAssertionTaskCallback callback_;
std::unique_ptr<pin::HMACSecretRequest> hmac_secret_request_;
std::vector<AuthenticatorGetAssertionResponse> responses_;
bool canceled_ = false;

@ -34,11 +34,11 @@ namespace {
using TestGetAssertionTaskCallbackReceiver =
::device::test::StatusAndValueCallbackReceiver<
CtapDeviceResponseCode,
absl::optional<AuthenticatorGetAssertionResponse>>;
std::vector<AuthenticatorGetAssertionResponse>>;
class FidoGetAssertionTaskTest : public testing::Test {
public:
FidoGetAssertionTaskTest() {}
FidoGetAssertionTaskTest() = default;
TestGetAssertionTaskCallbackReceiver& get_assertion_callback_receiver() {
return cb_;
@ -69,7 +69,7 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAssertionSuccess) {
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
EXPECT_TRUE(get_assertion_callback_receiver().value());
EXPECT_EQ(get_assertion_callback_receiver().value().size(), 1u);
}
TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) {
@ -92,7 +92,7 @@ TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) {
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
EXPECT_TRUE(get_assertion_callback_receiver().value());
EXPECT_EQ(get_assertion_callback_receiver().value().size(), 1u);
}
TEST_F(FidoGetAssertionTaskTest, TestSignSuccessWithFake) {
@ -122,16 +122,19 @@ TEST_F(FidoGetAssertionTaskTest, TestSignSuccessWithFake) {
ASSERT_GE(32u + 1u + 4u + 8u, // Minimal ECDSA signature is 8 bytes
get_assertion_callback_receiver()
.value()
->authenticator_data.SerializeToByteArray()
.at(0)
.authenticator_data.SerializeToByteArray()
.size());
EXPECT_EQ(0x01,
get_assertion_callback_receiver()
.value()
->authenticator_data.SerializeToByteArray()[32]); // UP flag
.at(0)
.authenticator_data.SerializeToByteArray()[32]); // UP flag
// Counter starts at zero and is incremented for every sign request.
EXPECT_EQ(1, get_assertion_callback_receiver()
.value()
->authenticator_data.SerializeToByteArray()[36]); // counter
.at(0)
.authenticator_data.SerializeToByteArray()[36]); // counter
}
TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) {
@ -148,7 +151,7 @@ TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) {
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
get_assertion_callback_receiver().status());
EXPECT_FALSE(get_assertion_callback_receiver().value());
EXPECT_TRUE(get_assertion_callback_receiver().value().empty());
}
TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) {
@ -168,7 +171,7 @@ TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) {
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
get_assertion_callback_receiver().status());
EXPECT_FALSE(get_assertion_callback_receiver().value());
EXPECT_TRUE(get_assertion_callback_receiver().value().empty());
}
// Checks that when device supports both CTAP2 and U2F protocol and when