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