1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(WEB_AUTHN)
29
30#include "FidoTestData.h"
31#include "PlatformUtilities.h"
32#include <WebCore/CBORReader.h>
33#include <WebCore/CBORValue.h>
34#include <WebCore/CryptoAlgorithmAES_CBC.h>
35#include <WebCore/CryptoAlgorithmAesCbcCfbParams.h>
36#include <WebCore/CryptoAlgorithmECDH.h>
37#include <WebCore/CryptoKeyAES.h>
38#include <WebCore/CryptoKeyEC.h>
39#include <WebCore/CryptoKeyHMAC.h>
40#include <WebCore/FidoConstants.h>
41#include <WebCore/Pin.h>
42#include <WebCore/WebAuthenticationConstants.h>
43#include <WebCore/WebAuthenticationUtils.h>
44#include <wtf/text/Base64.h>
45
46namespace TestWebKitAPI {
47using namespace WebCore;
48using namespace cbor;
49using namespace fido;
50using namespace fido::pin;
51
52TEST(CtapPinTest, TestValidateAndConvertToUTF8)
53{
54 // Failure cases
55 auto result = validateAndConvertToUTF8("123");
56 EXPECT_FALSE(result);
57 result = validateAndConvertToUTF8("");
58 EXPECT_FALSE(result);
59 result = validateAndConvertToUTF8("1234567812345678123456781234567812345678123456781234567812345678");
60 EXPECT_FALSE(result);
61
62 // Success cases
63 result = validateAndConvertToUTF8("1234");
64 EXPECT_TRUE(result);
65 EXPECT_EQ(result->length(), 4u);
66 EXPECT_STREQ(result->data(), "1234");
67
68 result = validateAndConvertToUTF8("123456781234567812345678123456781234567812345678123456781234567");
69 EXPECT_TRUE(result);
70 EXPECT_EQ(result->length(), 63u);
71 EXPECT_STREQ(result->data(), "123456781234567812345678123456781234567812345678123456781234567");
72}
73
74TEST(CtapPinTest, TestRetriesRequest)
75{
76 auto result = encodeAsCBOR(RetriesRequest { });
77 EXPECT_EQ(result.size(), sizeof(TestData::kCtapClientPinRetries));
78 EXPECT_EQ(memcmp(result.data(), TestData::kCtapClientPinRetries, result.size()), 0);
79}
80
81TEST(CtapPinTest, TestRetriesResponse)
82{
83 // Failure cases
84 auto result = RetriesResponse::parse({ });
85 EXPECT_FALSE(result);
86
87 const uint8_t testData1[] = { 0x05 }; // wrong response code
88 result = RetriesResponse::parse(convertBytesToVector(testData1, sizeof(testData1)));
89 EXPECT_FALSE(result);
90
91 const uint8_t testData2[] = { 0x00, 0x00 }; // wrong CBOR map
92 result = RetriesResponse::parse(convertBytesToVector(testData2, sizeof(testData2)));
93 EXPECT_FALSE(result);
94
95 result = RetriesResponse::parse(convertBytesToVector(TestData::kCtapClientPinTokenResponse, sizeof(TestData::kCtapClientPinTokenResponse))); // wrong response
96 EXPECT_FALSE(result);
97
98 // Success cases
99 result = RetriesResponse::parse(convertBytesToVector(TestData::kCtapClientPinRetriesResponse, sizeof(TestData::kCtapClientPinRetriesResponse)));
100 EXPECT_TRUE(result);
101 EXPECT_EQ(result->retries, 8);
102}
103
104TEST(CtapPinTest, TestKeyAgreementRequest)
105{
106 auto result = encodeAsCBOR(KeyAgreementRequest { });
107 EXPECT_EQ(result.size(), sizeof(TestData::kCtapClientPinKeyAgreement));
108 EXPECT_EQ(memcmp(result.data(), TestData::kCtapClientPinKeyAgreement, result.size()), 0);
109}
110
111TEST(CtapPinTest, TestKeyAgreementResponse)
112{
113 // Failure cases
114 auto result = KeyAgreementResponse::parse({ });
115 EXPECT_FALSE(result);
116
117 const uint8_t testData1[] = { 0x05 }; // wrong response code
118 result = KeyAgreementResponse::parse(convertBytesToVector(testData1, sizeof(testData1)));
119 EXPECT_FALSE(result);
120
121 const uint8_t testData2[] = { 0x00, 0x00 }; // wrong CBOR map
122 result = KeyAgreementResponse::parse(convertBytesToVector(testData2, sizeof(testData2)));
123 EXPECT_FALSE(result);
124
125 result = KeyAgreementResponse::parse(convertBytesToVector(TestData::kCtapClientPinTokenResponse, sizeof(TestData::kCtapClientPinTokenResponse))); // wrong response
126 EXPECT_FALSE(result);
127
128 // Test COSE
129 auto coseKey = encodeCOSEPublicKey(Vector<uint8_t>(65));
130 coseKey[CBORValue(COSE::kty)] = CBORValue(0); // wrong kty
131 auto coseResult = KeyAgreementResponse::parseFromCOSE(coseKey);
132 EXPECT_FALSE(coseResult);
133
134 coseKey = encodeCOSEPublicKey(Vector<uint8_t>(65));
135 coseKey[CBORValue(COSE::alg)] = CBORValue(0); // wrong alg
136 coseResult = KeyAgreementResponse::parseFromCOSE(coseKey);
137 EXPECT_FALSE(coseResult);
138
139 coseKey = encodeCOSEPublicKey(Vector<uint8_t>(65));
140 coseKey[CBORValue(COSE::crv)] = CBORValue(0); // wrong crv
141 coseResult = KeyAgreementResponse::parseFromCOSE(coseKey);
142 EXPECT_FALSE(coseResult);
143
144 coseKey = encodeCOSEPublicKey(Vector<uint8_t>(65));
145 coseKey[CBORValue(COSE::x)] = CBORValue(0); // wrong crv
146 coseResult = KeyAgreementResponse::parseFromCOSE(coseKey);
147 EXPECT_FALSE(coseResult);
148
149 coseKey = encodeCOSEPublicKey(Vector<uint8_t>(65));
150 coseKey[CBORValue(COSE::y)] = CBORValue(0); // wrong crv
151 coseResult = KeyAgreementResponse::parseFromCOSE(coseKey);
152 EXPECT_FALSE(coseResult);
153
154 // Success cases
155 result = KeyAgreementResponse::parse(convertBytesToVector(TestData::kCtapClientPinKeyAgreementResponse, sizeof(TestData::kCtapClientPinKeyAgreementResponse)));
156 EXPECT_TRUE(result);
157 auto exportedRawKey = result->peerKey->exportRaw();
158 EXPECT_FALSE(exportedRawKey.hasException());
159 Vector<uint8_t> expectedRawKey;
160 expectedRawKey.reserveCapacity(65);
161 expectedRawKey.append(0x04);
162 expectedRawKey.append(TestData::kCtapClientPinKeyAgreementResponse + 14, 32); // X
163 expectedRawKey.append(TestData::kCtapClientPinKeyAgreementResponse + 49, 32); // Y
164 EXPECT_TRUE(exportedRawKey.returnValue() == expectedRawKey);
165}
166
167TEST(CtapPinTest, TestTokenRequest)
168{
169 // Generate an EC key pair as the peer key.
170 auto keyPairResult = CryptoKeyEC::generatePair(CryptoAlgorithmIdentifier::ECDH, "P-256", true, CryptoKeyUsageDeriveBits);
171 ASSERT_FALSE(keyPairResult.hasException());
172 auto keyPair = keyPairResult.releaseReturnValue();
173
174 CString pin = "1234";
175
176 auto token = TokenRequest::tryCreate(pin, downcast<CryptoKeyEC>(*keyPair.publicKey));
177 EXPECT_TRUE(token);
178 auto result = encodeAsCBOR(*token);
179 EXPECT_EQ(result.size(), 120u);
180 EXPECT_EQ(result[0], static_cast<uint8_t>(CtapRequestCommand::kAuthenticatorClientPin));
181
182 // Decode the CBOR binary to check if each field is encoded correctly.
183 Vector<uint8_t> buffer;
184 buffer.append(result.data() + 1, result.size() - 1);
185 auto decodedResponse = cbor::CBORReader::read(buffer);
186 EXPECT_TRUE(decodedResponse);
187 EXPECT_TRUE(decodedResponse->isMap());
188 const auto& responseMap = decodedResponse->getMap();
189
190 const auto& it1 = responseMap.find(CBORValue(static_cast<uint8_t>(RequestKey::kProtocol)));
191 EXPECT_NE(it1, responseMap.end());
192 EXPECT_EQ(it1->second.getInteger(), kProtocolVersion);
193
194 const auto& it2 = responseMap.find(CBORValue(static_cast<uint8_t>(RequestKey::kSubcommand)));
195 EXPECT_NE(it2, responseMap.end());
196 EXPECT_EQ(it2->second.getInteger(), static_cast<uint8_t>(Subcommand::kGetPinToken));
197
198 // COSE
199 auto it = responseMap.find(CBORValue(static_cast<int>(RequestKey::kKeyAgreement)));
200 EXPECT_TRUE(decodedResponse);
201 EXPECT_TRUE(decodedResponse->isMap());
202 const auto& coseKey = it->second.getMap();
203
204 const auto& it3 = coseKey.find(CBORValue(COSE::kty));
205 EXPECT_NE(it3, coseKey.end());
206 EXPECT_EQ(it3->second.getInteger(), COSE::EC2);
207
208 const auto& it4 = coseKey.find(CBORValue(COSE::alg));
209 EXPECT_NE(it4, coseKey.end());
210 EXPECT_EQ(it4->second.getInteger(), COSE::ECDH256);
211
212 const auto& it5 = coseKey.find(CBORValue(COSE::crv));
213 EXPECT_NE(it5, coseKey.end());
214 EXPECT_EQ(it5->second.getInteger(), COSE::P_256);
215
216 // Check the cose key.
217 const auto& xIt = coseKey.find(CBORValue(-2));
218 EXPECT_NE(xIt, coseKey.end());
219 const auto& yIt = coseKey.find(CBORValue(-3));
220 EXPECT_NE(yIt, coseKey.end());
221 auto cosePublicKey = CryptoKeyEC::importRaw(CryptoAlgorithmIdentifier::ECDH, "P-256", encodeRawPublicKey(xIt->second.getByteString(), yIt->second.getByteString()), true, CryptoKeyUsageDeriveBits);
222 EXPECT_TRUE(cosePublicKey);
223
224 // Check the encrypted Pin.
225 auto sharedKeyResult = CryptoAlgorithmECDH::platformDeriveBits(downcast<CryptoKeyEC>(*keyPair.privateKey), *cosePublicKey);
226 EXPECT_TRUE(sharedKeyResult);
227 auto aesKey = CryptoKeyAES::importRaw(CryptoAlgorithmIdentifier::AES_CBC, WTFMove(*sharedKeyResult), true, CryptoKeyUsageDecrypt);
228 EXPECT_TRUE(aesKey);
229
230 const auto& it6 = responseMap.find(CBORValue(static_cast<uint8_t>(RequestKey::kPinHashEnc)));
231 EXPECT_NE(it6, responseMap.end());
232 auto pinHashResult = CryptoAlgorithmAES_CBC::platformDecrypt({ }, *aesKey, it6->second.getByteString());
233 EXPECT_FALSE(pinHashResult.hasException());
234 auto pinHash = pinHashResult.releaseReturnValue();
235 const uint8_t expectedPinHash[] = { 0x03, 0xac, 0x67, 0x42, 0x16, 0xf3, 0xe1, 0x5c, 0x76, 0x1e, 0xe1, 0xa5, 0xe2, 0x55, 0xf0, 0x67 };
236 EXPECT_EQ(pinHash.size(), 16u);
237 EXPECT_EQ(memcmp(pinHash.data(), expectedPinHash, pinHash.size()), 0);
238}
239
240TEST(CtapPinTest, TestTokenResponse)
241{
242 const uint8_t sharedKeyData[] = {
243 0x03, 0xac, 0x67, 0x42, 0x16, 0xf3, 0xe1, 0x5c,
244 0x76, 0x1e, 0xe1, 0xa5, 0xe2, 0x55, 0xf0, 0x67,
245 0x95, 0x36, 0x23, 0xc8, 0xb3, 0x88, 0xb4, 0x45,
246 0x9e, 0x13, 0xf9, 0x78, 0xd7, 0xc8, 0x46, 0xf4, };
247 auto sharedKey = CryptoKeyAES::importRaw(CryptoAlgorithmIdentifier::AES_CBC, convertBytesToVector(sharedKeyData, sizeof(sharedKeyData)), true, CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt);
248 ASSERT_TRUE(sharedKey);
249
250 // Failure cases
251 auto result = TokenResponse::parse(*sharedKey, { });
252 EXPECT_FALSE(result);
253
254 const uint8_t testData1[] = { 0x05 }; // wrong response code
255 result = TokenResponse::parse(*sharedKey, convertBytesToVector(testData1, sizeof(testData1)));
256 EXPECT_FALSE(result);
257
258 const uint8_t testData2[] = { 0x00, 0x00 }; // wrong CBOR map
259 result = TokenResponse::parse(*sharedKey, convertBytesToVector(testData2, sizeof(testData2)));
260 EXPECT_FALSE(result);
261
262 result = TokenResponse::parse(*sharedKey, convertBytesToVector(TestData::kCtapClientPinKeyAgreementResponse, sizeof(TestData::kCtapClientPinKeyAgreementResponse))); // wrong response
263 EXPECT_FALSE(result);
264
265 // Success cases
266 result = TokenResponse::parse(*sharedKey, convertBytesToVector(TestData::kCtapClientPinTokenResponse, sizeof(TestData::kCtapClientPinTokenResponse)));
267 EXPECT_TRUE(result);
268 const uint8_t expectedToken[] = { 0x03, 0xac, 0x67, 0x42, 0x16, 0xf3, 0xe1, 0x5c, 0x76, 0x1e, 0xe1, 0xa5, 0xe2, 0x55, 0xf0, 0x67 };
269 EXPECT_EQ(result->token().size(), 16u);
270 EXPECT_EQ(memcmp(result->token().data(), expectedToken, result->token().size()), 0);
271}
272
273TEST(CtapPinTest, TestPinAuth)
274{
275 // 1. Generate the token.
276 const uint8_t sharedKeyData[] = {
277 0x03, 0xac, 0x67, 0x42, 0x16, 0xf3, 0xe1, 0x5c,
278 0x76, 0x1e, 0xe1, 0xa5, 0xe2, 0x55, 0xf0, 0x67,
279 0x95, 0x36, 0x23, 0xc8, 0xb3, 0x88, 0xb4, 0x45,
280 0x9e, 0x13, 0xf9, 0x78, 0xd7, 0xc8, 0x46, 0xf4, };
281 auto sharedKey = CryptoKeyAES::importRaw(CryptoAlgorithmIdentifier::AES_CBC, convertBytesToVector(sharedKeyData, sizeof(sharedKeyData)), true, CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt);
282 ASSERT_TRUE(sharedKey);
283 auto result = TokenResponse::parse(*sharedKey, convertBytesToVector(TestData::kCtapClientPinTokenResponse, sizeof(TestData::kCtapClientPinTokenResponse)));
284 ASSERT_TRUE(result);
285
286 // 2. Generate the pinAuth.
287 auto pinAuth = result->pinAuth(convertBytesToVector(sharedKeyData, sizeof(sharedKeyData))); // sharedKeyData pretends to be clientDataHash
288 const uint8_t expectedPinAuth[] = { 0xb3, 0xc2, 0x65, 0x1c, 0xfd, 0xc8, 0x42, 0xb4, 0x60, 0x16, 0xed, 0x20, 0x64, 0x53, 0xaf, 0x84 };
289 EXPECT_EQ(pinAuth.size(), 16u);
290 EXPECT_EQ(memcmp(pinAuth.data(), expectedPinAuth, pinAuth.size()), 0);
291}
292
293} // namespace TestWebKitAPI
294
295#endif // ENABLE(WEB_AUTHN)