Commit 9d64c5b7 authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[webauthn] Initial support for large blobs

This is an initial patch to add support for authenticator large blobs.
When a chrome extension creates a resident credential on an
authenticator that supports largeBlobKey, a key is generated and stored
alongside the credential.

At the moment this does nothing in the real world since there are no
hardware implementations for the authenticator extension.

See https://w3c.github.io/webauthn/#sctn-large-blob-extension

Bug: 1114875
Change-Id: Ib0791e143ab78d9f09461ee0a3226fb9c23f75cc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2353150
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#800775}
parent 81bbe1e6
...@@ -887,6 +887,13 @@ void AuthenticatorCommon::MakeCredential( ...@@ -887,6 +887,13 @@ void AuthenticatorCommon::MakeCredential(
// On dual protocol CTAP2/U2F devices, force credential creation over U2F. // On dual protocol CTAP2/U2F devices, force credential creation over U2F.
ctap_make_credential_request_->is_u2f_only = origin_is_crypto_token_extension; ctap_make_credential_request_->is_u2f_only = origin_is_crypto_token_extension;
if (resident_key && caller_origin.scheme() == "chrome-extension") {
// The large blob key extension is set for every request since we cannot
// know in advance if the RP will attempt storing a blob on a future
// GetAssertion request.
ctap_make_credential_request_->large_blob_key = true;
}
if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) && if (base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) &&
!origin_is_crypto_token_extension && !is_cross_origin) { !origin_is_crypto_token_extension && !is_cross_origin) {
// Send the unhashed origin and challenge to caBLEv2 authenticators, because // Send the unhashed origin and challenge to caBLEv2 authenticators, because
......
...@@ -1561,6 +1561,8 @@ class OverrideRPIDAuthenticatorRequestDelegate ...@@ -1561,6 +1561,8 @@ class OverrideRPIDAuthenticatorRequestDelegate
return caller_origin.Serialize(); return caller_origin.Serialize();
} }
bool SupportsResidentKeys() override { return true; }
private: private:
DISALLOW_COPY_AND_ASSIGN(OverrideRPIDAuthenticatorRequestDelegate); DISALLOW_COPY_AND_ASSIGN(OverrideRPIDAuthenticatorRequestDelegate);
}; };
...@@ -1575,11 +1577,18 @@ class OverrideRPIDAuthenticatorContentBrowserClient ...@@ -1575,11 +1577,18 @@ class OverrideRPIDAuthenticatorContentBrowserClient
} }
}; };
class OverrideRPIDAuthenticatorTest : public AuthenticatorImplTest { static constexpr char kExtensionId[] = "abcdefg";
class ExtensionAuthenticatorTest : public AuthenticatorImplTest {
public: public:
void SetUp() override { void SetUp() override {
AuthenticatorImplTest::SetUp(); AuthenticatorImplTest::SetUp();
old_client_ = SetBrowserClientForTesting(&test_client_); old_client_ = SetBrowserClientForTesting(&test_client_);
const std::string extension_origin =
std::string("chrome-extension://") + kExtensionId;
const std::string extension_page = extension_origin + "/test.html";
NavigateAndCommit(GURL(extension_page));
} }
void TearDown() override { void TearDown() override {
...@@ -1592,15 +1601,9 @@ class OverrideRPIDAuthenticatorTest : public AuthenticatorImplTest { ...@@ -1592,15 +1601,9 @@ class OverrideRPIDAuthenticatorTest : public AuthenticatorImplTest {
ContentBrowserClient* old_client_ = nullptr; ContentBrowserClient* old_client_ = nullptr;
}; };
TEST_F(OverrideRPIDAuthenticatorTest, ChromeExtensions) { // Test that credentials can be created and used from an extension origin when
// Test that credentials can be created and used from an extension origin when // permitted by the delegate.
// permitted by the delegate. TEST_F(ExtensionAuthenticatorTest, ChromeExtensions) {
constexpr char kExtensionId[] = "abcdefg";
const std::string extension_origin =
std::string("chrome-extension://") + kExtensionId;
const std::string extension_page = extension_origin + "/test.html";
NavigateAndCommit(GURL(extension_page));
std::vector<uint8_t> credential_id; std::vector<uint8_t> credential_id;
{ {
PublicKeyCredentialCreationOptionsPtr options = PublicKeyCredentialCreationOptionsPtr options =
...@@ -1625,6 +1628,43 @@ TEST_F(OverrideRPIDAuthenticatorTest, ChromeExtensions) { ...@@ -1625,6 +1628,43 @@ TEST_F(OverrideRPIDAuthenticatorTest, ChromeExtensions) {
} }
} }
// Tests that registering a resident credential on a capable authenticator also
// registers a large blob key when called from an extension.
TEST_F(ExtensionAuthenticatorTest, MakeCredentialLargeBlobKeyExtension) {
base::Optional<device::PublicKeyCredentialDescriptor> credential;
device::VirtualCtap2Device::Config config;
config.internal_uv_support = true;
virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
config.resident_key_support = true;
for (bool rk_enabled : {false, true}) {
SCOPED_TRACE(::testing::Message() << "rk=" << rk_enabled);
for (bool large_blob_supported : {false, true}) {
SCOPED_TRACE(::testing::Message()
<< "largeBlob=" << large_blob_supported);
config.large_blob_support = large_blob_supported;
virtual_device_factory_->SetCtap2Config(config);
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->authenticator_selection->SetRequireResidentKeyForTesting(
rk_enabled);
options->user.id = {1, 2, 3, 4};
options->user.name = "name";
options->user.display_name = "displayName";
MakeCredentialResult make_credential_result =
AuthenticatorMakeCredential(std::move(options));
EXPECT_EQ(make_credential_result.status, AuthenticatorStatus::SUCCESS);
auto& registration =
*virtual_device_factory_->mutable_state()->registrations.begin();
EXPECT_EQ(rk_enabled && large_blob_supported,
registration.second.large_blob_key.has_value());
virtual_device_factory_->mutable_state()->registrations.clear();
}
}
}
enum class EnterprisePolicy { enum class EnterprisePolicy {
LISTED, LISTED,
NOT_LISTED, NOT_LISTED,
......
...@@ -97,6 +97,11 @@ AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) { ...@@ -97,6 +97,11 @@ AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) {
return *this; return *this;
} }
void AuthenticatorGetAssertionResponse::set_large_blob_key(
const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key) {
large_blob_key_ = fido_parsing_utils::Materialize(large_blob_key);
}
base::Optional<base::span<const uint8_t>> base::Optional<base::span<const uint8_t>>
AuthenticatorGetAssertionResponse::hmac_secret() const { AuthenticatorGetAssertionResponse::hmac_secret() const {
if (hmac_secret_) { if (hmac_secret_) {
......
...@@ -67,6 +67,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse ...@@ -67,6 +67,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse
android_client_data_ext_ = data; android_client_data_ext_ = data;
} }
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key()
const {
return large_blob_key_;
}
void set_large_blob_key(
const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key);
// hmac_secret contains the output of the hmac_secret extension. // hmac_secret contains the output of the hmac_secret extension.
base::Optional<base::span<const uint8_t>> hmac_secret() const; base::Optional<base::span<const uint8_t>> hmac_secret() const;
void set_hmac_secret(std::vector<uint8_t>); void set_hmac_secret(std::vector<uint8_t>);
...@@ -92,6 +99,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse ...@@ -92,6 +99,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse
// authenticator output. // authenticator output.
base::Optional<std::vector<uint8_t>> android_client_data_ext_; base::Optional<std::vector<uint8_t>> android_client_data_ext_;
// The large blob key associated to the credential. This value is only
// returned if the assertion request contains the largeBlobKey extension on a
// capable authenticator and the credential has an associated large blob key.
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetAssertionResponse); DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetAssertionResponse);
}; };
......
...@@ -102,6 +102,11 @@ AuthenticatorMakeCredentialResponse::GetRpIdHash() const { ...@@ -102,6 +102,11 @@ AuthenticatorMakeCredentialResponse::GetRpIdHash() const {
return attestation_object_.rp_id_hash(); return attestation_object_.rp_id_hash();
} }
void AuthenticatorMakeCredentialResponse::set_large_blob_key(
const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key) {
large_blob_key_ = fido_parsing_utils::Materialize(large_blob_key);
}
std::vector<uint8_t> AsCTAPStyleCBORBytes( std::vector<uint8_t> AsCTAPStyleCBORBytes(
const AuthenticatorMakeCredentialResponse& response) { const AuthenticatorMakeCredentialResponse& response) {
const AttestationObject& object = response.attestation_object(); const AttestationObject& object = response.attestation_object();
...@@ -116,6 +121,9 @@ std::vector<uint8_t> AsCTAPStyleCBORBytes( ...@@ -116,6 +121,9 @@ std::vector<uint8_t> AsCTAPStyleCBORBytes(
if (response.enterprise_attestation_returned) { if (response.enterprise_attestation_returned) {
map.emplace(4, true); map.emplace(4, true);
} }
if (response.large_blob_key()) {
map.emplace(5, cbor::Value(*response.large_blob_key()));
}
auto encoded_bytes = cbor::Writer::Write(cbor::Value(std::move(map))); auto encoded_bytes = cbor::Writer::Write(cbor::Value(std::move(map)));
DCHECK(encoded_bytes); DCHECK(encoded_bytes);
return std::move(*encoded_bytes); return std::move(*encoded_bytes);
......
...@@ -78,6 +78,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse ...@@ -78,6 +78,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse
android_client_data_ext_ = data; android_client_data_ext_ = data;
} }
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key()
const {
return large_blob_key_;
}
void set_large_blob_key(
const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key);
// enterprise_attestation_returned is true if the authenticator indicated that // enterprise_attestation_returned is true if the authenticator indicated that
// it returned an enterprise attestation. Note: U2F authenticators can // it returned an enterprise attestation. Note: U2F authenticators can
// support enterprise/individual attestation but cannot indicate when they // support enterprise/individual attestation but cannot indicate when they
...@@ -95,6 +102,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse ...@@ -95,6 +102,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse
// authenticator output. // authenticator output.
base::Optional<std::vector<uint8_t>> android_client_data_ext_; base::Optional<std::vector<uint8_t>> android_client_data_ext_;
// The large blob key associated to the credential. This value is only
// returned if the credential is created with the largeBlobKey extension on a
// capable authenticator.
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorMakeCredentialResponse); DISALLOW_COPY_AND_ASSIGN(AuthenticatorMakeCredentialResponse);
}; };
......
...@@ -96,6 +96,10 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) { ...@@ -96,6 +96,10 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) {
option_map.emplace(kEnterpriseAttestationKey, true); option_map.emplace(kEnterpriseAttestationKey, true);
} }
if (options.supports_large_blobs) {
option_map.emplace(kLargeBlobsKey, true);
}
return cbor::Value(std::move(option_map)); return cbor::Value(std::move(option_map));
} }
......
...@@ -97,6 +97,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions { ...@@ -97,6 +97,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions {
// uninteresting to Chromium because we do not support the administrative // uninteresting to Chromium because we do not support the administrative
// operation to configure it. Thus this member reduces to a boolean.) // operation to configure it. Thus this member reduces to a boolean.)
bool enterprise_attestation = false; bool enterprise_attestation = false;
// Indicates whether the authenticator supports the authenticatorLargeBlobs
// command.
bool supports_large_blobs = false;
}; };
COMPONENT_EXPORT(DEVICE_FIDO) COMPONENT_EXPORT(DEVICE_FIDO)
......
...@@ -121,6 +121,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { ...@@ -121,6 +121,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
base::Optional<std::array<uint8_t, crypto::kSHA256Length>> base::Optional<std::array<uint8_t, crypto::kSHA256Length>>
alternative_application_parameter; alternative_application_parameter;
base::Optional<HMACSecret> hmac_secret; base::Optional<HMACSecret> hmac_secret;
bool large_blob_key = false;
bool is_incognito_mode = false; bool is_incognito_mode = false;
bool is_u2f_only = false; bool is_u2f_only = false;
......
...@@ -156,6 +156,11 @@ base::Optional<CtapMakeCredentialRequest> CtapMakeCredentialRequest::Parse( ...@@ -156,6 +156,11 @@ base::Optional<CtapMakeCredentialRequest> CtapMakeCredentialRequest::Parse(
return base::nullopt; return base::nullopt;
} }
request.android_client_data_ext = std::move(*android_client_data_ext); request.android_client_data_ext = std::move(*android_client_data_ext);
} else if (extension_name == kExtensionLargeBlobKey) {
if (!extension.second.is_bool() || !extension.second.GetBool()) {
return base::nullopt;
}
request.large_blob_key = true;
} }
} }
} }
...@@ -272,6 +277,10 @@ AsCTAPRequestValuePair(const CtapMakeCredentialRequest& request) { ...@@ -272,6 +277,10 @@ AsCTAPRequestValuePair(const CtapMakeCredentialRequest& request) {
extensions[cbor::Value(kExtensionHmacSecret)] = cbor::Value(true); extensions[cbor::Value(kExtensionHmacSecret)] = cbor::Value(true);
} }
if (request.large_blob_key) {
extensions[cbor::Value(kExtensionLargeBlobKey)] = cbor::Value(true);
}
if (request.cred_protect) { if (request.cred_protect) {
extensions.emplace(kExtensionCredProtect, extensions.emplace(kExtensionCredProtect,
static_cast<int64_t>(*request.cred_protect)); static_cast<int64_t>(*request.cred_protect));
......
...@@ -72,9 +72,12 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest { ...@@ -72,9 +72,12 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest {
AuthenticatorAttachment authenticator_attachment = AuthenticatorAttachment authenticator_attachment =
AuthenticatorAttachment::kAny; AuthenticatorAttachment::kAny;
bool resident_key_required = false; bool resident_key_required = false;
// hmac_secret_ indicates whether the "hmac-secret" extension should be // hmac_secret indicates whether the "hmac-secret" extension should be
// asserted to CTAP2 authenticators. // asserted to CTAP2 authenticators.
bool hmac_secret = false; bool hmac_secret = false;
// large_blob_key indicates whether a large blob key should be associated to
// the new credential through the "largeBlobKey" extension.
bool large_blob_key = false;
// If true, instruct the request handler only to dispatch this request via // If true, instruct the request handler only to dispatch this request via
// U2F. // U2F.
......
...@@ -120,6 +120,16 @@ ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used, ...@@ -120,6 +120,16 @@ ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used,
} }
} }
it = decoded_map.find(CBOR(5));
if (it != decoded_map.end()) {
if (!it->second.is_bytestring() ||
it->second.GetBytestring().size() != kLargeBlobKeyLength) {
return base::nullopt;
}
response.set_large_blob_key(
base::make_span<kLargeBlobKeyLength>(it->second.GetBytestring()));
}
return response; return response;
} }
...@@ -179,6 +189,16 @@ base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse( ...@@ -179,6 +189,16 @@ base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
} }
} }
it = response_map.find(CBOR(0x0B));
if (it != response_map.end()) {
if (!it->second.is_bytestring() ||
it->second.GetBytestring().size() != kLargeBlobKeyLength) {
return base::nullopt;
}
response.set_large_blob_key(
base::make_span<kLargeBlobKeyLength>(it->second.GetBytestring()));
}
return response; return response;
} }
...@@ -404,6 +424,14 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( ...@@ -404,6 +424,14 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
options.enterprise_attestation = option_map_it->second.GetBool(); options.enterprise_attestation = option_map_it->second.GetBool();
} }
option_map_it = option_map.find(CBOR(kLargeBlobsKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_bool() || !options.supports_resident_key) {
return base::nullopt;
}
options.supports_large_blobs = option_map_it->second.GetBool();
}
response.options = std::move(options); response.options = std::move(options);
} }
......
...@@ -36,6 +36,7 @@ const char kBioEnrollmentPreviewMapKey[] = "userVerificationMgmtPreview"; ...@@ -36,6 +36,7 @@ const char kBioEnrollmentPreviewMapKey[] = "userVerificationMgmtPreview";
const char kPinUvTokenMapKey[] = "pinUvAuthToken"; const char kPinUvTokenMapKey[] = "pinUvAuthToken";
const char kDefaultCredProtectKey[] = "defaultCredProtect"; const char kDefaultCredProtectKey[] = "defaultCredProtect";
const char kEnterpriseAttestationKey[] = "ep"; const char kEnterpriseAttestationKey[] = "ep";
const char kLargeBlobsKey[] = "largeBlobs";
const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20); const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20);
const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200); const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200);
...@@ -71,6 +72,7 @@ const char kCtap2_1Version[] = "FIDO_2_1"; ...@@ -71,6 +72,7 @@ const char kCtap2_1Version[] = "FIDO_2_1";
const char kExtensionHmacSecret[] = "hmac-secret"; const char kExtensionHmacSecret[] = "hmac-secret";
const char kExtensionCredProtect[] = "credProtect"; const char kExtensionCredProtect[] = "credProtect";
const char kExtensionAndroidClientData[] = "googleAndroidClientData"; const char kExtensionAndroidClientData[] = "googleAndroidClientData";
const char kExtensionLargeBlobKey[] = "largeBlobKey";
const base::TimeDelta kBleDevicePairingModeWaitingInterval = const base::TimeDelta kBleDevicePairingModeWaitingInterval =
base::TimeDelta::FromSeconds(2); base::TimeDelta::FromSeconds(2);
......
...@@ -40,6 +40,10 @@ constexpr size_t kClientDataHashLength = 32; ...@@ -40,6 +40,10 @@ constexpr size_t kClientDataHashLength = 32;
// https://www.w3.org/TR/webauthn/#sec-authenticator-data // https://www.w3.org/TR/webauthn/#sec-authenticator-data
constexpr size_t kRpIdHashLength = 32; constexpr size_t kRpIdHashLength = 32;
// Length of the key used to encrypt large blobs.
// TODO(nsatragno): add a link to the spec once it's published.
constexpr size_t kLargeBlobKeyLength = 32;
// Max length for the user handle: // Max length for the user handle:
// https://www.w3.org/TR/webauthn/#user-handle // https://www.w3.org/TR/webauthn/#user-handle
constexpr size_t kUserHandleMaxLength = 64; constexpr size_t kUserHandleMaxLength = 64;
...@@ -325,6 +329,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentPreviewMapKey[]; ...@@ -325,6 +329,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentPreviewMapKey[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kPinUvTokenMapKey[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kPinUvTokenMapKey[];
extern const char kDefaultCredProtectKey[]; extern const char kDefaultCredProtectKey[];
extern const char kEnterpriseAttestationKey[]; extern const char kEnterpriseAttestationKey[];
extern const char kLargeBlobsKey[];
// HID transport specific constants. // HID transport specific constants.
constexpr uint32_t kHidBroadcastChannel = 0xffffffff; constexpr uint32_t kHidBroadcastChannel = 0xffffffff;
...@@ -419,6 +424,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[]; ...@@ -419,6 +424,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[];
COMPONENT_EXPORT(DEVICE_FIDO) COMPONENT_EXPORT(DEVICE_FIDO)
extern const char kExtensionAndroidClientData[]; extern const char kExtensionAndroidClientData[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionLargeBlobKey[];
// Maximum number of seconds the browser waits for Bluetooth authenticator to // Maximum number of seconds the browser waits for Bluetooth authenticator to
// send packets that advertises that the device is in pairing mode before // send packets that advertises that the device is in pairing mode before
......
...@@ -317,6 +317,11 @@ bool ResponseValid(const FidoAuthenticator& authenticator, ...@@ -317,6 +317,11 @@ bool ResponseValid(const FidoAuthenticator& authenticator,
return false; return false;
} }
if (request.large_blob_key && !response.large_blob_key()) {
FIDO_LOG(ERROR) << "Large blob key requested but not returned";
return false;
}
return true; return true;
} }
} // namespace } // namespace
...@@ -974,6 +979,11 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator( ...@@ -974,6 +979,11 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
request->hmac_secret = false; request->hmac_secret = false;
} }
if (request->large_blob_key &&
!authenticator->Options()->supports_large_blobs) {
request->large_blob_key = false;
}
if (!authenticator->SupportsEnterpriseAttestation()) { if (!authenticator->SupportsEnterpriseAttestation()) {
switch (request->attestation_preference) { switch (request->attestation_preference) {
case AttestationConveyancePreference::kEnterpriseApprovedByBrowser: case AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
......
...@@ -167,7 +167,8 @@ std::vector<uint8_t> ConstructMakeCredentialResponse( ...@@ -167,7 +167,8 @@ std::vector<uint8_t> ConstructMakeCredentialResponse(
base::span<const uint8_t> signature, base::span<const uint8_t> signature,
AuthenticatorData authenticator_data, AuthenticatorData authenticator_data,
base::Optional<std::vector<uint8_t>> android_client_data_ext, base::Optional<std::vector<uint8_t>> android_client_data_ext,
bool enterprise_attestation_requested) { bool enterprise_attestation_requested,
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key) {
cbor::Value::MapValue attestation_map; cbor::Value::MapValue attestation_map;
attestation_map.emplace("alg", -7); attestation_map.emplace("alg", -7);
attestation_map.emplace("sig", fido_parsing_utils::Materialize(signature)); attestation_map.emplace("sig", fido_parsing_utils::Materialize(signature));
...@@ -190,6 +191,9 @@ std::vector<uint8_t> ConstructMakeCredentialResponse( ...@@ -190,6 +191,9 @@ std::vector<uint8_t> ConstructMakeCredentialResponse(
} }
make_credential_response.enterprise_attestation_returned = make_credential_response.enterprise_attestation_returned =
enterprise_attestation_requested; enterprise_attestation_requested;
if (large_blob_key) {
make_credential_response.set_large_blob_key(*large_blob_key);
}
return AsCTAPStyleCBORBytes(make_credential_response); return AsCTAPStyleCBORBytes(make_credential_response);
} }
...@@ -417,6 +421,9 @@ std::vector<uint8_t> EncodeGetAssertionResponse( ...@@ -417,6 +421,9 @@ std::vector<uint8_t> EncodeGetAssertionResponse(
response_map.emplace(0xf0, response_map.emplace(0xf0,
cbor::Value(*response.android_client_data_ext())); cbor::Value(*response.android_client_data_ext()));
} }
if (response.large_blob_key()) {
response_map.emplace(0x0b, cbor::Value(*response.large_blob_key()));
}
return WriteCBOR(cbor::Value(std::move(response_map)), allow_invalid_utf8); return WriteCBOR(cbor::Value(std::move(response_map)), allow_invalid_utf8);
} }
...@@ -536,6 +543,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, ...@@ -536,6 +543,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
options.enterprise_attestation = true; options.enterprise_attestation = true;
} }
if (config.large_blob_support) {
DCHECK(config.resident_key_support);
options_updated = true;
options.supports_large_blobs = true;
}
if (options_updated) { if (options_updated) {
device_info_->options = std::move(options); device_info_->options = std::move(options);
} }
...@@ -554,6 +567,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, ...@@ -554,6 +567,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
extensions.emplace_back(device::kExtensionAndroidClientData); extensions.emplace_back(device::kExtensionAndroidClientData);
} }
if (config.large_blob_support) {
extensions.emplace_back(device::kExtensionLargeBlobKey);
}
if (!extensions.empty()) { if (!extensions.empty()) {
device_info_->extensions.emplace(std::move(extensions)); device_info_->extensions.emplace(std::move(extensions));
} }
...@@ -914,7 +931,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -914,7 +931,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
// extensions but Chromium should not make this mistake. // extensions but Chromium should not make this mistake.
DLOG(ERROR) DLOG(ERROR)
<< "Rejecting makeCredential due to unexpected hmac_secret extension"; << "Rejecting makeCredential due to unexpected hmac_secret extension";
return base::nullopt; return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
} }
extensions_map.emplace(cbor::Value(kExtensionHmacSecret), extensions_map.emplace(cbor::Value(kExtensionHmacSecret),
cbor::Value(true)); cbor::Value(true));
...@@ -934,6 +951,19 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -934,6 +951,19 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
cbor::Value(static_cast<int64_t>(cred_protect))); cbor::Value(static_cast<int64_t>(cred_protect)));
} }
if (request.large_blob_key) {
if (!config_.large_blob_support) {
DLOG(ERROR) << "Rejecting makeCredential due to unexpected largeBlobKey "
"extension";
return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
}
if (!request.resident_key_required) {
DLOG(ERROR)
<< "largeBlobKey is not supported for non resident credentials";
return CtapDeviceResponseCode::kCtap2ErrInvalidOption;
}
}
if (config_.add_extra_extension) { if (config_.add_extra_extension) {
extensions_map.emplace(cbor::Value("unsolicited"), cbor::Value(42)); extensions_map.emplace(cbor::Value("unsolicited"), cbor::Value(42));
} }
...@@ -1025,9 +1055,16 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -1025,9 +1055,16 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
client_data_json.size())); client_data_json.size()));
} }
base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key;
if (request.large_blob_key) {
large_blob_key.emplace();
RAND_bytes(large_blob_key->data(), large_blob_key->size());
}
*response = ConstructMakeCredentialResponse( *response = ConstructMakeCredentialResponse(
std::move(attestation_cert), sig, std::move(authenticator_data), std::move(attestation_cert), sig, std::move(authenticator_data),
std::move(opt_android_client_data_ext), enterprise_attestation_requested); std::move(opt_android_client_data_ext), enterprise_attestation_requested,
large_blob_key);
RegistrationData registration(std::move(private_key), rp_id_hash, RegistrationData registration(std::move(private_key), rp_id_hash,
1 /* signature counter */); 1 /* signature counter */);
...@@ -1068,6 +1105,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -1068,6 +1105,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
registration.hmac_key->second.size()); registration.hmac_key->second.size());
} }
registration.large_blob_key = std::move(large_blob_key);
StoreNewKey(key_handle, std::move(registration)); StoreNewKey(key_handle, std::move(registration));
return CtapDeviceResponseCode::kSuccess; return CtapDeviceResponseCode::kSuccess;
} }
...@@ -1350,6 +1389,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( ...@@ -1350,6 +1389,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
assertion.SetUserEntity(registration.second->user.value()); assertion.SetUserEntity(registration.second->user.value());
} }
if (request.large_blob_key) {
if (!config_.large_blob_support) {
return CtapDeviceResponseCode::kCtap2ErrExtensionNotSupported;
}
if (registration.second->large_blob_key) {
assertion.set_large_blob_key(*registration.second->large_blob_key);
}
}
if (opt_android_client_data_json) { if (opt_android_client_data_json) {
std::vector<uint8_t> android_client_data_ext; std::vector<uint8_t> android_client_data_ext;
fido_parsing_utils::Append( fido_parsing_utils::Append(
......
...@@ -68,6 +68,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device ...@@ -68,6 +68,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
uint8_t bio_enrollment_samples_required = 4; uint8_t bio_enrollment_samples_required = 4;
bool cred_protect_support = false; bool cred_protect_support = false;
bool hmac_secret_support = false; bool hmac_secret_support = false;
bool large_blob_support = false;
IncludeCredential include_credential_in_assertion_response = IncludeCredential include_credential_in_assertion_response =
IncludeCredential::ONLY_IF_NEEDED; IncludeCredential::ONLY_IF_NEEDED;
......
...@@ -114,6 +114,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { ...@@ -114,6 +114,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
base::Optional<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>> base::Optional<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>>
hmac_key; hmac_key;
base::Optional<std::array<uint8_t, 32>> large_blob_key;
DISALLOW_COPY_AND_ASSIGN(RegistrationData); DISALLOW_COPY_AND_ASSIGN(RegistrationData);
}; };
......
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