1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "device/fido/mac/credential_metadata.h"
6
7 #include "base/logging.h"
8 #include "components/cbor/values.h"
9 #include "components/cbor/writer.h"
10 #include "device/fido/public_key_credential_user_entity.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "url/gurl.h"
13
14 namespace device {
15 namespace fido {
16 namespace mac {
17 namespace {
18
MetadataEq(const CredentialMetadata & lhs,const CredentialMetadata & rhs)19 bool MetadataEq(const CredentialMetadata& lhs, const CredentialMetadata& rhs) {
20 return lhs.user_id == rhs.user_id && lhs.user_name == rhs.user_name &&
21 lhs.user_display_name == rhs.user_display_name &&
22 lhs.is_resident == rhs.is_resident;
23 }
24
to_bytes(base::StringPiece in)25 base::span<const uint8_t> to_bytes(base::StringPiece in) {
26 return base::make_span(reinterpret_cast<const uint8_t*>(in.data()),
27 in.size());
28 }
29
30 class CredentialMetadataTest : public ::testing::Test {
31 protected:
DefaultUser()32 CredentialMetadata DefaultUser() {
33 return CredentialMetadata(default_id_, "user", "user@acme.com",
34 /*is_resident=*/false);
35 }
36
SealCredentialId(CredentialMetadata user)37 std::vector<uint8_t> SealCredentialId(CredentialMetadata user) {
38 return device::fido::mac::SealCredentialId(key_, rp_id_, std::move(user));
39 }
40
UnsealCredentialId(base::span<const uint8_t> credential_id)41 CredentialMetadata UnsealCredentialId(
42 base::span<const uint8_t> credential_id) {
43 return *device::fido::mac::UnsealCredentialId(key_, rp_id_, credential_id);
44 }
45
EncodeRpIdAndUserId(base::StringPiece user_id)46 std::string EncodeRpIdAndUserId(base::StringPiece user_id) {
47 return device::fido::mac::EncodeRpIdAndUserId(key_, rp_id_,
48 to_bytes(user_id));
49 }
EncodeRpId()50 std::string EncodeRpId() {
51 return device::fido::mac::EncodeRpId(key_, rp_id_);
52 }
53
DecodeRpId(const std::string & ct)54 std::string DecodeRpId(const std::string& ct) {
55 return *device::fido::mac::DecodeRpId(key_, ct);
56 }
57
58 std::vector<uint8_t> default_id_ = {0, 1, 2, 3};
59 std::string rp_id_ = "acme.com";
60 std::string key_ = "supersecretsupersecretsupersecre";
61 std::string wrong_key_ = "SUPERsecretsupersecretsupersecre";
62 };
63
TEST_F(CredentialMetadataTest,CredentialId)64 TEST_F(CredentialMetadataTest, CredentialId) {
65 std::vector<uint8_t> id = SealCredentialId(DefaultUser());
66 EXPECT_EQ(1, (id)[0]);
67 EXPECT_EQ(55u, id.size());
68 EXPECT_TRUE(MetadataEq(UnsealCredentialId(id), DefaultUser()));
69 }
70
TEST_F(CredentialMetadataTest,LegacyCredentialId)71 TEST_F(CredentialMetadataTest, LegacyCredentialId) {
72 auto user = DefaultUser();
73 std::vector<uint8_t> id = SealLegacyV0CredentialIdForTestingOnly(
74 key_, rp_id_, user.user_id, user.user_name, user.user_display_name);
75 EXPECT_EQ(0, id[0]);
76 EXPECT_EQ(54u, id.size());
77 EXPECT_TRUE(MetadataEq(UnsealCredentialId(id), DefaultUser()));
78 }
79
TEST_F(CredentialMetadataTest,CredentialId_IsRandom)80 TEST_F(CredentialMetadataTest, CredentialId_IsRandom) {
81 EXPECT_NE(SealCredentialId(DefaultUser()), SealCredentialId(DefaultUser()));
82 }
83
TEST_F(CredentialMetadataTest,CredentialId_FailDecode)84 TEST_F(CredentialMetadataTest, CredentialId_FailDecode) {
85 const auto id = SealCredentialId(DefaultUser());
86 // Flipping a bit in version, nonce, or ciphertext will fail credential
87 // decryption.
88 for (size_t i = 0; i < id.size(); i++) {
89 std::vector<uint8_t> new_id(id);
90 new_id[i] = new_id[i] ^ 0x01;
91 EXPECT_FALSE(device::fido::mac::UnsealCredentialId(key_, rp_id_, new_id));
92 }
93 }
94
TEST_F(CredentialMetadataTest,CredentialId_InvalidRp)95 TEST_F(CredentialMetadataTest, CredentialId_InvalidRp) {
96 std::vector<uint8_t> id = SealCredentialId(DefaultUser());
97 // The credential id is authenticated with the RP, thus decryption under a
98 // different RP fails.
99 EXPECT_FALSE(device::fido::mac::UnsealCredentialId(key_, "notacme.com", id));
100 }
101
TEST_F(CredentialMetadataTest,EncodeRpIdAndUserId)102 TEST_F(CredentialMetadataTest, EncodeRpIdAndUserId) {
103 EXPECT_EQ(64u, EncodeRpIdAndUserId("user@acme.com").size())
104 << EncodeRpIdAndUserId("user@acme.com");
105
106 EXPECT_EQ(EncodeRpIdAndUserId("user"), EncodeRpIdAndUserId("user"));
107 EXPECT_NE(EncodeRpIdAndUserId("userA"), EncodeRpIdAndUserId("userB"));
108 EXPECT_NE(EncodeRpIdAndUserId("user"),
109 device::fido::mac::EncodeRpIdAndUserId(key_, "notacme.com",
110 to_bytes("user")));
111 EXPECT_NE(EncodeRpIdAndUserId("user"),
112 device::fido::mac::EncodeRpIdAndUserId(wrong_key_, rp_id_,
113 to_bytes("user")));
114 }
115
TEST_F(CredentialMetadataTest,EncodeRpId)116 TEST_F(CredentialMetadataTest, EncodeRpId) {
117 EXPECT_EQ(48u, EncodeRpId().size());
118
119 EXPECT_EQ(EncodeRpId(), EncodeRpId());
120 EXPECT_NE(EncodeRpId(), device::fido::mac::EncodeRpId(key_, "notacme.com"));
121 EXPECT_NE(EncodeRpId(), device::fido::mac::EncodeRpId(wrong_key_, rp_id_));
122 }
123
TEST_F(CredentialMetadataTest,DecodeRpId)124 TEST_F(CredentialMetadataTest, DecodeRpId) {
125 EXPECT_EQ(rp_id_, DecodeRpId(EncodeRpId()));
126 EXPECT_NE(rp_id_,
127 *device::fido::mac::DecodeRpId(
128 key_, device::fido::mac::EncodeRpId(key_, "notacme.com")));
129 EXPECT_FALSE(device::fido::mac::DecodeRpId(wrong_key_, EncodeRpId()));
130 }
131
TEST_F(CredentialMetadataTest,Truncation)132 TEST_F(CredentialMetadataTest, Truncation) {
133 constexpr char len70[] =
134 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345";
135 constexpr char len71[] =
136 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456";
137 constexpr char truncated[] =
138 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012…";
139 auto credential_id =
140 SealCredentialId(CredentialMetadata({1}, len71, len71, false));
141 CredentialMetadata metadata = UnsealCredentialId(credential_id);
142 EXPECT_EQ(metadata.user_name, truncated);
143 EXPECT_EQ(metadata.user_display_name, truncated);
144
145 credential_id =
146 SealCredentialId(CredentialMetadata({1}, len70, len70, false));
147 metadata = UnsealCredentialId(credential_id);
148 EXPECT_EQ(metadata.user_name, len70);
149 EXPECT_EQ(metadata.user_display_name, len70);
150 }
151
TEST(CredentialMetadata,GenerateCredentialMetadataSecret)152 TEST(CredentialMetadata, GenerateCredentialMetadataSecret) {
153 std::string s1 = GenerateCredentialMetadataSecret();
154 EXPECT_EQ(32u, s1.size());
155 std::string s2 = GenerateCredentialMetadataSecret();
156 EXPECT_EQ(32u, s2.size());
157 EXPECT_NE(s1, s2);
158 }
159
TEST(CredentialMetadata,FromPublicKeyCredentialUserEntity)160 TEST(CredentialMetadata, FromPublicKeyCredentialUserEntity) {
161 std::vector<uint8_t> user_id = {{1, 2, 3}};
162 PublicKeyCredentialUserEntity in(user_id);
163 in.name = "username";
164 in.display_name = "display name";
165 in.icon_url = GURL("http://rp.foo/user.png");
166 CredentialMetadata out =
167 CredentialMetadata::FromPublicKeyCredentialUserEntity(
168 std::move(in), /*is_resident=*/false);
169 EXPECT_EQ(user_id, out.user_id);
170 EXPECT_EQ("username", out.user_name);
171 EXPECT_EQ("display name", out.user_display_name);
172 EXPECT_FALSE(out.is_resident);
173 }
174
TEST(CredentialMetadata,ToPublicKeyCredentialUserEntity)175 TEST(CredentialMetadata, ToPublicKeyCredentialUserEntity) {
176 std::vector<uint8_t> user_id = {{1, 2, 3}};
177 CredentialMetadata in(user_id, "username", "display name",
178 /*is_resident=*/false);
179 PublicKeyCredentialUserEntity out = in.ToPublicKeyCredentialUserEntity();
180 EXPECT_EQ(user_id, out.id);
181 EXPECT_EQ("username", out.name.value());
182 EXPECT_EQ("display name", out.display_name.value());
183 EXPECT_FALSE(out.icon_url.has_value());
184 }
185
186 } // namespace
187 } // namespace mac
188 } // namespace fido
189 } // namespace device
190