Commit 7aeebf41 authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[webauthn] Resident support for AddCredential command

Add support for injecting resident credentials via the DevTools API. This was
missed in the original implementation. Resident Credentials need to store the
Relying Party ID and User Handle, so these two parameters are added. The
Relying Party ID hash is made redundant and therefore removed.

This is one in a series of patches intended to create a Testing API for
WebAuthn, for use in Web Platform Tests and by external webauthn tests.

For an overview of overall design, please see
https://docs.google.com/document/d/1bp2cMgjm2HSpvL9-WsJoIQMsBi1oKGQY6CvWD-9WmIQ/edit?usp=sharing

Bug: 922572
Change-Id: Ifee5edb0150593238e425fc3b2963ed04dda1609
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1728423
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Auto-Submit: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683281}
parent 9740f4ef
include_rules = [ include_rules = [
# For parsing and validating fido enums in protocol/webauthn_handler.cc. # For parsing and validating fido enums in protocol/webauthn_handler.cc.
"+device/fido/fido_constants.h", "+device/fido/fido_constants.h",
"+device/fido/fido_parsing_utils.h",
"+device/fido/fido_transport_protocol.h", "+device/fido/fido_transport_protocol.h",
# For VirtualFidoDevice::RegistrationData used in protocol/webauthn_handler.cc. # For VirtualFidoDevice::RegistrationData used in protocol/webauthn_handler.cc.
"+device/fido/virtual_fido_device.h", "+device/fido/virtual_fido_device.h",
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "content/browser/devtools/protocol/webauthn_handler.h" #include "content/browser/devtools/protocol/webauthn_handler.h"
#include <map> #include <map>
#include <string>
#include <utility>
#include <vector> #include <vector>
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
...@@ -14,6 +16,7 @@ ...@@ -14,6 +16,7 @@
#include "content/browser/webauth/virtual_authenticator.h" #include "content/browser/webauth/virtual_authenticator.h"
#include "content/browser/webauth/virtual_fido_discovery_factory.h" #include "content/browser/webauth/virtual_fido_discovery_factory.h"
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h" #include "device/fido/fido_transport_protocol.h"
#include "device/fido/virtual_fido_device.h" #include "device/fido/virtual_fido_device.h"
#include "device/fido/virtual_u2f_device.h" #include "device/fido/virtual_u2f_device.h"
...@@ -32,10 +35,16 @@ static constexpr char kDevToolsNotAttached[] = ...@@ -32,10 +35,16 @@ static constexpr char kDevToolsNotAttached[] =
"The DevTools session is not attached to a frame"; "The DevTools session is not attached to a frame";
static constexpr char kErrorCreatingAuthenticator[] = 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[] =
"The User Handle is required for Resident Credentials";
static constexpr char kInvalidProtocol[] = "The protocol is not valid"; static constexpr char kInvalidProtocol[] = "The protocol is not valid";
static constexpr char kInvalidRpIdHash[] =
"The Relying Party ID hash must have a size of ";
static constexpr char kInvalidTransport[] = "The transport 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 kResidentCredentialNotSupported[] =
"The Authenticator does not support Resident Credentials.";
static constexpr char kRpIdRequired[] =
"The Relying Party ID is a required parameter";
static constexpr char kVirtualEnvironmentNotEnabled[] = 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";
...@@ -147,19 +156,42 @@ Response WebAuthnHandler::AddCredential( ...@@ -147,19 +156,42 @@ Response WebAuthnHandler::AddCredential(
if (!response.isSuccess()) if (!response.isSuccess())
return response; return response;
if (credential->GetRpIdHash().size() != device::kRpIdHashLength) { Binary user_handle = credential->GetUserHandle(Binary());
if (credential->HasUserHandle() &&
user_handle.size() > device::kUserHandleMaxLength) {
return Response::InvalidParams( return Response::InvalidParams(
kInvalidRpIdHash + base::NumberToString(device::kRpIdHashLength)); kInvalidUserHandle +
base::NumberToString(device::kUserHandleMaxLength));
} }
if (!authenticator->AddRegistration( if (!credential->HasRpId())
CopyBinaryToVector(credential->GetCredentialId()), return Response::InvalidParams(kRpIdRequired);
CopyBinaryToVector(credential->GetRpIdHash()), std::string rp_id = credential->GetRpId("");
CopyBinaryToVector(credential->GetPrivateKey()),
credential->GetSignCount())) { bool credential_created;
return Response::Error(kCouldNotCreateCredential); if (credential->GetIsResidentCredential()) {
if (!authenticator->has_resident_key())
return Response::InvalidParams(kResidentCredentialNotSupported);
if (!credential->HasUserHandle())
return Response::InvalidParams(kHandleRequiredForResidentCredential);
credential_created = authenticator->AddResidentRegistration(
CopyBinaryToVector(credential->GetCredentialId()), rp_id,
CopyBinaryToVector(credential->GetPrivateKey()),
credential->GetSignCount(), CopyBinaryToVector(user_handle));
} else {
credential_created = authenticator->AddRegistration(
CopyBinaryToVector(credential->GetCredentialId()),
base::make_span<uint8_t, device::kRpIdHashLength>(
device::fido_parsing_utils::CreateSHA256Hash(rp_id)),
CopyBinaryToVector(credential->GetPrivateKey()),
credential->GetSignCount());
} }
if (!credential_created)
return Response::Error(kCouldNotCreateCredential);
return Response::OK(); return Response::OK();
} }
...@@ -172,19 +204,25 @@ Response WebAuthnHandler::GetCredentials( ...@@ -172,19 +204,25 @@ Response WebAuthnHandler::GetCredentials(
return response; return response;
*out_credentials = std::make_unique<Array<WebAuthn::Credential>>(); *out_credentials = std::make_unique<Array<WebAuthn::Credential>>();
for (const auto& credential : authenticator->registrations()) { for (const auto& registration : authenticator->registrations()) {
const auto& rp_id_hash = credential.second.application_parameter;
std::vector<uint8_t> private_key; std::vector<uint8_t> private_key;
credential.second.private_key->ExportPrivateKey(&private_key); registration.second.private_key->ExportPrivateKey(&private_key);
(*out_credentials)
->emplace_back( auto credential =
WebAuthn::Credential::Create() WebAuthn::Credential::Create()
.SetCredentialId(Binary::fromVector(credential.first)) .SetCredentialId(Binary::fromVector(registration.first))
.SetRpIdHash( .SetPrivateKey(Binary::fromVector(std::move(private_key)))
Binary::fromSpan(rp_id_hash.data(), rp_id_hash.size())) .SetSignCount(registration.second.counter)
.SetPrivateKey(Binary::fromVector(std::move(private_key))) .SetIsResidentCredential(registration.second.is_resident)
.SetSignCount(credential.second.counter) .Build();
.Build());
if (registration.second.rp)
credential->SetRpId(registration.second.rp->id);
if (registration.second.user) {
credential->SetUserHandle(
Binary::fromVector(registration.second.user->id));
}
(*out_credentials)->emplace_back(std::move(credential));
} }
return Response::OK(); return Response::OK();
} }
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/guid.h" #include "base/guid.h"
#include "crypto/ec_private_key.h" #include "crypto/ec_private_key.h"
#include "device/fido/public_key_credential_rp_entity.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/virtual_ctap2_device.h" #include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_u2f_device.h" #include "device/fido/virtual_u2f_device.h"
...@@ -43,26 +45,39 @@ void VirtualAuthenticator::AddBinding( ...@@ -43,26 +45,39 @@ void VirtualAuthenticator::AddBinding(
bool VirtualAuthenticator::AddRegistration( bool VirtualAuthenticator::AddRegistration(
std::vector<uint8_t> key_handle, std::vector<uint8_t> key_handle,
const std::vector<uint8_t>& rp_id_hash, base::span<const uint8_t, device::kRpIdHashLength> rp_id_hash,
const std::vector<uint8_t>& private_key, const std::vector<uint8_t>& private_key,
int32_t counter) { int32_t counter) {
if (rp_id_hash.size() != device::kRpIdHashLength)
return false;
auto ec_private_key = auto ec_private_key =
crypto::ECPrivateKey::CreateFromPrivateKeyInfo(private_key); crypto::ECPrivateKey::CreateFromPrivateKeyInfo(private_key);
if (!ec_private_key) if (!ec_private_key)
return false; return false;
return state_->registrations return state_->registrations
.emplace( .emplace(std::move(key_handle),
std::move(key_handle), ::device::VirtualFidoDevice::RegistrationData(
::device::VirtualFidoDevice::RegistrationData( std::move(ec_private_key), std::move(rp_id_hash), counter))
std::move(ec_private_key),
base::make_span<device::kRpIdHashLength>(rp_id_hash), counter))
.second; .second;
} }
bool VirtualAuthenticator::AddResidentRegistration(
std::vector<uint8_t> key_handle,
std::string rp_id,
const std::vector<uint8_t>& private_key,
int32_t counter,
std::vector<uint8_t> user_handle) {
auto ec_private_key =
crypto::ECPrivateKey::CreateFromPrivateKeyInfo(private_key);
if (!ec_private_key)
return false;
return state_->InjectResidentKey(
std::move(key_handle),
device::PublicKeyCredentialRpEntity(std::move(rp_id)),
device::PublicKeyCredentialUserEntity(std::move(user_handle)), counter,
std::move(ec_private_key));
}
void VirtualAuthenticator::ClearRegistrations() { void VirtualAuthenticator::ClearRegistrations() {
state_->registrations.clear(); state_->registrations.clear();
} }
...@@ -118,9 +133,16 @@ void VirtualAuthenticator::GetRegistrations(GetRegistrationsCallback callback) { ...@@ -118,9 +133,16 @@ void VirtualAuthenticator::GetRegistrations(GetRegistrationsCallback callback) {
void VirtualAuthenticator::AddRegistration( void VirtualAuthenticator::AddRegistration(
blink::test::mojom::RegisteredKeyPtr registration, blink::test::mojom::RegisteredKeyPtr registration,
AddRegistrationCallback callback) { AddRegistrationCallback callback) {
std::move(callback).Run(AddRegistration( if (registration->application_parameter.size() != device::kRpIdHashLength) {
std::move(registration->key_handle), registration->application_parameter, std::move(callback).Run(false);
registration->private_key, registration->counter)); return;
}
std::move(callback).Run(
AddRegistration(std::move(registration->key_handle),
base::make_span<device::kRpIdHashLength>(
registration->application_parameter),
registration->private_key, registration->counter));
} }
void VirtualAuthenticator::ClearRegistrations( void VirtualAuthenticator::ClearRegistrations(
......
...@@ -43,10 +43,19 @@ class CONTENT_EXPORT VirtualAuthenticator ...@@ -43,10 +43,19 @@ class CONTENT_EXPORT VirtualAuthenticator
// Register a new credential. Returns true if the registration was successful, // Register a new credential. Returns true if the registration was successful,
// false otherwise. // false otherwise.
bool AddRegistration(std::vector<uint8_t> key_handle, bool AddRegistration(
const std::vector<uint8_t>& rp_id_hash, std::vector<uint8_t> key_handle,
const std::vector<uint8_t>& private_key, base::span<const uint8_t, device::kRpIdHashLength> rp_id_hash,
int32_t counter); const std::vector<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,
int32_t counter,
std::vector<uint8_t> user_handle);
// Removes all the credentials. // Removes all the credentials.
void ClearRegistrations(); void ClearRegistrations();
...@@ -61,6 +70,8 @@ class CONTENT_EXPORT VirtualAuthenticator ...@@ -61,6 +70,8 @@ class CONTENT_EXPORT VirtualAuthenticator
is_user_verified_ = is_user_verified; is_user_verified_ = is_user_verified;
} }
bool has_resident_key() const { return has_resident_key_; }
::device::FidoTransportProtocol transport() const { ::device::FidoTransportProtocol transport() const {
return state_->transport; return state_->transport;
} }
......
...@@ -69,6 +69,10 @@ constexpr size_t kClientDataHashLength = 32; ...@@ -69,6 +69,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;
// Max length for the user handle:
// https://www.w3.org/TR/webauthn/#user-handle
constexpr size_t kUserHandleMaxLength = 64;
static_assert(kU2fApplicationParamLength == kRpIdHashLength, static_assert(kU2fApplicationParamLength == kRpIdHashLength,
"kU2fApplicationParamLength must be equal to kRpIdHashLength."); "kU2fApplicationParamLength must be equal to kRpIdHashLength.");
......
...@@ -83,7 +83,9 @@ bool VirtualFidoDevice::State::InjectRegistration( ...@@ -83,7 +83,9 @@ bool VirtualFidoDevice::State::InjectRegistration(
bool VirtualFidoDevice::State::InjectResidentKey( bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id, base::span<const uint8_t> credential_id,
device::PublicKeyCredentialRpEntity rp, device::PublicKeyCredentialRpEntity rp,
device::PublicKeyCredentialUserEntity user) { device::PublicKeyCredentialUserEntity user,
int32_t signature_counter,
std::unique_ptr<crypto::ECPrivateKey> private_key) {
auto application_parameter = fido_parsing_utils::CreateSHA256Hash(rp.id); auto application_parameter = fido_parsing_utils::CreateSHA256Hash(rp.id);
// Cannot create a duplicate credential for the same (RP ID, user ID) pair. // Cannot create a duplicate credential for the same (RP ID, user ID) pair.
...@@ -95,12 +97,9 @@ bool VirtualFidoDevice::State::InjectResidentKey( ...@@ -95,12 +97,9 @@ bool VirtualFidoDevice::State::InjectResidentKey(
} }
} }
auto private_key = crypto::ECPrivateKey::Create();
DCHECK(private_key);
RegistrationData registration(std::move(private_key), RegistrationData registration(std::move(private_key),
std::move(application_parameter), std::move(application_parameter),
0 /* signature counter */); signature_counter);
registration.is_resident = true; registration.is_resident = true;
registration.rp = std::move(rp); registration.rp = std::move(rp);
registration.user = std::move(user); registration.user = std::move(user);
...@@ -111,6 +110,17 @@ bool VirtualFidoDevice::State::InjectResidentKey( ...@@ -111,6 +110,17 @@ bool VirtualFidoDevice::State::InjectResidentKey(
return was_inserted; return was_inserted;
} }
bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id,
device::PublicKeyCredentialRpEntity rp,
device::PublicKeyCredentialUserEntity user) {
auto private_key = crypto::ECPrivateKey::Create();
DCHECK(private_key);
return InjectResidentKey(std::move(credential_id), std::move(rp),
std::move(user), /*signature_counter=*/0,
std::move(private_key));
}
bool VirtualFidoDevice::State::InjectResidentKey( bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id, base::span<const uint8_t> credential_id,
const std::string& relying_party_id, const std::string& relying_party_id,
......
...@@ -157,7 +157,18 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { ...@@ -157,7 +157,18 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
bool InjectRegistration(base::span<const uint8_t> credential_id, bool InjectRegistration(base::span<const uint8_t> credential_id,
const std::string& relying_party_id); const std::string& relying_party_id);
// InjectResidentKey adds a resident credential with the specified values. // Adds a resident credential with the specified values.
// Returns false if there already exists a resident credential for the same
// (RP ID, user ID) pair, or for the same credential ID. Otherwise returns
// true.
bool InjectResidentKey(base::span<const uint8_t> credential_id,
device::PublicKeyCredentialRpEntity rp,
device::PublicKeyCredentialUserEntity user,
int32_t signature_counter,
std::unique_ptr<crypto::ECPrivateKey> private_key);
// Adds a resident credential with the specified values, creating a new
// private key.
// Returns false if there already exists a resident credential for the same // Returns false if there already exists a resident credential for the same
// (RP ID, user ID) pair, or for the same credential ID. Otherwise returns // (RP ID, user ID) pair, or for the same credential ID. Otherwise returns
// true. // true.
......
...@@ -7109,12 +7109,15 @@ experimental domain WebAuthn ...@@ -7109,12 +7109,15 @@ experimental domain WebAuthn
type Credential extends object type Credential extends object
properties properties
binary credentialId binary credentialId
# SHA-256 hash of the Relying Party ID the credential is scoped to. Must boolean isResidentCredential
# be 32 bytes long. # Relying Party ID the credential is scoped to. Must be set when adding a
# See https://w3c.github.io/webauthn/#rpidhash # credential.
binary rpIdHash optional string rpId
# The private key in PKCS#8 format. # The ECDSA P-256 private key in PKCS#8 format.
binary privateKey binary privateKey
# An opaque byte sequence with a maximum size of 64 bytes mapping the
# credential to a specific user.
optional binary userHandle
# Signature counter. This is incremented by one for each successful # Signature counter. This is incremented by one for each successful
# assertion. # assertion.
# See https://w3c.github.io/webauthn/#signature-counter # See https://w3c.github.io/webauthn/#signature-counter
......
...@@ -18,7 +18,31 @@ Check that the WebAuthn command addCredential validates parameters ...@@ -18,7 +18,31 @@ Check that the WebAuthn command addCredential validates parameters
{ {
error : { error : {
code : -32602 code : -32602
message : The Relying Party ID hash must have a size of 32 message : The Relying Party ID is a required parameter
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : The Authenticator does not support Resident Credentials.
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : The User Handle is required for Resident Credentials
}
id : <number>
sessionId : <string>
}
{
error : {
code : -32602
message : The User Handle must have a maximum size of 64
} }
id : <number> id : <number>
sessionId : <string> sessionId : <string>
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
credential: { credential: {
credentialId: btoa(credentialId), credentialId: btoa(credentialId),
privateKey: btoa("invalid private key"), privateKey: btoa("invalid private key"),
rpIdHash: btoa("invalid hash"),
signCount: 0, signCount: 0,
isResidentCredential: true,
} }
}; };
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
await dp.WebAuthn.enable(); await dp.WebAuthn.enable();
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions)); testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try with an invalid RP ID hash. // Try without an RP ID.
credentialOptions.authenticatorId = (await dp.WebAuthn.addVirtualAuthenticator({ credentialOptions.authenticatorId = (await dp.WebAuthn.addVirtualAuthenticator({
options: { options: {
protocol: "ctap2", protocol: "ctap2",
...@@ -33,9 +33,31 @@ ...@@ -33,9 +33,31 @@
})).result.authenticatorId; })).result.authenticatorId;
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions)); testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try registering a resident credential on an authenticator not capable of
// resident credentials.
credentialOptions.credential.rpId = "devtools.test";
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try registering a resident credential without a user handle.
credentialOptions.authenticatorId = (await dp.WebAuthn.addVirtualAuthenticator({
options: {
protocol: "ctap2",
transport: "usb",
hasResidentKey: true,
hasUserVerification: false,
},
})).result.authenticatorId;
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try a user handle that exceeds the max size.
const MAX_USER_HANDLE_SIZE = 64;
const longHandle = "a".repeat(MAX_USER_HANDLE_SIZE + 1);
credentialOptions.credential.userHandle = btoa(longHandle);
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
// Try with a private key that is not valid. // Try with a private key that is not valid.
credentialOptions.credential.rpIdHash = credentialOptions.credential.userHandle = btoa("nina");
await session.evaluateAsync("generateRpIdHash()");
testRunner.log(await dp.WebAuthn.addCredential(credentialOptions)); testRunner.log(await dp.WebAuthn.addCredential(credentialOptions));
testRunner.completeTest(); testRunner.completeTest();
}) })
...@@ -10,4 +10,15 @@ Check that the WebAuthn command addCredential works ...@@ -10,4 +10,15 @@ Check that the WebAuthn command addCredential works
} }
status : OK status : OK
} }
{
id : <number>
result : {
}
sessionId : <string>
}
{
attestation : {
}
status : OK
}
...@@ -10,28 +10,50 @@ ...@@ -10,28 +10,50 @@
options: { options: {
protocol: "ctap2", protocol: "ctap2",
transport: "usb", transport: "usb",
hasResidentKey: false, hasResidentKey: true,
hasUserVerification: false, hasUserVerification: false,
}, },
})).result.authenticatorId; })).result.authenticatorId;
// Register a credential. // Register a non-resident credential.
const credentialId = "cred-1"; const nonResidentCredentialId = "cred-1";
const addCredentialResult = (await dp.WebAuthn.addCredential({ testRunner.log(await dp.WebAuthn.addCredential({
authenticatorId, authenticatorId,
credential: { credential: {
credentialId: btoa(credentialId), credentialId: btoa(nonResidentCredentialId),
rpIdHash: await session.evaluateAsync("generateRpIdHash()"), rpId: "devtools.test",
privateKey: await session.evaluateAsync("generateBase64Key()"), privateKey: await session.evaluateAsync("generateBase64Key()"),
signCount: 0, signCount: 0,
isResidentCredential: false,
} }
})); }));
testRunner.log(addCredentialResult);
// Authenticate with the registered credential. // Authenticate with the non-resident credential.
testRunner.log(await session.evaluateAsync(`getCredential({ testRunner.log(await session.evaluateAsync(`getCredential({
type: "public-key", type: "public-key",
id: new TextEncoder().encode("${credentialId}"), id: new TextEncoder().encode("${nonResidentCredentialId}"),
transports: ["usb", "ble", "nfc"],
})`));
// Register a resident credential.
const userHandle = "nina";
const residentCredentialId = "cred-2";
testRunner.log(await dp.WebAuthn.addCredential({
authenticatorId,
credential: {
credentialId: btoa(residentCredentialId),
rpId: "devtools.test",
privateKey: await session.evaluateAsync("generateBase64Key()"),
signCount: 0,
isResidentCredential: true,
userHandle: btoa(userHandle),
}
}));
// Authenticate with the resident credential.
testRunner.log(await session.evaluateAsync(`getCredential({
type: "public-key",
id: new TextEncoder().encode("${residentCredentialId}"),
transports: ["usb", "ble", "nfc"], transports: ["usb", "ble", "nfc"],
})`)); })`));
......
...@@ -8,11 +8,22 @@ Check that the WebAuthn command getCredentials works ...@@ -8,11 +8,22 @@ Check that the WebAuthn command getCredentials works
sessionId : <string> sessionId : <string>
} }
OK OK
OK {
RP ID hash matches expected value id : <number>
1 result : {
RP ID hash matches expected value }
1 sessionId : <string>
}
Resident Credential:
isResidentCredential: true
signCount: 1
rpId: devtools.test
userHandle: nina
Non-Resident Credential:
isResidentCredential: false
signCount: 1
rpId: undefined
userHandle:
{ {
attestation : { attestation : {
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
options: { options: {
protocol: "ctap2", protocol: "ctap2",
transport: "usb", transport: "usb",
hasResidentKey: false, hasResidentKey: true,
hasUserVerification: false, hasUserVerification: false,
}, },
})).result.authenticatorId; })).result.authenticatorId;
...@@ -17,37 +17,56 @@ ...@@ -17,37 +17,56 @@
// No credentials registered yet. // No credentials registered yet.
testRunner.log(await dp.WebAuthn.getCredentials({authenticatorId})); testRunner.log(await dp.WebAuthn.getCredentials({authenticatorId}));
// Register two credentials. // Register a non-resident credential.
testRunner.log((await session.evaluateAsync("registerCredential()")).status);
testRunner.log((await session.evaluateAsync("registerCredential()")).status); testRunner.log((await session.evaluateAsync("registerCredential()")).status);
// TODO(nsatragno): content_shell does not support registering resident
// credentials through navigator.credentials.create(). Update this test to use
// registerCredential() once that feature is supported.
const userHandle = "nina";
const credentialId = "cred-2";
testRunner.log(await dp.WebAuthn.addCredential({
authenticatorId,
credential: {
credentialId: btoa(credentialId),
rpId: "devtools.test",
privateKey: await session.evaluateAsync("generateBase64Key()"),
signCount: 1,
isResidentCredential: true,
userHandle: btoa(userHandle),
}
}));
let logCredential = credential => {
testRunner.log("isResidentCredential: " + credential.isResidentCredential);
testRunner.log("signCount: " + credential.signCount);
testRunner.log("rpId: " + credential.rpId);
testRunner.log("userHandle: " + atob(credential.userHandle || ""));
};
// Get the registered credentials. // Get the registered credentials.
let credentials = (await dp.WebAuthn.getCredentials({authenticatorId})).result.credentials; let credentials = (await dp.WebAuthn.getCredentials({authenticatorId})).result.credentials;
let expectedRpIdHash = await session.evaluateAsync("generateRpIdHash()"); let residentCredential = credentials.find(cred => cred.isResidentCredential);
for (let credential of credentials) { let nonResidentCredential = credentials.find(cred => !cred.isResidentCredential);
if (credential.rpIdHash === expectedRpIdHash) testRunner.log("Resident Credential:");
testRunner.log("RP ID hash matches expected value"); logCredential(residentCredential);
else testRunner.log("Non-Resident Credential:");
testRunner.log(`RP ID hash does not match. Actual: ${credential.rpIdHash}, expected: ${expectedRpIdHash}`); logCredential(nonResidentCredential);
testRunner.log(credential.signCount);
} // Authenticating with the non resident credential should succeed.
// Authenticating with the first credential should succeed.
let credential = credentials[0];
testRunner.log(await session.evaluateAsync(`getCredential({ testRunner.log(await session.evaluateAsync(`getCredential({
type: "public-key", type: "public-key",
id: base64ToArrayBuffer("${credential.credentialId}"), id: base64ToArrayBuffer("${nonResidentCredential.credentialId}"),
transports: ["usb", "ble", "nfc"], transports: ["usb", "ble", "nfc"],
})`)); })`));
// Sign count should be increased by one for |credential|. // Sign count should be increased by one for |nonResidentCredential|.
credentials = (await dp.WebAuthn.getCredentials({authenticatorId})).result.credentials; credentials = (await dp.WebAuthn.getCredentials({authenticatorId})).result.credentials;
testRunner.log(credentials.find( testRunner.log(credentials.find(
cred => cred.id === credential.id).signCount); cred => cred.credentialId === nonResidentCredential.credentialId).signCount);
// We should be able to parse the private key. // We should be able to parse the private key.
let keyData = let keyData =
Uint8Array.from(atob(credential.privateKey), c => c.charCodeAt(0)).buffer; Uint8Array.from(atob(nonResidentCredential.privateKey), c => c.charCodeAt(0)).buffer;
let key = await window.crypto.subtle.importKey( let key = await window.crypto.subtle.importKey(
"pkcs8", keyData, { name: "ECDSA", namedCurve: "P-256" }, "pkcs8", keyData, { name: "ECDSA", namedCurve: "P-256" },
true /* extractable */, ["sign"]); true /* extractable */, ["sign"]);
......
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