Commit 33562edc authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

fido/mac: truncate credential user metadata

The credential IDs of the Touch ID authenticator are basically an
AEAD of the associated PublicKeyCredentialUserEntity (= (id, name,
display name), with the RP ID as the AD. While the user ID is bounded to
64 bytes, the user name and display name are not. Instead, CTAP
authenticators are supposed to truncate them at any length larger than
64 bytes as they see fit.

The spec doesn't define an upper limit for credential IDs, but I suspect
some RPs will limit what they accept based on what they observe in
security keys from large manufacturers. Also storing potentially
unbounded IDs in attribute fields of the macOS keychain items might not
be the best idea. Hence, let's impose some (arbitrary) limit.

Bug: 1631393
Change-Id: I43cbbf3daa6e926baba7007ff99223b5666773e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1631655
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665653}
parent a6f1035a
......@@ -218,6 +218,20 @@ std::string GenerateCredentialMetadataSecret() {
return secret;
}
static std::string MaybeTruncateWithTrailingEllipsis(const std::string& in) {
constexpr size_t kMaxLength = 70u;
if (in.size() <= kMaxLength) {
return in;
}
std::string out;
// CTAP authenticators are not supposed to truncate before 64 bytes, but
// there is no truncate-with-min-size method, so truncate to a 67 byte max
// instead. Adding the 3-byte ellipsis gets us to a maximum of 70 bytes.
base::TruncateUTF8ToByteSize(in, kMaxLength - 3, &out);
out += "…"; // HORIZONTAL ELLIPSIS (E2 80 A6).
return out;
}
base::Optional<std::vector<uint8_t>> SealCredentialId(
const std::string& secret,
const std::string& rp_id,
......@@ -236,9 +250,12 @@ base::Optional<std::vector<uint8_t>> SealCredentialId(
// AES-256-GCM and authenticated with the version and RP ID.
Value::ArrayValue cbor_user;
cbor_user.emplace_back(Value(metadata.user_id));
cbor_user.emplace_back(Value(metadata.user_name, Value::Type::BYTE_STRING));
cbor_user.emplace_back(
Value(metadata.user_display_name, Value::Type::BYTE_STRING));
Value(MaybeTruncateWithTrailingEllipsis(metadata.user_name),
Value::Type::BYTE_STRING));
cbor_user.emplace_back(
Value(MaybeTruncateWithTrailingEllipsis(metadata.user_display_name),
Value::Type::BYTE_STRING));
// TODO(martinkr): Allow creation of resident keys.
cbor_user.emplace_back(Value(false));
base::Optional<std::vector<uint8_t>> pt =
......
......@@ -76,17 +76,20 @@ std::string GenerateCredentialMetadataSecret();
//
// Credential IDs have following format:
//
// | version | nonce | AEAD(pt=CBOR(user_entity), |
// | version | nonce | AEAD(pt=CBOR(metadata), |
// | (1 byte) | (12 bytes) | nonce=nonce, |
// | | | ad=(version, rpID)) |
//
// with version as 0x00, a random 12-byte nonce, and using AES-256-GCM as the
// AEAD.
//
// The |user_name| and |user_display_name| fields may be truncated before
// encryption. The truncated values are guaranteed to be valid UTF-8.
COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<std::vector<uint8_t>> SealCredentialId(
const std::string& secret,
const std::string& rp_id,
const CredentialMetadata& user);
const CredentialMetadata& metadata);
// UnsealCredentialId attempts to decrypt a CredentialMetadata from a credential
// id.
......
......@@ -129,6 +129,26 @@ TEST_F(CredentialMetadataTest, DecodeRpId) {
EXPECT_FALSE(device::fido::mac::DecodeRpId(wrong_key_, EncodeRpId()));
}
TEST_F(CredentialMetadataTest, Truncation) {
constexpr char len70[] =
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345";
constexpr char len71[] =
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456";
constexpr char truncated[] =
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012…";
auto credential_id =
SealCredentialId(CredentialMetadata({1}, len71, len71, false));
CredentialMetadata metadata = UnsealCredentialId(credential_id);
EXPECT_EQ(metadata.user_name, truncated);
EXPECT_EQ(metadata.user_display_name, truncated);
credential_id =
SealCredentialId(CredentialMetadata({1}, len70, len70, false));
metadata = UnsealCredentialId(credential_id);
EXPECT_EQ(metadata.user_name, len70);
EXPECT_EQ(metadata.user_display_name, len70);
}
TEST(CredentialMetadata, GenerateCredentialMetadataSecret) {
std::string s1 = GenerateCredentialMetadataSecret();
EXPECT_EQ(32u, s1.size());
......
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