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 @@
#include <utility>
#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_piece.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
......@@ -30,6 +33,8 @@ static constexpr char kCableNotSupportedOnU2f[] =
"U2F only supports the \"usb\", \"ble\" and \"nfc\" transports";
static constexpr char kCouldNotCreateCredential[] =
"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[] =
"Could not find a credential matching the ID";
static constexpr char kDevToolsNotAttached[] =
......@@ -38,10 +43,16 @@ static constexpr char kErrorCreatingAuthenticator[] =
"An error occurred when trying to create the authenticator";
static constexpr char kHandleRequiredForResidentCredential[] =
"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 kInvalidTransport[] = "The transport is not valid";
static constexpr char kInvalidUserHandle[] =
"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[] =
"The Authenticator does not support Resident Credentials.";
static constexpr char kRpIdRequired[] =
......@@ -50,6 +61,38 @@ static constexpr char kVirtualEnvironmentNotEnabled[] =
"The Virtual Authenticator Environment has not been enabled for this "
"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) {
if (protocol == WebAuthn::AuthenticatorProtocolEnum::Ctap2)
return device::ProtocolVersion::kCtap2;
......@@ -58,6 +101,15 @@ device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) {
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) {
return std::vector<uint8_t>(binary.data(), binary.data() + binary.size());
}
......@@ -143,6 +195,20 @@ Response WebAuthnHandler::AddVirtualAuthenticator(
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;
switch (protocol) {
case device::ProtocolVersion::kU2f:
......@@ -150,13 +216,12 @@ Response WebAuthnHandler::AddVirtualAuthenticator(
break;
case device::ProtocolVersion::kCtap2:
authenticator = authenticator_manager->CreateCTAP2Authenticator(
device::Ctap2Version::kCtap2_0, *transport,
*ctap2_version, *transport,
transport == device::FidoTransportProtocol::kInternal
? device::AuthenticatorAttachment::kPlatform
: device::AuthenticatorAttachment::kCrossPlatform,
options->GetHasResidentKey(/*default=*/false),
options->GetHasUserVerification(/*default=*/false),
options->GetHasLargeBlob(/*default=*/false));
has_resident_key, options->GetHasUserVerification(/*default=*/false),
has_large_blob);
break;
case device::ProtocolVersion::kUnknown:
NOTREACHED();
......@@ -188,84 +253,136 @@ Response WebAuthnHandler::RemoveVirtualAuthenticator(
return Response::Success();
}
Response WebAuthnHandler::AddCredential(
void WebAuthnHandler::AddCredential(
const String& authenticator_id,
std::unique_ptr<WebAuthn::Credential> credential) {
std::unique_ptr<WebAuthn::Credential> credential,
std::unique_ptr<AddCredentialCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
if (!response.IsSuccess()) {
callback->sendFailure(std::move(response));
return;
}
Binary user_handle = credential->GetUserHandle(Binary());
if (credential->HasUserHandle() &&
user_handle.size() > device::kUserHandleMaxLength) {
return Response::InvalidParams(
callback->sendFailure(Response::InvalidParams(
kInvalidUserHandle +
base::NumberToString(device::kUserHandleMaxLength));
base::NumberToString(device::kUserHandleMaxLength)));
return;
}
if (!credential->HasRpId())
return Response::InvalidParams(kRpIdRequired);
if (!credential->HasRpId()) {
callback->sendFailure(Response::InvalidParams(kRpIdRequired));
return;
}
if (credential->HasLargeBlob() && !credential->GetIsResidentCredential()) {
callback->sendFailure(
Response::InvalidParams(kLargeBlobRequiresResidentKey));
return;
}
bool credential_created;
std::vector<uint8_t> credential_id =
CopyBinaryToVector(credential->GetCredentialId());
if (credential->GetIsResidentCredential()) {
if (!authenticator->has_resident_key())
return Response::InvalidParams(kResidentCredentialNotSupported);
if (!credential->HasUserHandle())
return Response::InvalidParams(kHandleRequiredForResidentCredential);
if (!authenticator->has_resident_key()) {
callback->sendFailure(
Response::InvalidParams(kResidentCredentialNotSupported));
return;
}
if (!credential->HasUserHandle()) {
callback->sendFailure(
Response::InvalidParams(kHandleRequiredForResidentCredential));
return;
}
credential_created = authenticator->AddResidentRegistration(
CopyBinaryToVector(credential->GetCredentialId()),
credential->GetRpId(""),
CopyBinaryToVector(credential->GetPrivateKey()),
credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetSignCount(), CopyBinaryToVector(user_handle));
} else {
credential_created = authenticator->AddRegistration(
CopyBinaryToVector(credential->GetCredentialId()),
credential->GetRpId(""),
CopyBinaryToVector(credential->GetPrivateKey()),
credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetSignCount());
}
if (!credential_created)
return Response::ServerError(kCouldNotCreateCredential);
if (!credential_created) {
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 Binary& credential_id,
std::unique_ptr<WebAuthn::Credential>* out_credential) {
std::unique_ptr<GetCredentialCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
if (!response.IsSuccess()) {
callback->sendFailure(response);
return;
}
auto registration =
authenticator->registrations().find(CopyBinaryToVector(credential_id));
if (registration == authenticator->registrations().end())
return Response::InvalidParams(kCredentialNotFound);
if (registration == authenticator->registrations().end()) {
callback->sendFailure(Response::InvalidParams(kCredentialNotFound));
return;
}
*out_credential = BuildCredentialFromRegistration(*registration);
return Response::Success();
authenticator->GetLargeBlob(
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,
std::unique_ptr<Array<WebAuthn::Credential>>* out_credentials) {
std::unique_ptr<GetCredentialsCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
if (!response.IsSuccess()) {
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()) {
(*out_credentials)
->emplace_back(BuildCredentialFromRegistration(registration));
authenticator->GetLargeBlob(
registration.first,
base::BindOnce(&GetCredentialCallbackAggregator::OnLargeBlob,
aggregator,
BuildCredentialFromRegistration(registration)));
}
return Response::Success();
}
Response WebAuthnHandler::RemoveCredential(const String& authenticator_id,
......
......@@ -20,6 +20,8 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
public:
CONTENT_EXPORT WebAuthnHandler();
CONTENT_EXPORT ~WebAuthnHandler() override;
WebAuthnHandler(const WebAuthnHandler&) = delete;
WebAuthnHandler operator=(const WebAuthnHandler&) = delete;
// DevToolsDomainHandler:
void SetRenderer(int process_host_id,
......@@ -33,17 +35,15 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
std::unique_ptr<WebAuthn::VirtualAuthenticatorOptions> options,
String* out_authenticator_id) override;
Response RemoveVirtualAuthenticator(const String& authenticator_id) override;
Response AddCredential(
void AddCredential(const String& authenticator_id,
std::unique_ptr<protocol::WebAuthn::Credential> credential,
std::unique_ptr<AddCredentialCallback> callback) override;
void GetCredential(const String& authenticator_id,
const Binary& credential_id,
std::unique_ptr<GetCredentialCallback> callback) override;
void GetCredentials(
const String& authenticator_id,
std::unique_ptr<protocol::WebAuthn::Credential> credential) override;
Response GetCredential(
const String& authenticator_id,
const Binary& credential_id,
std::unique_ptr<WebAuthn::Credential>* out_credential) override;
Response GetCredentials(
const String& authenticator_id,
std::unique_ptr<protocol::Array<protocol::WebAuthn::Credential>>*
out_credentials) override;
std::unique_ptr<GetCredentialsCallback> callback) override;
Response RemoveCredential(const String& in_authenticator_id,
const Binary& credential_id) override;
Response ClearCredentials(const String& in_authenticator_id) override;
......@@ -58,7 +58,6 @@ class WebAuthnHandler : public DevToolsDomainHandler, public WebAuthn::Backend {
Response FindAuthenticator(const String& id,
VirtualAuthenticator** out_authenticator);
RenderFrameHostImpl* frame_host_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WebAuthnHandler);
};
} // namespace protocol
......
......@@ -110,7 +110,8 @@
},
{
"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(
bool VirtualAuthenticator::AddRegistration(
std::vector<uint8_t> key_handle,
const std::string& rp_id,
const std::vector<uint8_t>& private_key,
base::span<const uint8_t> private_key,
int32_t counter) {
base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
fido_private_key =
......@@ -71,7 +71,7 @@ bool VirtualAuthenticator::AddRegistration(
bool VirtualAuthenticator::AddResidentRegistration(
std::vector<uint8_t> key_handle,
std::string rp_id,
const std::vector<uint8_t>& private_key,
base::span<const uint8_t> private_key,
int32_t counter,
std::vector<uint8_t> user_handle) {
base::Optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
......@@ -221,7 +221,7 @@ void VirtualAuthenticator::OnLargeBlobUncompressed(
}
void VirtualAuthenticator::OnLargeBlobCompressed(
const std::vector<uint8_t>& key_handle,
base::span<const uint8_t> key_handle,
SetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result) {
auto registration = state_->registrations.find(key_handle);
......
......@@ -9,6 +9,7 @@
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
......@@ -52,14 +53,14 @@ class CONTENT_EXPORT VirtualAuthenticator
// false otherwise.
bool AddRegistration(std::vector<uint8_t> key_handle,
const std::string& rp_id,
const std::vector<uint8_t>& private_key,
base::span<const uint8_t> private_key,
int32_t counter);
// Register a new resident credential. Returns true if the registration was
// successful, false otherwise.
bool AddResidentRegistration(std::vector<uint8_t> key_handle,
std::string rp_id,
const std::vector<uint8_t>& private_key,
base::span<const uint8_t> private_key,
int32_t counter,
std::vector<uint8_t> user_handle);
......@@ -70,11 +71,6 @@ class CONTENT_EXPORT VirtualAuthenticator
// credential was found and removed, false otherwise.
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
// this authenticator. The default is true.
void SetUserPresence(bool is_user_present);
......@@ -129,7 +125,7 @@ class CONTENT_EXPORT VirtualAuthenticator
GetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result);
void OnLargeBlobCompressed(
const std::vector<uint8_t>& key_handle,
base::span<const uint8_t> key_handle,
SetLargeBlobCallback callback,
data_decoder::DataDecoder::ResultOrError<mojo_base::BigBuffer> result);
......
......@@ -564,8 +564,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
if (config.large_blob_support) {
DCHECK(config.resident_key_support);
DCHECK(base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1));
DCHECK(config.pin_uv_auth_token_support)
<< "PinUvAuthToken support is required to write large blobs";
DCHECK((!config.pin_support && !config.internal_uv_support) ||
config.pin_uv_auth_token_support)
<< "PinUvAuthToken support is required to write large blobs for "
"uv-enabled authenticators";
options_updated = true;
options.supports_large_blobs = true;
}
......
......@@ -8255,6 +8255,11 @@ experimental domain WebAuthn
# Client To Authenticator Protocol 2.
ctap2
type Ctap2Version extends string
enum
ctap2_0
ctap2_1
type AuthenticatorTransport extends string
enum
# Cross-Platform authenticator attachments:
......@@ -8268,6 +8273,8 @@ experimental domain WebAuthn
type VirtualAuthenticatorOptions extends object
properties
AuthenticatorProtocol protocol
# Defaults to ctap2_0. Ignored if |protocol| == u2f.
optional Ctap2Version ctap2Version
AuthenticatorTransport transport
# Defaults to false.
optional boolean hasResidentKey
......@@ -8300,6 +8307,9 @@ experimental domain WebAuthn
# assertion.
# See https://w3c.github.io/webauthn/#signature-counter
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
# retrieval with a virtual authenticator.
......
......@@ -58,14 +58,19 @@ async function registerCredential(options = {}) {
try {
const credential = await navigator.credentials.create({publicKey: options});
return {
let result = {
status: "OK",
credential: {
id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)),
transports: credential.response.getTransports(),
}
},
};
if (credential.getClientExtensionResults().largeBlob) {
result.largeBlobSupported =
credential.getClientExtensionResults().largeBlob.supported;
}
return result;
} catch (error) {
return {status: error.toString()};
}
......@@ -81,10 +86,15 @@ async function getCredential(credential, options = {}) {
try {
const attestation = await navigator.credentials.get({publicKey: options});
return {
let result = {
status: "OK",
attestation,
};
if (attestation.getClientExtensionResults().largeBlob) {
result.blob = new TextDecoder().decode(
attestation.getClientExtensionResults().largeBlob.blob);
}
return result;
} catch (error) {
return {status: error.toString()};
}
......
......@@ -55,4 +55,12 @@ Check that the WebAuthn command addCredential validates parameters
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : Large blob requires resident key support
}
id : <number>
sessionId : <string>
}
......@@ -59,5 +59,12 @@
credentialOptions.credential.userHandle = btoa("nina");
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();
})
......@@ -15,6 +15,14 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : Invalid CTAP version. Valid values are "ctap2_0" and "ctap2_1"
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
......@@ -31,4 +39,28 @@ Check that the WebAuthn addVirtualAuthenticator command validates parameters
id : <number>
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 @@
});
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({
options: {
protocol: "ctap2",
......@@ -45,5 +56,41 @@
});
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();
})
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