Commit 3ee90626 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

fido/mac: store is_resident boolean in credential metadata

This renames the UserEntity struct to Credential Metadata and adds an
|is_resident| bool field to track whether a credential in the macOS keychain
was created by a WebAuthn create() request with requireResidentKey set to true.
The credential ID format is evolved accordingly and legacy credentials created
before the introduction of this field will default to is_resident=false.

The calling code in the MakeCredential operation still refuses to create
resident credentials, so this change still does not enable creation of resident
credentials.

Bug: 1631393
Change-Id: I602195c9d240343eca330c8ddedec61133961bbc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1631371
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665287}
parent 9c8b9003
This diff is collapsed.
......@@ -28,25 +28,36 @@ class PublicKeyCredentialUserEntity;
namespace fido {
namespace mac {
// UserEntity loosely corresponds to a PublicKeyCredentialUserEntity
// (https://www.w3.org/TR/webauthn/#sctn-user-credential-params). Values of
// this type should be moved whenever possible.
struct COMPONENT_EXPORT(DEVICE_FIDO) UserEntity {
// CredentialMetadata is the metadata for a Touch ID credential stored, in an
// encrypted/authenticated format, in the macOS keychain. Values of this type
// should be moved whenever possible.
struct COMPONENT_EXPORT(DEVICE_FIDO) CredentialMetadata {
public:
static UserEntity FromPublicKeyCredentialUserEntity(
const PublicKeyCredentialUserEntity&);
UserEntity(std::vector<uint8_t> id_, std::string name_, std::string display_);
UserEntity(const UserEntity&);
UserEntity(UserEntity&&);
UserEntity& operator=(UserEntity&&);
~UserEntity();
static CredentialMetadata FromPublicKeyCredentialUserEntity(
const PublicKeyCredentialUserEntity&,
bool is_resident);
CredentialMetadata(std::vector<uint8_t> user_id_,
std::string user_name_,
std::string user_display_name_,
bool is_resident_);
CredentialMetadata(const CredentialMetadata&);
CredentialMetadata(CredentialMetadata&&);
CredentialMetadata& operator=(CredentialMetadata&&);
~CredentialMetadata();
PublicKeyCredentialUserEntity ToPublicKeyCredentialUserEntity();
std::vector<uint8_t> id;
std::string name;
std::string display_name;
// The following correspond to the fields of the same name in
// PublicKeyCredentialUserEntity
// (https://www.w3.org/TR/webauthn/#sctn-user-credential-params).
std::vector<uint8_t> user_id;
std::string user_name;
std::string user_display_name;
// Whether this credential has the resident key (rk) bit and may be returned
// in response to GetAssertion requests with an empty allowList.
bool is_resident;
};
// Generates a random secret for encrypting and authenticating credential
......@@ -61,7 +72,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) UserEntity {
COMPONENT_EXPORT(DEVICE_FIDO)
std::string GenerateCredentialMetadataSecret();
// SealCredentialId encrypts the given UserEntity into a credential id.
// SealCredentialId encrypts the given CredentialMetadata into a credential id.
//
// Credential IDs have following format:
//
......@@ -72,13 +83,15 @@ std::string GenerateCredentialMetadataSecret();
// with version as 0x00, a random 12-byte nonce, and using AES-256-GCM as the
// AEAD.
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<std::vector<uint8_t>> SealCredentialId(const std::string& secret,
const std::string& rp_id,
const UserEntity& user);
base::Optional<std::vector<uint8_t>> SealCredentialId(
const std::string& secret,
const std::string& rp_id,
const CredentialMetadata& user);
// UnsealCredentialId attempts to decrypt a UserEntity from a credential id.
// UnsealCredentialId attempts to decrypt a CredentialMetadata from a credential
// id.
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<UserEntity> UnsealCredentialId(
base::Optional<CredentialMetadata> UnsealCredentialId(
const std::string& secret,
const std::string& rp_id,
base::span<const uint8_t> credential_id);
......@@ -108,6 +121,15 @@ COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<std::string> DecodeRpId(const std::string& secret,
const std::string& ciphertext);
// Seals a legacy V0 credential ID.
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<std::vector<uint8_t>> SealLegacyV0CredentialIdForTestingOnly(
const std::string& secret,
const std::string& rp_id,
const std::vector<uint8_t>& user_id,
const std::string& user_name,
const std::string& user_display_name);
} // namespace mac
} // namespace fido
} // namespace device
......
......@@ -4,6 +4,9 @@
#include "device/fido/mac/credential_metadata.h"
#include "base/logging.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
......@@ -13,9 +16,10 @@ namespace fido {
namespace mac {
namespace {
bool UserEqual(const UserEntity& lhs, const UserEntity& rhs) {
return lhs.id == rhs.id && lhs.name == rhs.name &&
lhs.display_name == rhs.display_name;
bool MetadataEq(const CredentialMetadata& lhs, const CredentialMetadata& rhs) {
return lhs.user_id == rhs.user_id && lhs.user_name == rhs.user_name &&
lhs.user_display_name == rhs.user_display_name &&
lhs.is_resident == rhs.is_resident;
}
base::span<const uint8_t> to_bytes(base::StringPiece in) {
......@@ -25,15 +29,17 @@ base::span<const uint8_t> to_bytes(base::StringPiece in) {
class CredentialMetadataTest : public ::testing::Test {
protected:
UserEntity DefaultUser() {
return UserEntity(default_id_, "user", "user@acme.com");
CredentialMetadata DefaultUser() {
return CredentialMetadata(default_id_, "user", "user@acme.com",
/*is_resident=*/false);
}
std::vector<uint8_t> SealCredentialId(UserEntity user) {
std::vector<uint8_t> SealCredentialId(CredentialMetadata user) {
return *device::fido::mac::SealCredentialId(key_, rp_id_, std::move(user));
}
UserEntity UnsealCredentialId(base::span<const uint8_t> credential_id) {
CredentialMetadata UnsealCredentialId(
base::span<const uint8_t> credential_id) {
return *device::fido::mac::UnsealCredentialId(key_, rp_id_, credential_id);
}
......@@ -57,9 +63,18 @@ class CredentialMetadataTest : public ::testing::Test {
TEST_F(CredentialMetadataTest, CredentialId) {
std::vector<uint8_t> id = SealCredentialId(DefaultUser());
EXPECT_EQ(0, (id)[0]);
EXPECT_EQ(54u, id.size());
EXPECT_TRUE(UserEqual(UnsealCredentialId(id), DefaultUser()));
EXPECT_EQ(1, (id)[0]);
EXPECT_EQ(55u, id.size());
EXPECT_TRUE(MetadataEq(UnsealCredentialId(id), DefaultUser()));
}
TEST_F(CredentialMetadataTest, LegacyCredentialId) {
auto user = DefaultUser();
auto id = SealLegacyV0CredentialIdForTestingOnly(
key_, rp_id_, user.user_id, user.user_name, user.user_display_name);
EXPECT_EQ(0, (*id)[0]);
EXPECT_EQ(54u, id->size());
EXPECT_TRUE(MetadataEq(UnsealCredentialId(*id), DefaultUser()));
}
TEST_F(CredentialMetadataTest, CredentialId_IsRandom) {
......@@ -128,15 +143,19 @@ TEST(CredentialMetadata, FromPublicKeyCredentialUserEntity) {
in.name = "username";
in.display_name = "display name";
in.icon_url = GURL("http://rp.foo/user.png");
UserEntity out = UserEntity::FromPublicKeyCredentialUserEntity(std::move(in));
EXPECT_EQ(user_id, out.id);
EXPECT_EQ("username", out.name);
EXPECT_EQ("display name", out.display_name);
CredentialMetadata out =
CredentialMetadata::FromPublicKeyCredentialUserEntity(
std::move(in), /*is_resident=*/false);
EXPECT_EQ(user_id, out.user_id);
EXPECT_EQ("username", out.user_name);
EXPECT_EQ("display name", out.user_display_name);
EXPECT_FALSE(out.is_resident);
}
TEST(CredentialMetadata, ToPublicKeyCredentialUserEntity) {
std::vector<uint8_t> user_id = {{1, 2, 3}};
UserEntity in(user_id, "username", "display name");
CredentialMetadata in(user_id, "username", "display name",
/*is_resident=*/false);
PublicKeyCredentialUserEntity out = in.ToPublicKeyCredentialUserEntity();
EXPECT_EQ(user_id, out.id);
EXPECT_EQ("username", out.name.value());
......
......@@ -93,9 +93,9 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) {
}
// Decrypt the user entity from the credential ID.
base::Optional<UserEntity> credential_user =
base::Optional<CredentialMetadata> metadata =
UnsealCredentialId(metadata_secret(), RpId(), credential->credential_id);
if (!credential_user) {
if (!metadata) {
// The keychain query already filtered for the RP ID encoded under this
// operation's metadata secret, so the credential id really should have
// been decryptable.
......@@ -115,11 +115,11 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) {
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
auto response = AuthenticatorGetAssertionResponse(
std::move(authenticator_data), std::move(*signature));
AuthenticatorGetAssertionResponse response(std::move(authenticator_data),
std::move(*signature));
response.SetCredential(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey, std::move(credential->credential_id)));
response.SetUserEntity(credential_user->ToPublicKeyCredentialUserEntity());
response.SetUserEntity(metadata->ToPublicKeyCredentialUserEntity());
std::move(callback())
.Run(CtapDeviceResponseCode::kSuccess, std::move(response));
......
......@@ -229,9 +229,11 @@ void MakeCredentialOperation::PromptTouchIdDone(bool success) {
base::Optional<std::vector<uint8_t>>
MakeCredentialOperation::GenerateCredentialIdForRequest() const {
return SealCredentialId(
metadata_secret(), RpId(),
UserEntity::FromPublicKeyCredentialUserEntity(request().user));
// TODO(martinkr): Handle resident key creation.
return SealCredentialId(metadata_secret(), RpId(),
CredentialMetadata::FromPublicKeyCredentialUserEntity(
request().user,
/*is_resident=*/false));
}
} // namespace mac
......
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