Commit 3106cc62 authored by Jun Choi's avatar Jun Choi Committed by Commit Bot

Implement sign for VirtualCtap2Device

Implement GetAssertion operation for CTAP2 software key. The implement
GetAssertionOperation does not support multiple accounts per device and
never returns UserEntity as a response.

Bug: 829413
Change-Id: I58a43a032fc520494ea8fdbfb1c64959a04c6b82
Reviewed-on: https://chromium-review.googlesource.com/1117369
Commit-Queue: Jun Choi <hongjunchoi@chromium.org>
Reviewed-by: default avatarKim Paulhamus <kpaulhamus@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572296}
parent 336a7d28
...@@ -6,12 +6,14 @@ ...@@ -6,12 +6,14 @@
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "crypto/ec_private_key.h"
#include "device/base/features.h" #include "device/base/features.h"
#include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/ctap_get_assertion_request.h" #include "device/fido/ctap_get_assertion_request.h"
...@@ -20,6 +22,7 @@ ...@@ -20,6 +22,7 @@
#include "device/fido/fido_test_data.h" #include "device/fido/fido_test_data.h"
#include "device/fido/mock_fido_device.h" #include "device/fido/mock_fido_device.h"
#include "device/fido/test_callback_receiver.h" #include "device/fido/test_callback_receiver.h"
#include "device/fido/virtual_ctap2_device.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -113,6 +116,51 @@ TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) { ...@@ -113,6 +116,51 @@ TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) {
EXPECT_FALSE(device->device_info()); EXPECT_FALSE(device->device_info());
} }
TEST_F(FidoGetAssertionTaskTest, TestSignSuccessWithFake) {
auto private_key = crypto::ECPrivateKey::Create();
std::string public_key;
private_key->ExportRawPublicKey(&public_key);
auto hash = fido_parsing_utils::CreateSHA256Hash(public_key);
std::vector<uint8_t> key_handle(hash.begin(), hash.end());
CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request_param.SetAllowList({{CredentialType::kPublicKey, key_handle}});
auto device = std::make_unique<VirtualCtap2Device>();
device->mutable_state()->registrations.emplace(
key_handle,
VirtualFidoDevice::RegistrationData(
std::move(private_key),
fido_parsing_utils::CreateSHA256Hash(test_data::kRelyingPartyId),
42 /* counter */));
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request_param),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
// Just a sanity check, we don't verify the actual signature.
ASSERT_GE(32u + 1u + 4u + 8u, // Minimal ECDSA signature is 8 bytes
get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()
.size());
EXPECT_EQ(0x01,
get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()[32]); // UP flag
// Counter is incremented for every sign request.
EXPECT_EQ(43, get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()[36]); // counter
}
TEST_F(FidoGetAssertionTaskTest, TestU2fSignWithoutFlag) { TEST_F(FidoGetAssertionTaskTest, TestU2fSignWithoutFlag) {
RemoveCtapFlag(); RemoveCtapFlag();
auto device = std::make_unique<MockFidoDevice>(); auto device = std::make_unique<MockFidoDevice>();
......
...@@ -9,11 +9,14 @@ ...@@ -9,11 +9,14 @@
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "components/cbor/cbor_writer.h" #include "components/cbor/cbor_writer.h"
#include "crypto/ec_private_key.h" #include "crypto/ec_private_key.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_make_credential_response.h" #include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h" #include "device/fido/ctap_make_credential_request.h"
#include "device/fido/ec_public_key.h" #include "device/fido/ec_public_key.h"
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
...@@ -57,6 +60,18 @@ bool AreMakeCredentialOptionsValid(const AuthenticatorSupportedOptions& options, ...@@ -57,6 +60,18 @@ bool AreMakeCredentialOptionsValid(const AuthenticatorSupportedOptions& options,
kSupportedAndConfigured; kSupportedAndConfigured;
} }
bool AreGetAssertionOptionsValid(const AuthenticatorSupportedOptions& options,
const CtapGetAssertionRequest& request) {
if (request.user_presence_required() && !options.user_presence_required())
return false;
return request.user_verification() !=
UserVerificationRequirement::kRequired ||
options.user_verification_availability() ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured;
}
// Checks that whether the received MakeCredential request includes EA256 // Checks that whether the received MakeCredential request includes EA256
// algorithm in publicKeyCredParam. // algorithm in publicKeyCredParam.
bool AreMakeCredentialParamsValid(const CtapMakeCredentialRequest& request) { bool AreMakeCredentialParamsValid(const CtapMakeCredentialRequest& request) {
...@@ -113,6 +128,20 @@ std::vector<uint8_t> ConstructMakeCredentialResponse( ...@@ -113,6 +128,20 @@ std::vector<uint8_t> ConstructMakeCredentialResponse(
return GetSerializedCtapDeviceResponse(make_credential_response); return GetSerializedCtapDeviceResponse(make_credential_response);
} }
std::vector<uint8_t> ConstructGetAssertionResponse(
AuthenticatorData authenticator_data,
base::span<const uint8_t> signature,
base::span<const uint8_t> key_handle) {
AuthenticatorGetAssertionResponse response(
std::move(authenticator_data),
fido_parsing_utils::Materialize(signature));
response.SetCredential({CredentialType::kPublicKey,
fido_parsing_utils::Materialize(key_handle)});
response.SetNumCredentials(1);
return GetSerializedCtapDeviceResponse(response);
}
} // namespace } // namespace
VirtualCtap2Device::VirtualCtap2Device() VirtualCtap2Device::VirtualCtap2Device()
...@@ -158,6 +187,9 @@ void VirtualCtap2Device::DeviceTransact(std::vector<uint8_t> command, ...@@ -158,6 +187,9 @@ void VirtualCtap2Device::DeviceTransact(std::vector<uint8_t> command,
case CtapRequestCommand::kAuthenticatorMakeCredential: case CtapRequestCommand::kAuthenticatorMakeCredential:
response_code = OnMakeCredential(request_bytes, &response_data); response_code = OnMakeCredential(request_bytes, &response_data);
break; break;
case CtapRequestCommand::kAuthenticatorGetAssertion:
response_code = OnGetAssertion(request_bytes, &response_data);
break;
default: default:
break; break;
} }
...@@ -180,17 +212,23 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( ...@@ -180,17 +212,23 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
base::span<const uint8_t> request_bytes, base::span<const uint8_t> request_bytes,
std::vector<uint8_t>* response) { std::vector<uint8_t>* response) {
auto request = ParseCtapMakeCredentialRequest(request_bytes); auto request = ParseCtapMakeCredentialRequest(request_bytes);
if (!request) if (!request) {
DLOG(ERROR) << "Incorrectly formatted MakeCredential request.";
return CtapDeviceResponseCode::kCtap2ErrOther; return CtapDeviceResponseCode::kCtap2ErrOther;
}
if (!AreMakeCredentialOptionsValid(device_info_.options(), *request) || if (!AreMakeCredentialOptionsValid(device_info_.options(), *request) ||
!AreMakeCredentialParamsValid(*request)) { !AreMakeCredentialParamsValid(*request)) {
DLOG(ERROR) << "Virtual CTAP2 device does not support options required by "
"the request.";
return CtapDeviceResponseCode::kCtap2ErrOther; return CtapDeviceResponseCode::kCtap2ErrOther;
} }
// Client pin is not supported. // Client pin is not supported.
if (request->pin_auth()) if (request->pin_auth()) {
DLOG(ERROR) << "Virtual CTAP2 device does not support client pin.";
return CtapDeviceResponseCode::kCtap2ErrPinInvalid; return CtapDeviceResponseCode::kCtap2ErrPinInvalid;
}
// Check for already registered credentials. // Check for already registered credentials.
const auto rp_id_hash = const auto rp_id_hash =
...@@ -219,7 +257,7 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( ...@@ -219,7 +257,7 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
ConstructECPublicKey(public_key)); ConstructECPublicKey(public_key));
auto authenticator_data = ConstructAuthenticatorData( auto authenticator_data = ConstructAuthenticatorData(
rp_id_hash, std::move(attested_credential_data)); rp_id_hash, 01ul, std::move(attested_credential_data));
auto sign_buffer = auto sign_buffer =
ConstructSignatureBuffer(authenticator_data, request->client_data_hash()); ConstructSignatureBuffer(authenticator_data, request->client_data_hash());
...@@ -234,8 +272,10 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( ...@@ -234,8 +272,10 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
auto attestation_cert = GenerateAttestationCertificate( auto attestation_cert = GenerateAttestationCertificate(
false /* individual_attestation_requested */); false /* individual_attestation_requested */);
if (!attestation_cert) if (!attestation_cert) {
DLOG(ERROR) << "Failed to generate attestation certificate.";
return CtapDeviceResponseCode::kCtap2ErrOther; return CtapDeviceResponseCode::kCtap2ErrOther;
}
*response = ConstructMakeCredentialResponse(std::move(*attestation_cert), sig, *response = ConstructMakeCredentialResponse(std::move(*attestation_cert), sig,
std::move(authenticator_data)); std::move(authenticator_data));
...@@ -244,6 +284,66 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( ...@@ -244,6 +284,66 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
return CtapDeviceResponseCode::kSuccess; return CtapDeviceResponseCode::kSuccess;
} }
CtapDeviceResponseCode VirtualCtap2Device::OnGetAssertion(
base::span<const uint8_t> request_bytes,
std::vector<uint8_t>* response) {
auto request = ParseCtapGetAssertionRequest(request_bytes);
if (!request) {
DLOG(ERROR) << "Incorrectly formatted GetAssertion request.";
return CtapDeviceResponseCode::kCtap2ErrOther;
}
// Resident keys are not supported.
if (!request->allow_list() || request->allow_list()->empty()) {
DLOG(ERROR) << "Allowed credential list is empty, but Virtual CTAP2 device "
"does not support resident keys.";
return CtapDeviceResponseCode::kCtap2ErrNoCredentials;
}
// Client pin option is not supported.
if (request->pin_auth()) {
DLOG(ERROR) << "Virtual CTAP2 device does not support client pin.";
return CtapDeviceResponseCode::kCtap2ErrOther;
}
if (!AreGetAssertionOptionsValid(device_info_.options(), *request)) {
DLOG(ERROR) << "Unsupported options required from the request.";
return CtapDeviceResponseCode::kCtap2ErrOther;
}
const auto rp_id_hash =
fido_parsing_utils::CreateSHA256Hash(request->rp_id());
RegistrationData* found_data = nullptr;
base::span<const uint8_t> credential_id;
for (const auto& allowed_credential : *request->allow_list()) {
if ((found_data =
FindRegistrationData(allowed_credential.id(), rp_id_hash))) {
credential_id = allowed_credential.id();
break;
}
}
if (!found_data)
return CtapDeviceResponseCode::kCtap2ErrNoCredentials;
found_data->counter++;
auto authenticator_data =
ConstructAuthenticatorData(rp_id_hash, found_data->counter);
auto signature_buffer =
ConstructSignatureBuffer(authenticator_data, request->client_data_hash());
// Sign with the private key of the received key handle.
std::vector<uint8_t> sig;
auto* private_key = found_data->private_key.get();
bool status = Sign(private_key, std::move(signature_buffer), &sig);
DCHECK(status);
*response = ConstructGetAssertionResponse(std::move(authenticator_data),
std::move(sig), credential_id);
return CtapDeviceResponseCode::kSuccess;
}
CtapDeviceResponseCode VirtualCtap2Device::OnAuthenticatorGetInfo( CtapDeviceResponseCode VirtualCtap2Device::OnAuthenticatorGetInfo(
std::vector<uint8_t>* response) const { std::vector<uint8_t>* response) const {
*response = EncodeToCBOR(device_info_); *response = EncodeToCBOR(device_info_);
...@@ -252,24 +352,20 @@ CtapDeviceResponseCode VirtualCtap2Device::OnAuthenticatorGetInfo( ...@@ -252,24 +352,20 @@ CtapDeviceResponseCode VirtualCtap2Device::OnAuthenticatorGetInfo(
AuthenticatorData VirtualCtap2Device::ConstructAuthenticatorData( AuthenticatorData VirtualCtap2Device::ConstructAuthenticatorData(
base::span<const uint8_t, kRpIdHashLength> rp_id_hash, base::span<const uint8_t, kRpIdHashLength> rp_id_hash,
uint32_t current_signature_count,
base::Optional<AttestedCredentialData> attested_credential_data) { base::Optional<AttestedCredentialData> attested_credential_data) {
uint8_t flag = uint8_t flag =
base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence);
std::array<uint8_t, kSignCounterLength> signature_counter; std::array<uint8_t, kSignCounterLength> signature_counter;
// Constructing AuthenticatorData for registration operation. // Constructing AuthenticatorData for registration operation.
if (attested_credential_data) { if (attested_credential_data)
flag |= base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); flag |= base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation);
signature_counter = {{0x00, 0x00, 0x00, 0x01}};
// Constructing AuthenticatorData for sign operation. signature_counter[0] = (current_signature_count >> 24) & 0xff;
} else { signature_counter[1] = (current_signature_count >> 16) & 0xff;
auto* registration_data = FindRegistrationData( signature_counter[2] = (current_signature_count >> 8) & 0xff;
attested_credential_data->credential_id(), rp_id_hash); signature_counter[3] = (current_signature_count)&0xff;
signature_counter[0] = (registration_data->counter >> 24) & 0xff;
signature_counter[1] = (registration_data->counter >> 16) & 0xff;
signature_counter[2] = (registration_data->counter >> 8) & 0xff;
signature_counter[3] = (registration_data->counter) & 0xff;
}
return AuthenticatorData(rp_id_hash, flag, signature_counter, return AuthenticatorData(rp_id_hash, flag, signature_counter,
std::move(attested_credential_data)); std::move(attested_credential_data));
......
...@@ -41,12 +41,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device ...@@ -41,12 +41,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
CtapDeviceResponseCode OnMakeCredential(base::span<const uint8_t> request, CtapDeviceResponseCode OnMakeCredential(base::span<const uint8_t> request,
std::vector<uint8_t>* response); std::vector<uint8_t>* response);
CtapDeviceResponseCode OnGetAssertion(base::span<const uint8_t> request,
std::vector<uint8_t>* response);
CtapDeviceResponseCode OnAuthenticatorGetInfo( CtapDeviceResponseCode OnAuthenticatorGetInfo(
std::vector<uint8_t>* response) const; std::vector<uint8_t>* response) const;
AuthenticatorData ConstructAuthenticatorData( AuthenticatorData ConstructAuthenticatorData(
base::span<const uint8_t, kRpIdHashLength> rp_id_hash, base::span<const uint8_t, kRpIdHashLength> rp_id_hash,
base::Optional<AttestedCredentialData> attested_credential_data); uint32_t current_signature_count,
base::Optional<AttestedCredentialData> attested_credential_data =
base::nullopt);
AuthenticatorGetInfoResponse device_info_; AuthenticatorGetInfoResponse device_info_;
base::WeakPtrFactory<FidoDevice> weak_factory_; base::WeakPtrFactory<FidoDevice> weak_factory_;
......
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