Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/webauthn/WebAuthnManager.cpp
Line
Count
Source (jump to first uncovered line)
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 "hasht.h"
8
#include "nsHTMLDocument.h"
9
#include "nsIURIMutator.h"
10
#include "nsThreadUtils.h"
11
#include "WebAuthnCoseIdentifiers.h"
12
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
13
#include "mozilla/dom/Promise.h"
14
#include "mozilla/dom/PWebAuthnTransaction.h"
15
#include "mozilla/dom/WebAuthnManager.h"
16
#include "mozilla/dom/WebAuthnTransactionChild.h"
17
#include "mozilla/dom/WebAuthnUtil.h"
18
#include "mozilla/ipc/BackgroundChild.h"
19
#include "mozilla/ipc/PBackgroundChild.h"
20
21
using namespace mozilla::ipc;
22
23
namespace mozilla {
24
namespace dom {
25
26
/***********************************************************************
27
 * Statics
28
 **********************************************************************/
29
30
namespace {
31
static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
32
}
33
34
NS_IMPL_ISUPPORTS(WebAuthnManager, nsIDOMEventListener);
35
36
/***********************************************************************
37
 * Utility Functions
38
 **********************************************************************/
39
40
static nsresult
41
AssembleClientData(const nsAString& aOrigin,
42
                   const CryptoBuffer& aChallenge,
43
                   const nsAString& aType,
44
                   const AuthenticationExtensionsClientInputs& aExtensions,
45
                   /* out */ nsACString& aJsonOut)
46
0
{
47
0
  MOZ_ASSERT(NS_IsMainThread());
48
0
49
0
  nsString challengeBase64;
50
0
  nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
51
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
52
0
    return NS_ERROR_FAILURE;
53
0
  }
54
0
55
0
  CollectedClientData clientDataObject;
56
0
  clientDataObject.mType.Assign(aType);
57
0
  clientDataObject.mChallenge.Assign(challengeBase64);
58
0
  clientDataObject.mOrigin.Assign(aOrigin);
59
0
  clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
60
0
  clientDataObject.mClientExtensions = aExtensions;
61
0
62
0
  nsAutoString temp;
63
0
  if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
64
0
    return NS_ERROR_FAILURE;
65
0
  }
66
0
67
0
  aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
68
0
  return NS_OK;
69
0
}
70
71
nsresult
72
GetOrigin(nsPIDOMWindowInner* aParent,
73
          /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost)
74
0
{
75
0
  MOZ_ASSERT(aParent);
76
0
  nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
77
0
  MOZ_ASSERT(doc);
78
0
79
0
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
80
0
  nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
81
0
  if (NS_WARN_IF(NS_FAILED(rv)) ||
82
0
      NS_WARN_IF(aOrigin.IsEmpty())) {
83
0
    return NS_ERROR_FAILURE;
84
0
  }
85
0
86
0
  if (aOrigin.EqualsLiteral("null")) {
87
0
    // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
88
0
    // DOMException whose name is "NotAllowedError", and terminate this
89
0
    // algorithm
90
0
    MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
91
0
    return NS_ERROR_DOM_NOT_ALLOWED_ERR;
92
0
  }
93
0
94
0
  nsCOMPtr<nsIURI> originUri;
95
0
  if (NS_FAILED(principal->GetURI(getter_AddRefs(originUri)))) {
96
0
    return NS_ERROR_FAILURE;
97
0
  }
98
0
  if (NS_FAILED(originUri->GetAsciiHost(aHost))) {
99
0
    return NS_ERROR_FAILURE;
100
0
  }
101
0
102
0
  return NS_OK;
103
0
}
104
105
nsresult
106
RelaxSameOrigin(nsPIDOMWindowInner* aParent,
107
                const nsAString& aInputRpId,
108
                /* out */ nsACString& aRelaxedRpId)
109
0
{
110
0
  MOZ_ASSERT(aParent);
111
0
  nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
112
0
  MOZ_ASSERT(doc);
113
0
114
0
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
115
0
  nsCOMPtr<nsIURI> uri;
116
0
  if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) {
117
0
    return NS_ERROR_FAILURE;
118
0
  }
119
0
  nsAutoCString originHost;
