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