Commit a91eca7e authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[devtools] Add support for WebAuthn large blobs

Add support for storing and retrieving large blobs through the WebAuthn
DevTools Domain. This will in turn be used by WebDriver to implement the
webauthn:extension:largeBlob capability [1].

This patch also wires up the CTAP version through devtools. This is a
soft requirement for large blobs, and will also be used to move
credProps tests to WPTs [2].

[1] https://w3c.github.io/webauthn/#sctn-authenticator-extension-capabilities
[2] https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/web_tests/http/tests/credentialmanager/credentialscontainer-create-with-resident-keys.html

Bug: 1114875
Change-Id: Ia9a5353285bd9aec540d5e92ec737919c7790b62
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2486254
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#819488}
parent d1ab3b63
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/renderer_host/render_frame_host_impl.h"
...@@ -30,6 +33,8 @@ static constexpr char kCableNotSupportedOnU2f[] = ...@@ -30,6 +33,8 @@ static constexpr char kCableNotSupportedOnU2f[] =
"U2F only supports the \"usb\", \"ble\" and \"nfc\" transports"; "U2F only supports the \"usb\", \"ble\" and \"nfc\" transports";
static constexpr char kCouldNotCreateCredential[] = static constexpr char kCouldNotCreateCredential[] =
"An error occurred trying to create the credential"; "An error occurred trying to create the credential";
static constexpr char kCouldNotStoreLargeBlob[] =
"An error occurred trying to store the large blob";
static constexpr char kCredentialNotFound[] = static constexpr char kCredentialNotFound[] =
"Could not find a credential matching the ID"; "Could not find a credential matching the ID";
static constexpr char kDevToolsNotAttached[] = static constexpr char kDevToolsNotAttached[] =
...@@ -38,10 +43,16 @@ static constexpr char kErrorCreatingAuthenticator[] = ...@@ -38,10 +43,16 @@ static constexpr char kErrorCreatingAuthenticator[] =
"An error occurred when trying to create the authenticator"; "An error occurred when trying to create the authenticator";
static constexpr char kHandleRequiredForResidentCredential[] = static constexpr char kHandleRequiredForResidentCredential[] =
"The User Handle is required for Resident Credentials"; "The User Handle is required for Resident Credentials";
static constexpr char kInvalidCtapVersion[] =
"Invalid CTAP version. Valid values are \"ctap2_0\" and \"ctap2_1\"";
static constexpr char kInvalidProtocol[] = "The protocol is not valid"; static constexpr char kInvalidProtocol[] = "The protocol is not valid";
static constexpr char kInvalidTransport[] = "The transport is not valid"; static constexpr char kInvalidTransport[] = "The transport is not valid";
static constexpr char kInvalidUserHandle[] = static constexpr char kInvalidUserHandle[] =
"The User Handle must have a maximum size of "; "The User Handle must have a maximum size of ";
static constexpr char kLargeBlobRequiresResidentKey[] =
"Large blob requires resident key support";
static constexpr char kLargeBlobRequiresCtap2_1[] =
"Large blob requires a CTAP 2.1 authenticator";
static constexpr char kResidentCredentialNotSupported[] = static constexpr char kResidentCredentialNotSupported[] =
"The Authenticator does not support Resident Credentials."; "The Authenticator does not support Resident Credentials.";
static constexpr char kRpIdRequired[] = static constexpr char kRpIdRequired[] =
...@@ -50,6 +61,38 @@ static constexpr char kVirtualEnvironmentNotEnabled[] = ...@@ -50,6 +61,38 @@ static constexpr char kVirtualEnvironmentNotEnabled[] =
"The Virtual Authenticator Environment has not been enabled for this " "The Virtual Authenticator Environment has not been enabled for this "
"session"; "session";
class GetCredentialCallbackAggregator
: public base::RefCounted<GetCredentialCallbackAggregator> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
explicit GetCredentialCallbackAggregator(
std::unique_ptr<WebAuthn::Backend::GetCredentialsCallback> callback)
: callback_(std::move(callback)) {}
GetCredentialCallbackAggregator(const GetCredentialCallbackAggregator&) =
delete;
GetCredentialCallbackAggregator operator=(
const GetCredentialCallbackAggregator&) = delete;
void OnLargeBlob(std::unique_ptr<WebAuthn::Credential> credential,
const base::Optional<std::vector<uint8_t>>& blob) {
if (blob) {
credential->SetLargeBlob(Binary::fromVector(*blob));
}
credentials_->emplace_back(std::move(credential));
}
private:
friend class base::RefCounted<GetCredentialCallbackAggregator>;
~GetCredentialCallbackAggregator() {
callback_->sendSuccess(std::move(credentials_));
}
std::unique_ptr<WebAuthn::Backend::GetCredentialsCallback> callback_;
std::unique_ptr<Array<WebAuthn::Credential>> credentials_ =
std::make_unique<Array<WebAuthn::Credential>>();
};
device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) { device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) {
if (protocol == WebAuthn::AuthenticatorProtocolEnum::Ctap2) if (protocol == WebAuthn::AuthenticatorProtocolEnum::Ctap2)
return device::ProtocolVersion::kCtap2; return device::ProtocolVersion::kCtap2;
...@@ -58,6 +101,15 @@ device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) { ...@@ -58,6 +101,15 @@ device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) {
return device::ProtocolVersion::kUnknown; return device::ProtocolVersion::kUnknown;
} }
base::Optional<device::Ctap2Version> ConvertToCtap2Version(
base::StringPiece version) {
if (version == WebAuthn::Ctap2VersionEnum::Ctap2_0)
return device::Ctap2Version::kCtap2_0;
if (version == WebAuthn::Ctap2VersionEnum::Ctap2_1)
return device::Ctap2Version::kCtap2_1;
return base::nullopt;
}
std::vector<uint8_t> CopyBinaryToVector(const Binary& binary) { std::vector<uint8_t> CopyBinaryToVector(const Binary& binary) {
return std::vector<uint8_t>(binary.data(), binary.data() + binary.size()); return std::vector<uint8_t>(binary.data(), binary.data() + binary.size());
} }
...@@ -143,6 +195,20 @@ Response WebAuthnHandler::AddVirtualAuthenticator( ...@@ -143,6 +195,20 @@ Response WebAuthnHandler::AddVirtualAuthenticator(
return Response::InvalidParams(kCableNotSupportedOnU2f); return Response::InvalidParams(kCableNotSupportedOnU2f);
} }
auto ctap2_version = ConvertToCtap2Version(
options->GetCtap2Version(WebAuthn::Ctap2VersionEnum::Ctap2_0));
if (!ctap2_version)
return Response::InvalidParams(kInvalidCtapVersion);
bool has_large_blob = options->GetHasLargeBlob(/*default=*/false);
bool has_resident_key = options->GetHasResidentKey(/*default=*/false);
if (has_large_blob && !has_resident_key)
return Response::InvalidParams(kLargeBlobRequiresResidentKey);
if (has_large_blob && (protocol != device::ProtocolVersion::kCtap2 ||
ctap2_version < device::Ctap2Version::kCtap2_1)) {
return Response::InvalidParams(kLargeBlobRequiresCtap2_1);
}
VirtualAuthenticator* authenticator = nullptr; VirtualAuthenticator* authenticator = nullptr;
switch (protocol) { switch (protocol) {
case device::ProtocolVersion::kU2f: case device::ProtocolVersion::kU2f:
...@@ -150,13 +216,12 @@ Response WebAuthnHandler::AddVirtualAuthenticator( ...@@ -150,13 +216,12 @@ Response WebAuthnHandler::AddVirtualAuthenticator(
break; break;
case device::ProtocolVersion::kCtap2: case device::ProtocolVersion::kCtap2:
authenticator = authenticator_manager->CreateCTAP2Authenticator( authenticator = authenticator_manager->CreateCTAP2Authenticator(
device::Ctap2Version::kCtap2_0, *transport, *ctap2_version, *transport,
transport == device::FidoTransportProtocol::kInternal transport == device::FidoTransportProtocol::kInternal
? device::AuthenticatorAttachment::kPlatform ? device::AuthenticatorAttachment::kPlatform
: device::AuthenticatorAttachment::kCrossPlatform, : device::AuthenticatorAttachment::kCrossPlatform,
options->GetHasResidentKey(/*default=*/false), has_resident_key, options->GetHasUserVerification(/*default=*/false),
options->GetHasUserVerification(/*default=*/false), has_large_blob);
options->GetHasLargeBlob(/*default=*/false));
break; break;
case device::ProtocolVersion::kUnknown: case device::ProtocolVersion::kUnknown:
NOTREACHED(); NOTREACHED();
...@@ -188,84 +253,136 @@ Response WebAuthnHandler::RemoveVirtualAuthenticator( ...@@ -188,84 +253,136 @@ Response WebAuthnHandler::RemoveVirtualAuthenticator(
return Response::Success(); return Response::Success();
} }
Response WebAuthnHandler::AddCredential( void WebAuthnHandler::AddCredential(
const String& authenticator_id, const String& authenticator_id,
std::unique_ptr<WebAuthn::Credential> credential) { std::unique_ptr<WebAuthn::Credential> credential,
std::unique_ptr<AddCredentialCallback> callback) {
VirtualAuthenticator* authenticator; VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator); Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) if (!response.IsSuccess()) {
return response; callback->sendFailure(std::move(response));
return;
}
Binary user_handle = credential->GetUserHandle(Binary()); Binary user_handle = credential->GetUserHandle(Binary());
if (credential->HasUserHandle() && if (credential->HasUserHandle() &&
user_handle.size() > device::kUserHandleMaxLength) { user_handle.size() > device::kUserHandleMaxLength) {
return Response::InvalidParams( callback->sendFailure(Response::InvalidParams(
kInvalidUserHandle + kInvalidUserHandle +
base::NumberToString(device::kUserHandleMaxLength)); base::NumberToString(device::kUserHandleMaxLength)));
return;
} }
if (!credential->HasRpId()) if (!credential->HasRpId()) {
return Response::InvalidParams(kRpIdRequired); callback->sendFailure(Response::InvalidParams(kRpIdRequired));
return;
}
if (credential->HasLargeBlob() && !credential->GetIsResidentCredential()) {
callback->sendFailure(
Response::InvalidParams(kLargeBlobRequiresResidentKey));
return;
}
bool credential_created; bool credential_created;
std::vector<uint8_t> credential_id =
CopyBinaryToVector(credential->GetCredentialId());
if (credential->GetIsResidentCredential()) { if (credential->GetIsResidentCredential()) {
if (!authenticator->has_resident_key()) if (!authenticator->has_resident_key()) {
return Response::InvalidParams(kResidentCredentialNotSupported); callback->sendFailure(
Response::InvalidParams(kResidentCredentialNotSupported));
return;
}
if (!credential->HasUserHandle()) if (!credential->HasUserHandle()) {
return Response::InvalidParams(kHandleRequiredForResidentCredential); callback->sendFailure(
Response::InvalidParams(kHandleRequiredForResidentCredential));
return;
}
credential_created = authenticator->AddResidentRegistration( credential_created = authenticator->AddResidentRegistration(
CopyBinaryToVector(credential->GetCredentialId()), credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetRpId(""),
CopyBinaryToVector(credential->GetPrivateKey()),
credential->GetSignCount(), CopyBinaryToVector(user_handle)); credential->GetSignCount(), CopyBinaryToVector(user_handle));
} else { } else {
credential_created = authenticator->AddRegistration( credential_created = authenticator->AddRegistration(
CopyBinaryToVector(credential->GetCredentialId()), credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetRpId(""),
CopyBinaryToVector(credential->GetPrivateKey()),
credential->GetSignCount()); credential->GetSignCount());
} }
if (!credential_created) if (!credential_created) {
return Response::ServerError(kCouldNotCreateCredential); callback->sendFailure(Response::ServerError(kCouldNotCreateCredential));
return;
}
return Response::Success(); if (credential->HasLargeBlob()) {
authenticator->SetLargeBlob(
credential_id, CopyBinaryToVector(credential->GetLargeBlob({})),
base::BindOnce(
[](std::unique_ptr<AddCredentialCallback> callback, bool success) {
if (!success) {
callback->sendFailure(
Response::ServerError(kCouldNotStoreLargeBlob));
return;
}
callback->sendSuccess();
},
std::move(callback)));
return;
}
callback->sendSuccess();
} }
Response WebAuthnHandler::GetCredential( void WebAuthnHandler::GetCredential(
const String& authenticator_id, const String& authenticator_id,
const Binary& credential_id, const Binary& credential_id,
std::unique_ptr<WebAuthn::Credential>* out_credential) { std::unique_ptr<GetCredentialCallback> callback) {
VirtualAuthenticator* authenticator; VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator); Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) if (!response.IsSuccess()) {
return response; callback->sendFailure(response);
return;
}
auto registration = auto registration =
authenticator->registrations().find(CopyBinaryToVector(credential_id)); authenticator->registrations().find(CopyBinaryToVector(credential_id));
if (registration == authenticator->registrations().end()) if (registration == authenticator->registrations().end()) {
return Response::InvalidParams(kCredentialNotFound); callback->sendFailure(Response::InvalidParams(kCredentialNotFound));
return;
}
*out_credential = BuildCredentialFromRegistration(*registration); authenticator->GetLargeBlob(
return Response::Success(); registration->first,
base::BindOnce(
[](std::unique_ptr<WebAuthn::Credential> registration,
std::unique_ptr<GetCredentialCallback> callback,
const base::Optional<std::vector<uint8_t>>& blob) {
if (blob) {
registration->SetLargeBlob(Binary::fromVector(*blob));
}
callback->sendSuccess(std::move(registration));
},
BuildCredentialFromRegistration(*registration), std::move(callback)));
} }
Response WebAuthnHandler::GetCredentials( void WebAuthnHandler::GetCredentials(
const String& authenticator_id, const String& authenticator_id,
std::unique_ptr<Array<WebAuthn::Credential>>* out_credentials) { std::unique_ptr<GetCredentialsCallback> callback) {
VirtualAuthenticator* authenticator; VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator); Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) if (!response.IsSuccess()) {
return response; callback->sendFailure(response);
return;
}
*out_credentials = std::make_unique<Array<WebAuthn::Credential>>(); auto aggregator = base::MakeRefCounted<GetCredentialCallbackAggregator>(
std::move(callback));
for (const auto& registration : authenticator->registrations()) { for (const auto& registration : authenticator->registrations()) {
(*out_credentials) authenticator->GetLargeBlob(
->emplace_back(BuildCredentialFromRegistration(registration)); registration.first,
base::BindOnce(&GetCredentialCallbackAggregator::OnLargeBlob,
aggregator,
BuildCredentialFromRegistration(registration)));
} }
return Response::Success();
} }
Response WebAuthnHandler::RemoveCredential(const String& authenticator_id, Response WebAuthnHandler::RemoveCredential(const String& authenticator_id,
......
...@@ -20,6 +20,8 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend { ...@@ -20,6 +20,8 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
public: public:
CONTENT_EXPORT WebAuthnHandler(); CONTENT_EXPORT WebAuthnHandler();
CONTENT_EXPORT ~WebAuthnHandler() override; CONTENT_EXPORT ~WebAuthnHandler() override;
WebAuthnHandler(const WebAuthnHandler&) = delete;
WebAuthnHandler operator=(const WebAuthnHandler&) = delete;
// DevToolsDomainHandler: // DevToolsDomainHandler:
void SetRenderer(int process_host_id, void SetRenderer(int process_host_id,
...@@ -33,17 +35,15 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend { ...@@ -33,17 +35,15 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
std::unique_ptr<WebAuthn::VirtualAuthenticatorOptions> options, std::unique_ptr<WebAuthn::VirtualAuthenticatorOptions> options,
String* out_authenticator_id) override; String* out_authenticator_id) override;
Response RemoveVirtualAuthenticator(const String& authenticator_id) override; Response RemoveVirtualAuthenticator(const String& authenticator_id) override;
Response AddCredential( void AddCredential(const String& authenticator_id,
const String& authenticator_id, std::unique_ptr<protocol::WebAuthn::Credential> credential,
std::unique_ptr<protocol::WebAuthn::Credential> credential) override; std::unique_ptr<AddCredentialCallback> callback) override;
Response GetCredential( void GetCredential(const String& authenticator_id,
const String& authenticator_id,
const Binary& credential_id, const Binary& credential_id,
std::unique_ptr<WebAuthn::Credential>* out_credential) override; std::unique_ptr<GetCredentialCallback> callback) override;
Response GetCredentials( void GetCredentials(
const String& authenticator_id, const String& authenticator_id,
std::unique_ptr<protocol::Array<protocol::WebAuthn::Credential>>* std::unique_ptr<GetCredentialsCallback> callback) override;
out_credentials) override;
Response RemoveCredential(const String& in_authenticator_id, Response RemoveCredential(const String& in_authenticator_id,
const Binary& credential_id) override; const Binary& credential_id) override;
Response ClearCredentials(const String& in_authenticator_id) override; Response ClearCredentials(const String& in_authenticator_id) override;
...@@ -58,7 +58,6 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend { ...@@ -58,7 +58,6 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
Response FindAuthenticator(const String& id, Response FindAuthenticator(const String& id,
VirtualAuthenticator** out_authenticator); VirtualAuthenticator** out_authenticator);
RenderFrameHostImpl* frame_host_ = nullptr; RenderFrameHostImpl* frame_host_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WebAuthnHandler);
}; };
} // namespace protocol } // namespace protocol
......
...@@ -110,7 +110,8 @@ ...@@ -110,7 +110,8 @@
}, },
{ {
"domain": "WebAuthn", "domain": "WebAuthn",
"include": ["enable", "disable", "addVirtualAuthenticator", "removeVirtualAuthenticator", "addCredential", "removeCredential", "clearCredentials", "getCredential", "getCredentials", "setUserVerified", "setAutomaticPresenceSimulation"] "include": ["enable", "disable", "addVirtualAuthenticator", "removeVirtualAuthenticator", "addCredential", "removeCredential", "clearCredentials", "getCredential", "getCredentials", "setUserVerified", "setAutomaticPresenceSimulation"],
"async": ["addCredential", "getCredential", "getCredentials"]
} }
] ]
}, },
......
...@@ -51,7 +51,7 @@ void VirtualAuthenticator::AddReceiver( ...@@ -51,7 +51,7 @@ void VirtualAuthenticator::AddReceiver(
bool VirtualAuthenticator::AddRegistration( bool VirtualAuthenticator::AddRegistration(
std::vector<uint8_t> key_handle, std::vector<uint8_t> key_handle,
const std::string& rp_id, const std::string& rp_id,
const std::vector<uint8_t>& private_key, base::span<const uint8_t> private_key,
int32_t counter) { int32_t counter) {
base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>> base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
fido_private_key = fido_private_key =
...@@ -71,7 +71,7 @@ bool VirtualAuthenticator::AddRegistration( ...@@ -71,7 +71,7 @@ bool VirtualAuthenticator::AddRegistration(
bool VirtualAuthenticator::AddResidentRegistration( bool VirtualAuthenticator::AddResidentRegistration(
std::vector<uint8_t> key_handle, std::vector<uint8_t> key_handle,
std::string rp_id, std::string rp_id,
const std::vector<uint8_t>& private_key, base::span<const uint8_t> private_key,
int32_t counter, int32_t counter,
std::vector<uint8_t> user_handle) { std::vector<uint8_t> user_handle) {
base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>> base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
...@@ -221,7 +221,7 @@ void VirtualAuthenticator::OnLargeBlobUncompressed( ...@@ -221,7 +221,7 @@ void VirtualAuthenticator::OnLargeBlobUncompressed(
} }
void VirtualAuthenticator::OnLargeBlobCompressed( void VirtualAuthenticator::OnLargeBlobCompressed(
const std::vector<uint8_t>& key_handle, base::span<const uint8_t> key_handle,
SetLargeBlobCallback callback, SetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result) { data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result) {
auto registration = state_->registrations.find(key_handle); auto registration = state_->registrations.find(key_handle);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/containers/span.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
...@@ -52,14 +53,14 @@ class CONTENT_EXPORT VirtualAuthenticator ...@@ -52,14 +53,14 @@ class CONTENT_EXPORT VirtualAuthenticator
// false otherwise. // false otherwise.
bool AddRegistration(std::vector<uint8_t> key_handle, bool AddRegistration(std::vector<uint8_t> key_handle,
const std::string& rp_id, const std::string& rp_id,
const std::vector<uint8_t>& private_key, base::span<const uint8_t> private_key,
int32_t counter); int32_t counter);
// Register a new resident credential. Returns true if the registration was // Register a new resident credential. Returns true if the registration was
// successful, false otherwise. // successful, false otherwise.
bool AddResidentRegistration(std::vector<uint8_t> key_handle, bool AddResidentRegistration(std::vector<uint8_t> key_handle,
std::string rp_id, std::string rp_id,
const std::vector<uint8_t>& private_key, base::span<const uint8_t> private_key,
int32_t counter, int32_t counter,
std::vector<uint8_t> user_handle); std::vector<uint8_t> user_handle);
...@@ -70,11 +71,6 @@ class CONTENT_EXPORT VirtualAuthenticator ...@@ -70,11 +71,6 @@ class CONTENT_EXPORT VirtualAuthenticator
// credential was found and removed, false otherwise. // credential was found and removed, false otherwise.
bool RemoveRegistration(const std::vector<uint8_t>& key_handle); bool RemoveRegistration(const std::vector<uint8_t>& key_handle);
// Returns the large blob associated with the credential identified by
// |key_handle|, if any.
base::Optional<std::vector<uint8_t>> GetLargeBlob(
base::span<const uint8_t> key_handle);
// Sets whether tests of user presence succeed or not for new requests sent to // Sets whether tests of user presence succeed or not for new requests sent to
// this authenticator. The default is true. // this authenticator. The default is true.
void SetUserPresence(bool is_user_present); void SetUserPresence(bool is_user_present);
...@@ -129,7 +125,7 @@ class CONTENT_EXPORT VirtualAuthenticator ...@@ -129,7 +125,7 @@ class CONTENT_EXPORT VirtualAuthenticator
GetLargeBlobCallback callback, GetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result); data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result);
void OnLargeBlobCompressed( void OnLargeBlobCompressed(
const std::vector<uint8_t>& key_handle, base::span<const uint8_t> key_handle,
SetLargeBlobCallback callback, SetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result); data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result);
......
...@@ -564,8 +564,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, ...@@ -564,8 +564,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
if (config.large_blob_support) { if (config.large_blob_support) {
DCHECK(config.resident_key_support); DCHECK(config.resident_key_support);
DCHECK(base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1)); DCHECK(base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1));
DCHECK(config.pin_uv_auth_token_support) DCHECK((!config.pin_support && !config.internal_uv_support) ||
<< "PinUvAuthToken support is required to write large blobs"; config.pin_uv_auth_token_support)
<< "PinUvAuthToken support is required to write large blobs for "
"uv-enabled authenticators";
options_updated = true; options_updated = true;
options.supports_large_blobs = true; options.supports_large_blobs = true;
} }
......
...@@ -8255,6 +8255,11 @@ experimental domain WebAuthn ...@@ -8255,6 +8255,11 @@ experimental domain WebAuthn
# Client To Authenticator Protocol 2. # Client To Authenticator Protocol 2.
ctap2 ctap2
type Ctap2Version extends string
enum
ctap2_0
ctap2_1
type AuthenticatorTransport extends string type AuthenticatorTransport extends string
enum enum
# Cross-Platform authenticator attachments: # Cross-Platform authenticator attachments:
...@@ -8268,6 +8273,8 @@ experimental domain WebAuthn ...@@ -8268,6 +8273,8 @@ experimental domain WebAuthn
type VirtualAuthenticatorOptions extends object type VirtualAuthenticatorOptions extends object
properties properties
AuthenticatorProtocol protocol AuthenticatorProtocol protocol
# Defaults to ctap2_0. Ignored if |protocol| == u2f.
optional Ctap2Version ctap2Version
AuthenticatorTransport transport AuthenticatorTransport transport
# Defaults to false. # Defaults to false.
optional boolean hasResidentKey optional boolean hasResidentKey
...@@ -8300,6 +8307,9 @@ experimental domain WebAuthn ...@@ -8300,6 +8307,9 @@ experimental domain WebAuthn
# assertion. # assertion.
# See https://w3c.github.io/webauthn/#signature-counter # See https://w3c.github.io/webauthn/#signature-counter
integer signCount integer signCount
# The large blob associated with the credential.
# See https://w3c.github.io/webauthn/#sctn-large-blob-extension
optional binary largeBlob
# Enable the WebAuthn domain and start intercepting credential storage and # Enable the WebAuthn domain and start intercepting credential storage and
# retrieval with a virtual authenticator. # retrieval with a virtual authenticator.
......
...@@ -58,14 +58,19 @@ async function registerCredential(options = {}) { ...@@ -58,14 +58,19 @@ async function registerCredential(options = {}) {
try { try {
const credential = await navigator.credentials.create({publicKey: options}); const credential = await navigator.credentials.create({publicKey: options});
return { let result = {
status: "OK", status: "OK",
credential: { credential: {
id: credential.id, id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)), rawId: Array.from(new Uint8Array(credential.rawId)),
transports: credential.response.getTransports(), transports: credential.response.getTransports(),
} },
}; };
if (credential.getClientExtensionResults().largeBlob) {
result.largeBlobSupported =
credential.getClientExtensionResults().largeBlob.supported;
}
return result;
} catch (error) { } catch (error) {
return {status: error.toString()}; return {status: error.toString()};
} }
...@@ -81,10 +86,15 @@ async function getCredential(credential, options = {}) { ...@@ -81,10 +86,15 @@ async function getCredential(credential, options = {}) {
try { try {
const attestation = await navigator.credentials.get({publicKey: options}); const attestation = await navigator.credentials.get({publicKey: options});
return { let result = {
status: "OK", status: "OK",
attestation, attestation,
}; };
if (attestation.getClientExtensionResults().largeBlob) {
result.blob = new TextDecoder().decode(
attestation.getClientExtensionResults().largeBlob.blob);
}
return result;
} catch (error) { } catch (error) {
return {status: error.toString()}; return {status: error.toString()};
} }
......
...@@ -55,4 +55,12 @@ Check that the WebAuthn command addCredential validates parameters ...@@ -55,4 +55,12 @@ Check that the WebAuthn command addCredential validates parameters
id : <number> id : <number>
sessionId : <string> sessionId : <string>
} }
{
error : {
code : -32602
message : Large blob requires resident key support
}
id : <number>
sessionId : <string>
}
...@@ -59,5 +59,12 @@ ...@@ -59,5 +59,12 @@
credentialOptions.credential.userHandle = btoa("nina"); credentialOptions.credential.userHandle = btoa("nina");
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions)); testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try with a large blob on a non resident credential.
credentialOptions.credential.privateKey =
await session.evaluateAsync("generateBase64Key()");
credentialOptions.credential.largeBlob = btoa("large blob");
credentialOptions.credential.isResidentCredential = false;
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
testRunner.completeTest(); testRunner.completeTest();
}) })
...@@ -15,6 +15,14 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters ...@@ -15,6 +15,14 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters
id : <number> id : <number>
sessionId : <string> sessionId : <string>
} }
{
error : {
code : -32602
message : Invalid CTAP version. Valid values are "ctap2_0" and "ctap2_1"
}
id : <number>
sessionId : <string>
}
{ {
error : { error : {
code : -32602 code : -32602
...@@ -31,4 +39,28 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters ...@@ -31,4 +39,28 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters
id : <number> id : <number>
sessionId : <string> sessionId : <string>
} }
{
error : {
code : -32602
message : Large blob requires resident key support
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : Large blob requires a CTAP 2.1 authenticator
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : Large blob requires a CTAP 2.1 authenticator
}
id : <number>
sessionId : <string>
}
...@@ -25,6 +25,17 @@ ...@@ -25,6 +25,17 @@
}); });
testRunner.log(protocolError); testRunner.log(protocolError);
const ctapVersionError = await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "ctap2",
ctap2Version: "nonsense",
transport: "usb",
hasResidentKey: false,
hasUserVerification: false,
},
});
testRunner.log(ctapVersionError);
const transportError = await dp.WebAuthn.addVirtualAuthenticator({ const transportError = await dp.WebAuthn.addVirtualAuthenticator({
options: { options: {
protocol: "ctap2", protocol: "ctap2",
...@@ -45,5 +56,41 @@ ...@@ -45,5 +56,41 @@
}); });
testRunner.log(u2fCableError); testRunner.log(u2fCableError);
const largeBlobRequiresRKError = await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "ctap2",
ctap2Version: "ctap2_0",
transport: "usb",
hasResidentKey: false,
hasUserVerification: false,
hasLargeBlob: true
},
});
testRunner.log(largeBlobRequiresRKError);
const largeBlobRequiresCtapError = await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "u2f",
ctap2Version: "ctap2_1",
transport: "usb",
hasResidentKey: true,
hasUserVerification: false,
hasLargeBlob: true
},
});
testRunner.log(largeBlobRequiresCtapError);
const largeBlobRequiresCtap2_1Error = await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "ctap2",
ctap2Version: "ctap2_0",
transport: "usb",
hasResidentKey: true,
hasUserVerification: false,
hasLargeBlob: true
},
});
testRunner.log(largeBlobRequiresCtap2_1Error);
testRunner.completeTest(); testRunner.completeTest();
}) })
Check that WebAuthn large blob operations work
Create credential result: OK
Large blob support: true
{
id : <number>
result : {
}
sessionId : <string>
}
Assertion result: OK
Got I'm Commander Shepard, and this is my favorite blob on the Citadel!
Got I'm Commander Shepard, and this is my favorite blob on the Citadel!
(async function(testRunner) {
const {page, session, dp} =
await testRunner.startURL(
"https://devtools.test:8443/inspector-protocol/webauthn/resources/webauthn-test.https.html",
"Check that WebAuthn large blob operations work");
// Create an authenticator.
await dp.WebAuthn.enable();
const authenticatorId = (await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "ctap2",
ctap2Version: "ctap2_1",
transport: "usb",
hasResidentKey: true,
hasUserVerification: true,
hasLargeBlob: true,
isUserVerified: true,
},
})).result.authenticatorId;
// Register a credential with a large blob through webauthn.
let result = await session.evaluateAsync(`registerCredential({
extensions: {
largeBlob: {
support: "preferred",
},
},
authenticatorSelection: {
requireResidentKey: true,
},
})`);
testRunner.log(`Create credential result: ${result.status}`);
testRunner.log(`Large blob support: ${result.largeBlobSupported}`);
// Register a credential with a large blob through devtools.
const credentialId = btoa("cred-1");
const largeBlob =
"I'm Commander Shepard, and this is my favorite blob on the Citadel!";
testRunner.log(await dp.WebAuthn.addCredential({
authenticatorId,
credential: {
credentialId,
userHandle: btoa("isabelle"),
rpId: "devtools.test",
privateKey: await session.evaluateAsync("generateBase64Key()"),
signCount: 0,
isResidentCredential: true,
largeBlob: btoa(largeBlob),
}
}));
// Read the large blob through the WebAuthn API.
result = await session.evaluateAsync(`getCredential({
type: "public-key",
id: new TextEncoder().encode("cred-1"),
transports: ["usb", "ble", "nfc"],
}, {
extensions: {
largeBlob: {
read: true,
},
},
})`);
testRunner.log(`Assertion result: ${result.status}`);
testRunner.log(`Got ${result.blob}`);
// Read the large blob through Devtools.
let credential =
(await dp.WebAuthn.getCredential({authenticatorId, credentialId})).result.credential;
testRunner.log(`Got ${atob(credential.largeBlob)}`);
testRunner.completeTest();
})
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