120
0
  if (NS_FAILED(uri->GetAsciiHost(originHost))) {
121
0
    return NS_ERROR_FAILURE;
122
0
  }
123
0
  nsCOMPtr<nsIDocument> document = aParent->GetDoc();
124
0
  if (!document || !document->IsHTMLDocument()) {
125
0
    return NS_ERROR_FAILURE;
126
0
  }
127
0
  nsHTMLDocument* html = document->AsHTMLDocument();
128
0
  // See if the given RP ID is a valid domain string.
129
0
  // (We use the document's URI here as a template so we don't have to come up
130
0
  // with our own scheme, etc. If we can successfully set the host as the given
131
0
  // RP ID, then it should be a valid domain string.)
132
0
  nsCOMPtr<nsIURI> inputRpIdURI;
133
0
  nsresult rv = NS_MutateURI(uri)
134
0
         .SetHost(NS_ConvertUTF16toUTF8(aInputRpId))
135
0
         .Finalize(inputRpIdURI);
136
0
  if (NS_FAILED(rv)) {
137
0
    return NS_ERROR_DOM_SECURITY_ERR;
138
0
  }
139
0
  nsAutoCString inputRpId;
140
0
  if (NS_FAILED(inputRpIdURI->GetAsciiHost(inputRpId))) {
141
0
    return NS_ERROR_FAILURE;
142
0
  }
143
0
  if (!html->IsRegistrableDomainSuffixOfOrEqualTo(
144
0
      NS_ConvertUTF8toUTF16(inputRpId), originHost)) {
145
0
    return NS_ERROR_DOM_SECURITY_ERR;
146
0
  }
147
0
148
0
  aRelaxedRpId.Assign(inputRpId);
149
0
  return NS_OK;
150
0
}
151
152
/***********************************************************************
153
 * WebAuthnManager Implementation
154
 **********************************************************************/
155
156
void
157
WebAuthnManager::ClearTransaction()
158
0
{
159
0
  if (!NS_WARN_IF(mTransaction.isNothing())) {
160
0
    StopListeningForVisibilityEvents();
161
0
  }
162
0
163
0
  mTransaction.reset();
164
0
  Unfollow();
165
0
}
166
167
void
168
WebAuthnManager::RejectTransaction(const nsresult& aError)
169
0
{
170
0
  if (!NS_WARN_IF(mTransaction.isNothing())) {
171
0
    mTransaction.ref().mPromise->MaybeReject(aError);
172
0
  }
173
0
174
0
  ClearTransaction();
175
0
}
176
177
void
178
WebAuthnManager::CancelTransaction(const nsresult& aError)
179
0
{
180
0
  if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
181
0
    mChild->SendRequestCancel(mTransaction.ref().mId);
182
0
  }
183
0
184
0
  RejectTransaction(aError);
185
0
}
186
187
WebAuthnManager::~WebAuthnManager()
188
0
{
189
0
  MOZ_ASSERT(NS_IsMainThread());
190
0
191
0
  if (mTransaction.isSome()) {
192
0
    RejectTransaction(NS_ERROR_ABORT);
193
0
  }
194
0
195
0
  if (mChild) {
196
0
    RefPtr<WebAuthnTransactionChild> c;
197
0
    mChild.swap(c);
198
0
    c->Disconnect();
199
0
  }
200
0
}
201
202
already_AddRefed<Promise>
203
WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptions,
204
                                const Optional<OwningNonNull<AbortSignal>>& aSignal)
205
0
{
206
0
  MOZ_ASSERT(NS_IsMainThread());
207
0
208
0
  if (mTransaction.isSome()) {
209
0
    CancelTransaction(NS_ERROR_ABORT);
210
0
  }
211
0
212
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
213
0
214
0
  ErrorResult rv;
215
0
  RefPtr<Promise> promise = Promise::Create(global, rv);
216
0
  if (rv.Failed()) {
217
0
    return nullptr;
218
0
  }
219
0
220
0
  // Abort the request if aborted flag is already set.
221
0
  if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
222
0
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
223
0
    return promise.forget();
224
0
  }
225
0
226
0
  nsString origin;
227
0
  nsCString rpId;
228
0
  rv = GetOrigin(mParent, origin, rpId);
229
0
  if (NS_WARN_IF(rv.Failed())) {
230
0
    promise->MaybeReject(rv);
231
0
    return promise.forget();
232
0
  }
