Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
* You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "mozilla/dom/WebAuthnUtil.h"
8
#include "nsIEffectiveTLDService.h"
9
#include "nsNetUtil.h"
10
#include "mozpkix/pkixutil.h"
11
12
namespace mozilla {
13
namespace dom {
14
15
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
16
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1,
18
NS_NAMED_LITERAL_STRING(
19
kGoogleAccountsAppId2,
21
22
const uint8_t FLAG_TUP = 0x01; // Test of User Presence required
23
const uint8_t FLAG_AT = 0x40; // Authenticator Data is provided
24
25
bool EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
26
/* in/out */ nsString& aAppId) {
27
// Facet is the specification's way of referring to the web origin.
28
nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
29
nsCOMPtr<nsIURI> facetUri;
30
if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
31
return false;
32
}
33
34
// If the facetId (origin) is not HTTPS, reject
35
if (!facetUri->SchemeIs("https")) {
36
return false;
37
}
38
39
// If the appId is empty or null, overwrite it with the facetId and accept
40
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
41
aAppId.Assign(aOrigin);
42
return true;
43
}
44
45
// AppID is user-supplied. It's quite possible for this parse to fail.
46
nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
47
nsCOMPtr<nsIURI> appIdUri;
48
if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
49
return false;
50
}
51
52
// if the appId URL is not HTTPS, reject.
53
if (!appIdUri->SchemeIs("https")) {
54
return false;
55
}
56
57
nsAutoCString appIdHost;
58
if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
59
return false;
60
}
61
62
// Allow localhost.
63
if (appIdHost.EqualsLiteral("localhost")) {
64
nsAutoCString facetHost;
65
if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
66
return false;
67
}
68
69
if (facetHost.EqualsLiteral("localhost")) {
70
return true;
71
}
72
}
73
74
// Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
75
// Web Authentication. See Bug 1244959 comment #8 for context on why we are
76
// doing this instead of implementing the external-fetch FacetID logic.
77
nsCOMPtr<Document> document = aParent->GetDoc();
78
if (!document || !document->IsHTMLDocument()) {
79
return false;
80
}
81
82
nsHTMLDocument* html = document->AsHTMLDocument();
83
// Use the base domain as the facet for evaluation. This lets this algorithm
84
// relax the whole eTLD+1.
85
nsCOMPtr<nsIEffectiveTLDService> tldService =
86
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
87
if (!tldService) {
88
return false;
89
}
90
91
nsAutoCString lowestFacetHost;
92
if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
93
return false;
94
}
95
96
if (html->IsRegistrableDomainSuffixOfOrEqualTo(
97
NS_ConvertUTF8toUTF16(lowestFacetHost), appIdHost)) {
98
return true;
99
}
100
101
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
102
if (lowestFacetHost.EqualsLiteral("google.com") &&
103
(aAppId.Equals(kGoogleAccountsAppId1) ||
104
aAppId.Equals(kGoogleAccountsAppId2))) {
105
return true;
106
}
107
108
return false;
109
}
110
111
nsresult ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
112
uint32_t aLen) {
113
if (aSrc.EnsureLength(aLen) != pkix::Success) {
114
return NS_ERROR_DOM_UNKNOWN_ERR;
115
}
116
117
if (!aDest.SetCapacity(aLen, mozilla::fallible)) {
118
return NS_ERROR_OUT_OF_MEMORY;
119
}
120
121
for (uint32_t offset = 0; offset < aLen; ++offset) {
122
uint8_t b;
123
if (aSrc.Read(b) != pkix::Success) {
124
return NS_ERROR_DOM_UNKNOWN_ERR;
125
}
126
if (!aDest.AppendElement(b, mozilla::fallible)) {
127
return NS_ERROR_OUT_OF_MEMORY;
128
}
129
}
130
131
return NS_OK;
132
}
133
134
// Format:
135
// 32 bytes: SHA256 of the RP ID
136
// 1 byte: flags (TUP & AT)
137
// 4 bytes: sign counter
138
// variable: attestation data struct
139
// variable: CBOR-format extension auth data (optional, not flagged)
140
nsresult AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
141
const uint8_t flags,
142
const CryptoBuffer& counterBuf,
143
const CryptoBuffer& attestationDataBuf,
144
/* out */ CryptoBuffer& authDataBuf) {
145
if (NS_WARN_IF(!authDataBuf.SetCapacity(
146
32 + 1 + 4 + attestationDataBuf.Length(), mozilla::fallible))) {
147
return NS_ERROR_OUT_OF_MEMORY;
148
}
149
if (rpIdHashBuf.Length() != 32 || counterBuf.Length() != 4) {
150
return NS_ERROR_INVALID_ARG;
151
}
152
153
authDataBuf.AppendElements(rpIdHashBuf, mozilla::fallible);
154
authDataBuf.AppendElement(flags, mozilla::fallible);
155
authDataBuf.AppendElements(counterBuf, mozilla::fallible);
156
authDataBuf.AppendElements(attestationDataBuf, mozilla::fallible);
157
return NS_OK;
158
}
159
160
// attestation data struct format:
161
// - 16 bytes: AAGUID
162
// - 2 bytes: Length of Credential ID
163
// - L bytes: Credential ID
164
// - variable: CBOR-format public key
165
nsresult AssembleAttestationData(const CryptoBuffer& aaguidBuf,
166
const CryptoBuffer& keyHandleBuf,
167
const CryptoBuffer& pubKeyObj,
168
/* out */ CryptoBuffer& attestationDataBuf) {
169
if (NS_WARN_IF(!attestationDataBuf.SetCapacity(
170
aaguidBuf.Length() + 2 + keyHandleBuf.Length() + pubKeyObj.Length(),
171
mozilla::fallible))) {
172
return NS_ERROR_OUT_OF_MEMORY;
173
}
174
if (keyHandleBuf.Length() > 0xFFFF) {
175
return NS_ERROR_INVALID_ARG;
176
}
177
178
attestationDataBuf.AppendElements(aaguidBuf, mozilla::fallible);
179
attestationDataBuf.AppendElement((keyHandleBuf.Length() >> 8) & 0xFF,
180
mozilla::fallible);
181
attestationDataBuf.AppendElement((keyHandleBuf.Length() >> 0) & 0xFF,
182
mozilla::fallible);
183
attestationDataBuf.AppendElements(keyHandleBuf, mozilla::fallible);
184
attestationDataBuf.AppendElements(pubKeyObj, mozilla::fallible);
185
return NS_OK;
186
}
187
188
nsresult AssembleAttestationObject(const CryptoBuffer& aRpIdHash,
189
const CryptoBuffer& aPubKeyBuf,
190
const CryptoBuffer& aKeyHandleBuf,
191
const CryptoBuffer& aAttestationCertBuf,
192
const CryptoBuffer& aSignatureBuf,
193
bool aForceNoneAttestation,
194
/* out */ CryptoBuffer& aAttestationObjBuf) {
195
// Construct the public key object
196
CryptoBuffer pubKeyObj;
197
nsresult rv = CBOREncodePublicKeyObj(aPubKeyBuf, pubKeyObj);
198
if (NS_FAILED(rv)) {
199
return rv;
200
}
201
202
mozilla::dom::CryptoBuffer aaguidBuf;
203
if (NS_WARN_IF(!aaguidBuf.SetCapacity(16, mozilla::fallible))) {
204
return NS_ERROR_OUT_OF_MEMORY;
205
}
206
207
// FIDO U2F devices have no AAGUIDs, so they'll be all zeros until we add
208
// support for CTAP2 devices.
209
for (int i = 0; i < 16; i++) {
210
aaguidBuf.AppendElement(0x00, mozilla::fallible);
211
}
212
213
// During create credential, counter is always 0 for U2F
215
mozilla::dom::CryptoBuffer counterBuf;
216
if (NS_WARN_IF(!counterBuf.SetCapacity(4, mozilla::fallible))) {
217
return NS_ERROR_OUT_OF_MEMORY;
218
}
219
counterBuf.AppendElement(0x00, mozilla::fallible);
220
counterBuf.AppendElement(0x00, mozilla::fallible);
221
counterBuf.AppendElement(0x00, mozilla::fallible);
222
counterBuf.AppendElement(0x00, mozilla::fallible);
223
224
// Construct the Attestation Data, which slots into the end of the
225
// Authentication Data buffer.
226
CryptoBuffer attDataBuf;
227
rv = AssembleAttestationData(aaguidBuf, aKeyHandleBuf, pubKeyObj, attDataBuf);
228
if (NS_FAILED(rv)) {
229
return rv;
230
}
231
232
CryptoBuffer authDataBuf;
233
// attDataBuf always contains data, so per [1] we have to set the AT flag.
235
const uint8_t flags = FLAG_TUP | FLAG_AT;
236
rv = AssembleAuthenticatorData(aRpIdHash, flags, counterBuf, attDataBuf,
237
authDataBuf);
238
if (NS_FAILED(rv)) {
239
return rv;
240
}
241
242
// Direct attestation might have been requested by the RP.
243
// The user might override this and request anonymization anyway.
244
if (aForceNoneAttestation) {
245
rv = CBOREncodeNoneAttestationObj(authDataBuf, aAttestationObjBuf);
246
} else {
247
rv = CBOREncodeFidoU2FAttestationObj(authDataBuf, aAttestationCertBuf,
248
aSignatureBuf, aAttestationObjBuf);
249
}
250
251
return rv;
252
}
253
254
nsresult U2FDecomposeSignResponse(const CryptoBuffer& aResponse,
255
/* out */ uint8_t& aFlags,
256
/* out */ CryptoBuffer& aCounterBuf,
257
/* out */ CryptoBuffer& aSignatureBuf) {
258
if (aResponse.Length() < 5) {
259
return NS_ERROR_INVALID_ARG;
260
}
261
262
Span<const uint8_t> rspView = MakeSpan(aResponse);
263
aFlags = rspView[0];
264
265
if (NS_WARN_IF(!aCounterBuf.AppendElements(rspView.FromTo(1, 5),
266
mozilla::fallible))) {
267
return NS_ERROR_OUT_OF_MEMORY;
268
}
269
270
if (NS_WARN_IF(
271
!aSignatureBuf.AppendElements(rspView.From(5), mozilla::fallible))) {
272
return NS_ERROR_OUT_OF_MEMORY;
273
}
274
275
return NS_OK;
276
}
277
278
nsresult U2FDecomposeRegistrationResponse(
279
const CryptoBuffer& aResponse,
280
/* out */ CryptoBuffer& aPubKeyBuf,
281
/* out */ CryptoBuffer& aKeyHandleBuf,
282
/* out */ CryptoBuffer& aAttestationCertBuf,
283
/* out */ CryptoBuffer& aSignatureBuf) {
284
// U2F v1.1 Format via
286
//
287
// Bytes Value
288
// 1 0x05
289
// 65 public key
290
// 1 key handle length
291
// * key handle
292
// ASN.1 attestation certificate
293
// * attestation signature
294
295
pkix::Input u2fResponse;
296
u2fResponse.Init(aResponse.Elements(), aResponse.Length());
297
298
pkix::Reader input(u2fResponse);
299
300
uint8_t b;
301
if (input.Read(b) != pkix::Success) {
302
return NS_ERROR_DOM_UNKNOWN_ERR;
303
}
304
if (b != 0x05) {
305
return NS_ERROR_DOM_UNKNOWN_ERR;
306
}
307
308
nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
309
if (NS_FAILED(rv)) {
310
return rv;
311
}
312
313
uint8_t handleLen;
314
if (input.Read(handleLen) != pkix::Success) {
315
return NS_ERROR_DOM_UNKNOWN_ERR;
316
}
317
318
rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
319
if (NS_FAILED(rv)) {
320
return rv;
321
}
322
323
// We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
324
// length.
325
pkix::Input cert;
326
if (pkix::der::ExpectTagAndGetTLV(input, pkix::der::SEQUENCE, cert) !=
327
pkix::Success) {
328
return NS_ERROR_DOM_UNKNOWN_ERR;
329
}
330
331
pkix::Reader certInput(cert);
332
rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
333
if (NS_FAILED(rv)) {
334
return rv;
335
}
336
337
// The remainder of u2fResponse is the signature
338
pkix::Input u2fSig;
339
input.SkipToEnd(u2fSig);
340
pkix::Reader sigInput(u2fSig);
341
rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
342
if (NS_FAILED(rv)) {
343
return rv;
344
}
345
346
MOZ_ASSERT(input.AtEnd());
347
return NS_OK;
348
}
349
350
nsresult U2FDecomposeECKey(const CryptoBuffer& aPubKeyBuf,
351
/* out */ CryptoBuffer& aXcoord,
352
/* out */ CryptoBuffer& aYcoord) {
353
pkix::Input pubKey;
354
pubKey.Init(aPubKeyBuf.Elements(), aPubKeyBuf.Length());
355
356
pkix::Reader input(pubKey);
357
uint8_t b;
358
if (input.Read(b) != pkix::Success) {
359
return NS_ERROR_DOM_UNKNOWN_ERR;
360
}
361
if (b != 0x04) {
362
return NS_ERROR_DOM_UNKNOWN_ERR;
363
}
364
365
nsresult rv = ReadToCryptoBuffer(input, aXcoord, 32);
366
if (NS_FAILED(rv)) {
367
return rv;
368
}
369
370
rv = ReadToCryptoBuffer(input, aYcoord, 32);
371
if (NS_FAILED(rv)) {
372
return rv;
373
}
374
375
MOZ_ASSERT(input.AtEnd());
376
return NS_OK;
377
}
378
379
static nsresult HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
380
/* out */ CryptoBuffer& aOut) {
381
MOZ_ASSERT(aHashService);
382
383
nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
384
if (NS_WARN_IF(NS_FAILED(rv))) {
385
return rv;
386
}
387
388
rv = aHashService->Update(
389
reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
390
if (NS_WARN_IF(NS_FAILED(rv))) {
391
return rv;
392
}
393
394
nsAutoCString fullHash;
395
// Passing false below means we will get a binary result rather than a
396
// base64-encoded string.
397
rv = aHashService->Finish(false, fullHash);
398
if (NS_WARN_IF(NS_FAILED(rv))) {
399
return rv;
400
}
401
402
if (NS_WARN_IF(!aOut.Assign(fullHash))) {
403
return NS_ERROR_OUT_OF_MEMORY;
404
}
405
406
return NS_OK;
407
}
408
409
nsresult HashCString(const nsACString& aIn, /* out */ CryptoBuffer& aOut) {
410
nsresult srv;
411
nsCOMPtr<nsICryptoHash> hashService =
412
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
413
if (NS_FAILED(srv)) {
414
return srv;
415
}
416
417
srv = HashCString(hashService, aIn, aOut);
418
if (NS_WARN_IF(NS_FAILED(srv))) {
419
return NS_ERROR_FAILURE;
420
}
421
422
return NS_OK;
423
}
424
425
nsresult BuildTransactionHashes(const nsCString& aRpId,
426
const nsCString& aClientDataJSON,
427
/* out */ CryptoBuffer& aRpIdHash,
428
/* out */ CryptoBuffer& aClientDataHash) {
429
nsresult srv;
430
nsCOMPtr<nsICryptoHash> hashService =
431
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
432
if (NS_FAILED(srv)) {
433
return srv;
434
}
435
436
if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
437
return NS_ERROR_OUT_OF_MEMORY;
438
}
439
srv = HashCString(hashService, aRpId, aRpIdHash);
440
if (NS_WARN_IF(NS_FAILED(srv))) {
441
return NS_ERROR_FAILURE;
442
}
443
444
if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
445
return NS_ERROR_OUT_OF_MEMORY;
446
}
447
srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
448
if (NS_WARN_IF(NS_FAILED(srv))) {
449
return NS_ERROR_FAILURE;
450
}
451
452
return NS_OK;
453
}
454
455
} // namespace dom
456
} // namespace mozilla