233
0
234
0
  // Enforce 5.4.3 User Account Parameters for Credential Generation
235
0
  // When we add UX, we'll want to do more with this value, but for now
236
0
  // we just have to verify its correctness.
237
0
  {
238
0
    CryptoBuffer userId;
239
0
    userId.Assign(aOptions.mUser.mId);
240
0
    if (userId.Length() > 64) {
241
0
      promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
242
0
      return promise.forget();
243
0
    }
244
0
  }
245
0
246
0
  // If timeoutSeconds was specified, check if its value lies within a
247
0
  // reasonable range as defined by the platform and if not, correct it to the
248
0
  // closest value lying within that range.
249
0
250
0
  uint32_t adjustedTimeout = 30000;
251
0
  if (aOptions.mTimeout.WasPassed()) {
252
0
    adjustedTimeout = aOptions.mTimeout.Value();
253
0
    adjustedTimeout = std::max(15000u, adjustedTimeout);
254
0
    adjustedTimeout = std::min(120000u, adjustedTimeout);
255
0
  }
256
0
257
0
  if (aOptions.mRp.mId.WasPassed()) {
258
0
    // If rpId is specified, then invoke the procedure used for relaxing the
259
0
    // same-origin restriction by setting the document.domain attribute, using
260
0
    // rpId as the given value but without changing the current document’s
261
0
    // domain. If no errors are thrown, set rpId to the value of host as
262
0
    // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
263
0
    // Otherwise, reject promise with a DOMException whose name is
264
0
    // "SecurityError", and terminate this algorithm.
265
0
266
0
    if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRp.mId.Value(), rpId))) {
267
0
      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
268
0
      return promise.forget();
269
0
    }
270
0
  }
271
0
272
0
  // <https://w3c.github.io/webauthn/#sctn-appid-extension>
273
0
  if (aOptions.mExtensions.mAppid.WasPassed()) {
274
0
    promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
275
0
    return promise.forget();
276
0
  }
277
0
278
0
  // TODO: Move this logic into U2FTokenManager in Bug 1409220.
279
0
280
0
  // Process each element of mPubKeyCredParams using the following steps, to
281
0
  // produce a new sequence acceptableParams.
282
0
  nsTArray<PublicKeyCredentialParameters> acceptableParams;
283
0
  for (size_t a = 0; a < aOptions.mPubKeyCredParams.Length(); ++a) {
284
0
    // Let current be the currently selected element of
285
0
    // mPubKeyCredParams.
286
0
287
0
    // If current.type does not contain a PublicKeyCredentialType
288
0
    // supported by this implementation, then stop processing current and move
289
0
    // on to the next element in mPubKeyCredParams.
290
0
    if (aOptions.mPubKeyCredParams[a].mType != PublicKeyCredentialType::Public_key) {
291
0
      continue;
292
0
    }
293
0
294
0
    nsString algName;
295
0
    if (NS_FAILED(CoseAlgorithmToWebCryptoId(aOptions.mPubKeyCredParams[a].mAlg,
296
0
                                             algName))) {
297
0
      continue;
298
0
    }
299
0
300
0
    if (!acceptableParams.AppendElement(aOptions.mPubKeyCredParams[a],
301
0
                                        mozilla::fallible)){
302
0
      promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
303
0
      return promise.forget();
304
0
    }
305
0
  }
306
0
307
0
  // If acceptableParams is empty and mPubKeyCredParams was not empty, cancel
308
0
  // the timer started in step 2, reject promise with a DOMException whose name
309
0
  // is "NotSupportedError", and terminate this algorithm.
310
0
  if (acceptableParams.IsEmpty() && !aOptions.mPubKeyCredParams.IsEmpty()) {
311
0
    promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
312
0
    return promise.forget();
313
0
  }
314
0
315
0
  // If excludeList is undefined, set it to the empty list.
316
0
  //
317
0
  // If extensions was specified, process any extensions supported by this
318
0
  // client platform, to produce the extension data that needs to be sent to the
319
0
  // authenticator. If an error is encountered while processing an extension,
320
0
  // skip that extension and do not produce any extension data for it. Call the
321
0
  // result of this processing clientExtensions.
322
0
  //
323
0
  // Currently no extensions are supported
324
0
  //
325
0
  // Use attestationChallenge, callerOrigin and rpId, along with the token
326
0
  // binding key associated with callerOrigin (if any), to create a ClientData
327
0
  // structure representing this request. Choose a hash algorithm for hashAlg
328
0
  // and compute the clientDataJSON and clientDataHash.
329
0
330
0
  CryptoBuffer challenge;
331
0
  if (!challenge.Assign(aOptions.mChallenge)) {
332
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
333
0
    return promise.forget();
334
0
  }
335
0
336
0
  nsAutoCString clientDataJSON;
337
0
  nsresult srv = AssembleClientData(origin, challenge,
338
0
                                    NS_LITERAL_STRING("webauthn.create"),
339
0
                                    aOptions.mExtensions, clientDataJSON);
340
0
  if (NS_WARN_IF(NS_FAILED(srv))) {
341
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
342
0
    return promise.forget();
343
0
  }
344
0
345
0
  nsTArray<WebAuthnScopedCredential> excludeList;
346
0
  for (const auto& s: aOptions.mExcludeCredentials) {
347
0
    WebAuthnScopedCredential c;
348
0
    CryptoBuffer cb;
349
0
    cb.Assign(s.mId);
350
0
    c.id() = cb;
351
0
    excludeList.AppendElement(c);
352
0
  }
353
0
354
0
  if (!MaybeCreateBackgroundActor()) {
355
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
356
0
    return promise.forget();
357
0
  }
358
0
359
0
  // TODO: Add extension list building
360
0
  nsTArray<WebAuthnExtension> extensions;
361
0
362
0
  const auto& selection = aOptions.mAuthenticatorSelection;
363
0
  const auto& attachment = selection.mAuthenticatorAttachment;
364
0
  const AttestationConveyancePreference& attestation = aOptions.mAttestation;
365
0
366
0
  // Does the RP require attachment == "platform"?
367
0
  bool requirePlatformAttachment =
368
0
    attachment.WasPassed() && attachment.Value() == AuthenticatorAttachment::Platform;
369
0
370
0
  // Does the RP require user verification?
371
0
  bool requireUserVerification =
372
0
    selection.mUserVerification == UserVerificationRequirement::Required;
373
0
374
0
  // Does the RP desire direct attestation? Indirect attestation is not
375
0
  // implemented, and thus is equivilent to None.
376
0
  bool requestDirectAttestation =
377
0
    attestation == AttestationConveyancePreference::Direct;
378
0
379
0
  // Create and forward authenticator selection criteria.
380
0
  WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
381
0
                                               requireUserVerification,
382
0
                                               requirePlatformAttachment);
383
0
384
0
  WebAuthnMakeCredentialExtraInfo extra(extensions,
385
0
                                        authSelection,
386
0
                                        requestDirectAttestation);
387
0
388
0
  WebAuthnMakeCredentialInfo info(origin,
389
0
                                  NS_ConvertUTF8toUTF16(rpId),
390
0
                                  challenge,
391
0
                                  clientDataJSON,
392
0
                                  adjustedTimeout,
393
0
                                  excludeList,
394
0
                                  extra);
395
0
396
0
  ListenForVisibilityEvents();
397
0
398
0
  AbortSignal* signal = nullptr;
399
0
  if (aSignal.WasPassed()) {
400
0
    signal = &aSignal.Value();
401
0
    Follow(signal);
402
0
  }
403
0
404
0
  MOZ_ASSERT(mTransaction.isNothing());
405
0
  mTransaction = Some(WebAuthnTransaction(promise));
406
0
  mChild->SendRequestRegister(mTransaction.ref().mId, info);
407
0
408
0
  return promise.forget();
409
0
}
410
411
already_AddRefed<Promise>
412
WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
413
                              const Optional<OwningNonNull<AbortSignal>>& aSignal)
414
0
{
415
0
  MOZ_ASSERT(NS_IsMainThread());
416
0
417
0
  if (mTransaction.isSome()) {
418
0
    CancelTransaction(NS_ERROR_ABORT);
419
0
  }
420
0
421
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
422
0
423
0
  ErrorResult rv;
424
0
  RefPtr<Promise> promise = Promise::Create(global, rv);
425
0
  if (rv.Failed()) {
426
0
    return nullptr;
427
0
  }
428
0
429
0
  // Abort the request if aborted flag is already set.
430
0
  if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
431
0
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
432
0
    return promise.forget();
433
0
  }
434
0
435
0
  nsString origin;
436
0
  nsCString rpId;
437
0
  rv = GetOrigin(mParent, origin, rpId);
438
0
  if (NS_WARN_IF(rv.Failed())) {
439
0
    promise->MaybeReject(rv);
440
0
    return promise.forget();
441
0
  }
442
0
443
0
  // If timeoutSeconds was specified, check if its value lies within a
444
0
  // reasonable range as defined by the platform and if not, correct it to the
445
0
  // closest value lying within that range.
446
0
447
0
  uint32_t adjustedTimeout = 30000;
448
0
  if (aOptions.mTimeout.WasPassed()) {
449
0
    adjustedTimeout = aOptions.mTimeout.Value();
450
0
    adjustedTimeout = std::max(15000u, adjustedTimeout);
451
0
    adjustedTimeout = std::min(120000u, adjustedTimeout);
452
0
  }
453
0
454
0
  if (aOptions.mRpId.WasPassed()) {
455
0
    // If rpId is specified, then invoke the procedure used for relaxing the
456
0
    // same-origin restriction by setting the document.domain attribute, using
457
0
    // rpId as the given value but without changing the current document’s
458
0
    // domain. If no errors are thrown, set rpId to the value of host as
459
0
    // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
460
0
    // Otherwise, reject promise with a DOMException whose name is
461
0
    // "SecurityError", and terminate this algorithm.
462
0
463
0
    if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRpId.Value(), rpId))) {
464
0
      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
465
0
      return promise.forget();
466
0
    }
467
0
  }
468
0
469
0
  CryptoBuffer rpIdHash;
470
0
  if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
471
0
    promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
472
0
    return promise.forget();
473
0
  }
474
0
475
0
  // Use assertionChallenge, callerOrigin and rpId, along with the token binding
476
0
  // key associated with callerOrigin (if any), to create a ClientData structure
477
0
  // representing this request. Choose a hash algorithm for hashAlg and compute
478
0
  // the clientDataJSON and clientDataHash.
479
0
  CryptoBuffer challenge;
480
0
  if (!challenge.Assign(aOptions.mChallenge)) {
481
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
482
0
    return promise.forget();
483
0
  }
484
0
485
0
  nsAutoCString clientDataJSON;
486
0
  nsresult srv = AssembleClientData(origin, challenge,
487
0
                                    NS_LITERAL_STRING("webauthn.get"),
488
0
                                    aOptions.mExtensions, clientDataJSON);
489
0
  if (NS_WARN_IF(NS_FAILED(srv))) {
490
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
491
0
    return promise.forget();
492
0
  }
493
0
494
0
  nsTArray<WebAuthnScopedCredential> allowList;
495
0
  for (const auto& s: aOptions.mAllowCredentials) {
496
0
    if (s.mType == PublicKeyCredentialType::Public_key) {
497
0
      WebAuthnScopedCredential c;
498
0
      CryptoBuffer cb;
499
0
      cb.Assign(s.mId);
500
0
      c.id() = cb;
501
0
502
0
      // Serialize transports.
503
0
      if (s.mTransports.WasPassed()) {
504
0
        uint8_t transports = 0;
505
0
        for (const auto& t: s.mTransports.Value()) {
506
0
          if (t == AuthenticatorTransport::Usb) {
507
0
            transports |= U2F_AUTHENTICATOR_TRANSPORT_USB;
508
0
          }
509
0
          if (t == AuthenticatorTransport::Nfc) {
510
0
            transports |= U2F_AUTHENTICATOR_TRANSPORT_NFC;
511
0
          }
512
0
          if (t == AuthenticatorTransport::Ble) {
513
0
            transports |= U2F_AUTHENTICATOR_TRANSPORT_BLE;
514
0
          }
515
0
        }
516
0
        c.transports() = transports;
517
0
      }
518
0
519
0
      allowList.AppendElement(c);
520
0
    }
521
0
  }
522
0
523
0
  if (!MaybeCreateBackgroundActor()) {
524
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
525
0
    return promise.forget();
526
0
  }
527
0
528
0
  // Does the RP require user verification?
529
0
  bool requireUserVerification =
530
0
    aOptions.mUserVerification == UserVerificationRequirement::Required;
531
0
532
0
  // If extensions were specified, process any extensions supported by this
533
0
  // client platform, to produce the extension data that needs to be sent to the
534
0
  // authenticator. If an error is encountered while processing an extension,
535
0
  // skip that extension and do not produce any extension data for it. Call the
536
0
  // result of this processing clientExtensions.
537
0
  nsTArray<WebAuthnExtension> extensions;
538
0
539
0
  // <https://w3c.github.io/webauthn/#sctn-appid-extension>
540
0
  if (aOptions.mExtensions.mAppid.WasPassed()) {
541
0
    nsString appId(aOptions.mExtensions.mAppid.Value());
542
0
543
0
    // Check that the appId value is allowed.
544
0
    if (!EvaluateAppID(mParent, origin, U2FOperation::Sign, appId)) {
545
0
      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
546
0
      return promise.forget();
547
0
    }
548
0
549
0
    CryptoBuffer appIdHash;
550
0
    if (!appIdHash.SetLength(SHA256_LENGTH, fallible)) {
551
0
      promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
552
0
      return promise.forget();
553
0
    }
554
0
555
0
    // We need the SHA-256 hash of the appId.
556
0
    srv = HashCString(NS_ConvertUTF16toUTF8(appId), appIdHash);
557
0
    if (NS_WARN_IF(NS_FAILED(srv))) {
558
0
      promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
559
0
      return promise.forget();
560
0
    }
561
0
562
0
    // Append the hash and send it to the backend.
563
0
    extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
564
0
  }
565
0
566
0
  WebAuthnGetAssertionExtraInfo extra(extensions, requireUserVerification);
567
0
568
0
  WebAuthnGetAssertionInfo info(origin,
569
0
                                NS_ConvertUTF8toUTF16(rpId),
570
0
                                challenge,
571
0
                                clientDataJSON,
572
0
                                adjustedTimeout,
573
0
                                allowList,
574
0
                                extra);
575
0
576
0
  ListenForVisibilityEvents();
577
0
578
0
  AbortSignal* signal = nullptr;
579
0
  if (aSignal.WasPassed()) {
580
0
    signal = &aSignal.Value();
581
0
    Follow(signal);
582
0
  }
583
0
584
0
  MOZ_ASSERT(mTransaction.isNothing());
585
0
  mTransaction = Some(WebAuthnTransaction(promise));
586
0
  mChild->SendRequestSign(mTransaction.ref().mId, info);
587
0
588
0
  return promise.forget();
589
0
}
590
591
already_AddRefed<Promise>
592
WebAuthnManager::Store(const Credential& aCredential)
593
0
{
594
0
  MOZ_ASSERT(NS_IsMainThread());
595
0
596
0
  if (mTransaction.isSome()) {
597
0
    CancelTransaction(NS_ERROR_ABORT);
598
0
  }
599
0
600
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
601
0
602
0
  ErrorResult rv;
603
0
  RefPtr<Promise> promise = Promise::Create(global, rv);
604
0
  if (rv.Failed()) {
605
0
    return nullptr;
606
0
  }
607
0
608
0
  promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
609
0
  return promise.forget();
610
0
}
611
612
void
613
WebAuthnManager::FinishMakeCredential(const uint64_t& aTransactionId,
614
                                      const WebAuthnMakeCredentialResult& aResult)
615
0
{
616
0
  MOZ_ASSERT(NS_IsMainThread());
617
0
618
0
  // Check for a valid transaction.
619
0
  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
620
0
    return;
621
0
  }
622
0
623
0
  CryptoBuffer clientDataBuf;
624
0
  if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
625
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
626
0
    return;
627
0
  }
628
0
629
0
  CryptoBuffer attObjBuf;
630
0
  if (NS_WARN_IF(!attObjBuf.Assign(aResult.AttestationObject()))) {
631
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
632
0
    return;
633
0
  }
634
0
635
0
  CryptoBuffer keyHandleBuf;
636
0
  if (NS_WARN_IF(!keyHandleBuf.Assign(aResult.KeyHandle()))) {
637
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
638
0
    return;
639
0
  }
640
0
641
0
  nsAutoString keyHandleBase64Url;
642
0
  nsresult rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url);
643
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
644
0
    RejectTransaction(rv);
645
0
    return;
646
0
  }
647
0
648
0
  // Create a new PublicKeyCredential object and populate its fields with the
649
0
  // values returned from the authenticator as well as the clientDataJSON
650
0
  // computed earlier.
651
0
  RefPtr<AuthenticatorAttestationResponse> attestation =
652
0
      new AuthenticatorAttestationResponse(mParent);
653
0
  attestation->SetClientDataJSON(clientDataBuf);
654
0
  attestation->SetAttestationObject(attObjBuf);
655
0
656
0
  RefPtr<PublicKeyCredential> credential =
657
0
      new PublicKeyCredential(mParent);
658
0
  credential->SetId(keyHandleBase64Url);
659
0
  credential->SetType(NS_LITERAL_STRING("public-key"));
660
0
  credential->SetRawId(keyHandleBuf);
661
0
  credential->SetResponse(attestation);
662
0
663
0
  mTransaction.ref().mPromise->MaybeResolve(credential);
664
0
  ClearTransaction();
665
0
}
666
667
void
668
WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
669
                                    const WebAuthnGetAssertionResult& aResult)
670
0
{
671
0
  MOZ_ASSERT(NS_IsMainThread());
672
0
673
0
  // Check for a valid transaction.
674
0
  if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
675
0
    return;
676
0
  }
677
0
678
0
  CryptoBuffer clientDataBuf;
679
0
  if (!clientDataBuf.Assign(aResult.ClientDataJSON())) {
680
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
681
0
    return;
682
0
  }
683
0
684
0
  CryptoBuffer credentialBuf;
685
0
  if (!credentialBuf.Assign(aResult.KeyHandle())) {
686
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
687
0
    return;
688
0
  }
689
0
690
0
  CryptoBuffer signatureBuf;
691
0
  if (!signatureBuf.Assign(aResult.Signature())) {
692
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
693
0
    return;
694
0
  }
695
0
696
0
  CryptoBuffer authenticatorDataBuf;
697
0
  if (!authenticatorDataBuf.Assign(aResult.AuthenticatorData())) {
698
0
    RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
699
0
    return;
700
0
  }
701
0
702
0
  nsAutoString credentialBase64Url;
703
0
  nsresult rv = credentialBuf.ToJwkBase64(credentialBase64Url);
704
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
705
0
    RejectTransaction(rv);
706
0
    return;
707
0
  }
708
0
709
0
  // If any authenticator returns success:
710
0
711
0
  // Create a new PublicKeyCredential object named value and populate its fields
712
0
  // with the values returned from the authenticator as well as the
713
0
  // clientDataJSON computed earlier.
714
0
  RefPtr<AuthenticatorAssertionResponse> assertion =
715
0
    new AuthenticatorAssertionResponse(mParent);
716
0
  assertion->SetClientDataJSON(clientDataBuf);
717
0
  assertion->SetAuthenticatorData(authenticatorDataBuf);
718
0
  assertion->SetSignature(signatureBuf);
719
0
720
0
  RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
721
0
  credential->SetId(credentialBase64Url);
722
0
  credential->SetType(NS_LITERAL_STRING("public-key"));
723
0
  credential->SetRawId(credentialBuf);
724
0
  credential->SetResponse(assertion);
725
0
726
0
  // Forward client extension results.
727
0
  for (auto& ext: aResult.Extensions()) {
728
0
    if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId) {
729
0
      bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
730
0
      credential->SetClientExtensionResultAppId(appid);
731
0
    }
732
0
  }
733
0
734
0
  mTransaction.ref().mPromise->MaybeResolve(credential);
735
0
  ClearTransaction();
736
0
}
737
738
void
739
WebAuthnManager::RequestAborted(const uint64_t& aTransactionId,
740
                                const nsresult& aError)
741
0
{
742
0
  MOZ_ASSERT(NS_IsMainThread());
743
0
744
0
  if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
745
0
    RejectTransaction(aError);
746
0
  }
747
0
}
748
749
void
750
WebAuthnManager::Abort()
751
0
{
752
0
  CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
753
0
}
754
755
}
756
}