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/U2F.h"
8 #include "mozilla/dom/WebCryptoCommon.h"
9 #include "mozilla/ipc/PBackgroundChild.h"
10 #include "mozilla/ipc/BackgroundChild.h"
11 #include "mozilla/dom/WebAuthnTransactionChild.h"
12 #include "mozilla/dom/WebAuthnUtil.h"
13 #include "nsContentUtils.h"
14 #include "nsICryptoHash.h"
15 #include "nsIEffectiveTLDService.h"
16 #include "nsNetCID.h"
17 #include "nsNetUtil.h"
18 #include "nsURLParsers.h"
19 #include "U2FUtil.h"
20 #include "hasht.h"
21 
22 using namespace mozilla::ipc;
23 
24 // Forward decl because of nsHTMLDocument.h's complex dependency on
25 // /layout/style
26 class nsHTMLDocument {
27  public:
28   bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
29                                             const nsACString& aOrigHost);
30 };
31 
32 namespace mozilla {
33 namespace dom {
34 
35 static mozilla::LazyLogModule gU2FLog("u2fmanager");
36 
37 NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
38 NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
39 
40 // Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
41 NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1,
42                         "https://www.gstatic.com/securitykey/origins.json");
43 NS_NAMED_LITERAL_STRING(
44     kGoogleAccountsAppId2,
45     "https://www.gstatic.com/securitykey/a/google.com/origins.json");
46 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
48   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
49   NS_INTERFACE_MAP_ENTRY(nsISupports)
50   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
51 NS_INTERFACE_MAP_END
52 
53 NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
54 NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
55 
56 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
57 
58 /***********************************************************************
59  * Utility Functions
60  **********************************************************************/
61 
62 static ErrorCode ConvertNSResultToErrorCode(const nsresult& aError) {
63   if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
64     return ErrorCode::TIMEOUT;
65   }
66   /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
67   if (aError == NS_ERROR_DOM_INVALID_STATE_ERR) {
68     return ErrorCode::DEVICE_INELIGIBLE;
69   }
70   return ErrorCode::OTHER_ERROR;
71 }
72 
AdjustedTimeoutMillis(const Optional<Nullable<int32_t>> & opt_aSeconds)73 static uint32_t AdjustedTimeoutMillis(
74     const Optional<Nullable<int32_t>>& opt_aSeconds) {
75   uint32_t adjustedTimeoutMillis = 30000u;
76   if (opt_aSeconds.WasPassed() && !opt_aSeconds.Value().IsNull()) {
77     adjustedTimeoutMillis = opt_aSeconds.Value().Value() * 1000u;
78     adjustedTimeoutMillis = std::max(15000u, adjustedTimeoutMillis);
79     adjustedTimeoutMillis = std::min(120000u, adjustedTimeoutMillis);
80   }
81   return adjustedTimeoutMillis;
82 }
83 
AssembleClientData(const nsAString & aOrigin,const nsAString & aTyp,const nsAString & aChallenge,nsString & aClientData)84 static nsresult AssembleClientData(const nsAString& aOrigin,
85                                    const nsAString& aTyp,
86                                    const nsAString& aChallenge,
87                                    /* out */ nsString& aClientData) {
88   MOZ_ASSERT(NS_IsMainThread());
89   U2FClientData clientDataObject;
90   clientDataObject.mTyp.Construct(aTyp);  // "Typ" from the U2F specification
91   clientDataObject.mChallenge.Construct(aChallenge);
92   clientDataObject.mOrigin.Construct(aOrigin);
93 
94   if (NS_WARN_IF(!clientDataObject.ToJSON(aClientData))) {
95     return NS_ERROR_FAILURE;
96   }
97 
98   return NS_OK;
99 }
100 
RegisteredKeysToScopedCredentialList(const nsAString & aAppId,const nsTArray<RegisteredKey> & aKeys,nsTArray<WebAuthnScopedCredential> & aList)101 static void RegisteredKeysToScopedCredentialList(
102     const nsAString& aAppId, const nsTArray<RegisteredKey>& aKeys,
103     nsTArray<WebAuthnScopedCredential>& aList) {
104   for (const RegisteredKey& key : aKeys) {
105     // Check for required attributes
106     if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() ||
107         key.mVersion.Value() != kRequiredU2FVersion) {
108       continue;
109     }
110 
111     // If this key's mAppId doesn't match the invocation, we can't handle it.
112     if (key.mAppId.WasPassed() && !key.mAppId.Value().Equals(aAppId)) {
113       continue;
114     }
115 
116     CryptoBuffer keyHandle;
117     nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value());
118     if (NS_WARN_IF(NS_FAILED(rv))) {
119       continue;
120     }
121 
122     WebAuthnScopedCredential c;
123     c.id() = keyHandle;
124     aList.AppendElement(c);
125   }
126 }
127 
BuildTransactionHashes(const nsCString & aRpId,const nsCString & aClientDataJSON,CryptoBuffer & aRpIdHash,CryptoBuffer & aClientDataHash)128 static nsresult BuildTransactionHashes(
129     const nsCString& aRpId, const nsCString& aClientDataJSON,
130     /* out */ CryptoBuffer& aRpIdHash,
131     /* out */ CryptoBuffer& aClientDataHash) {
132   nsresult srv;
133   nsCOMPtr<nsICryptoHash> hashService =
134       do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
135   if (NS_FAILED(srv)) {
136     return srv;
137   }
138 
139   if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
140     return NS_ERROR_OUT_OF_MEMORY;
141   }
142   srv = HashCString(hashService, aRpId, aRpIdHash);
143   if (NS_WARN_IF(NS_FAILED(srv))) {
144     return NS_ERROR_FAILURE;
145   }
146 
147   if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
148     return NS_ERROR_OUT_OF_MEMORY;
149   }
150   srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
151   if (NS_WARN_IF(NS_FAILED(srv))) {
152     return NS_ERROR_FAILURE;
153   }
154 
155   if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
156     nsString base64;
157     Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
158 
159     MOZ_LOG(gU2FLog, LogLevel::Debug,
160             ("dom::U2FManager::RpID: %s", aRpId.get()));
161 
162     MOZ_LOG(gU2FLog, LogLevel::Debug,
163             ("dom::U2FManager::Rp ID Hash (base64): %s",
164              NS_ConvertUTF16toUTF8(base64).get()));
165 
166     Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
167 
168     MOZ_LOG(gU2FLog, LogLevel::Debug,
169             ("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
170 
171     MOZ_LOG(gU2FLog, LogLevel::Debug,
172             ("dom::U2FManager::Client Data Hash (base64): %s",
173              NS_ConvertUTF16toUTF8(base64).get()));
174   }
175 
176   return NS_OK;
177 }
178 
179 /***********************************************************************
180  * U2F JavaScript API Implementation
181  **********************************************************************/
182 
~U2F()183 U2F::~U2F() {
184   MOZ_ASSERT(NS_IsMainThread());
185 
186   if (mTransaction.isSome()) {
187     ClearTransaction();
188   }
189 
190   if (mChild) {
191     RefPtr<WebAuthnTransactionChild> c;
192     mChild.swap(c);
193     c->Disconnect();
194   }
195 }
196 
Init(ErrorResult & aRv)197 void U2F::Init(ErrorResult& aRv) {
198   MOZ_ASSERT(mParent);
199 
200   nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
201   MOZ_ASSERT(doc);
202   if (!doc) {
203     aRv.Throw(NS_ERROR_FAILURE);
204     return;
205   }
206 
207   nsIPrincipal* principal = doc->NodePrincipal();
208   aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
209   if (NS_WARN_IF(aRv.Failed())) {
210     return;
211   }
212 
213   if (NS_WARN_IF(mOrigin.IsEmpty())) {
214     aRv.Throw(NS_ERROR_FAILURE);
215     return;
216   }
217 }
218 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)219 /* virtual */ JSObject* U2F::WrapObject(JSContext* aCx,
220                                         JS::Handle<JSObject*> aGivenProto) {
221   return U2FBinding::Wrap(aCx, this, aGivenProto);
222 }
223 
224 template <typename T, typename C>
ExecuteCallback(T & aResp,nsMainThreadPtrHandle<C> & aCb)225 void U2F::ExecuteCallback(T& aResp, nsMainThreadPtrHandle<C>& aCb) {
226   MOZ_ASSERT(NS_IsMainThread());
227   MOZ_ASSERT(aCb);
228 
229   // Assert that mTransaction was cleared before before we were called to allow
230   // reentrancy from microtask checkpoints.
231   MOZ_ASSERT(mTransaction.isNothing());
232 
233   ErrorResult error;
234   aCb->Call(aResp, error);
235   NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed");
236   error.SuppressException();  // Useful exceptions already emitted
237 }
238 
Register(const nsAString & aAppId,const Sequence<RegisterRequest> & aRegisterRequests,const Sequence<RegisteredKey> & aRegisteredKeys,U2FRegisterCallback & aCallback,const Optional<Nullable<int32_t>> & opt_aTimeoutSeconds,ErrorResult & aRv)239 void U2F::Register(const nsAString& aAppId,
240                    const Sequence<RegisterRequest>& aRegisterRequests,
241                    const Sequence<RegisteredKey>& aRegisteredKeys,
242                    U2FRegisterCallback& aCallback,
243                    const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
244                    ErrorResult& aRv) {
245   MOZ_ASSERT(NS_IsMainThread());
246 
247   if (mTransaction.isSome()) {
248     CancelTransaction(NS_ERROR_ABORT);
249   }
250 
251   nsMainThreadPtrHandle<U2FRegisterCallback> callback(
252       new nsMainThreadPtrHolder<U2FRegisterCallback>("U2F::Register::callback",
253                                                      &aCallback));
254 
255   // Ensure we have a callback.
256   if (NS_WARN_IF(!callback)) {
257     return;
258   }
259 
260   // Evaluate the AppID
261   nsString adjustedAppId(aAppId);
262   if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Register, adjustedAppId)) {
263     RegisterResponse response;
264     response.mErrorCode.Construct(
265         static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
266     ExecuteCallback(response, callback);
267     return;
268   }
269 
270   // Produce the AppParam from the current AppID
271   nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
272 
273   nsAutoString clientDataJSON;
274 
275   // Pick the first valid RegisterRequest; we can only work with one.
276   for (const RegisterRequest& req : aRegisterRequests) {
277     if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() ||
278         req.mVersion.Value() != kRequiredU2FVersion) {
279       continue;
280     }
281 
282     nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
283                                      req.mChallenge.Value(), clientDataJSON);
284     if (NS_WARN_IF(NS_FAILED(rv))) {
285       continue;
286     }
287   }
288 
289   // Did we not get a valid RegisterRequest? Abort.
290   if (clientDataJSON.IsEmpty()) {
291     RegisterResponse response;
292     response.mErrorCode.Construct(
293         static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
294     ExecuteCallback(response, callback);
295     return;
296   }
297 
298   // Build the exclusion list, if any
299   nsTArray<WebAuthnScopedCredential> excludeList;
300   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
301                                        excludeList);
302 
303   auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
304 
305   CryptoBuffer rpIdHash, clientDataHash;
306   if (NS_FAILED(BuildTransactionHashes(cAppId, clientData, rpIdHash,
307                                        clientDataHash))) {
308     RegisterResponse response;
309     response.mErrorCode.Construct(
310         static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
311     ExecuteCallback(response, callback);
312     return;
313   }
314 
315   if (!MaybeCreateBackgroundActor()) {
316     RegisterResponse response;
317     response.mErrorCode.Construct(
318         static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
319     ExecuteCallback(response, callback);
320     return;
321   }
322 
323   ListenForVisibilityEvents();
324 
325   // Always blank for U2F
326   nsTArray<WebAuthnExtension> extensions;
327 
328   // Default values for U2F.
329   WebAuthnAuthenticatorSelection authSelection(
330       false /* requireResidentKey */, false /* requireUserVerification */,
331       false /* requirePlatformAttachment */);
332 
333   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
334 
335   WebAuthnMakeCredentialInfo info(
336       mOrigin, rpIdHash, clientDataHash, adjustedTimeoutMillis, excludeList,
337       extensions, authSelection, false /* RequestDirectAttestation */);
338 
339   MOZ_ASSERT(mTransaction.isNothing());
340   mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
341   mChild->SendRequestRegister(mTransaction.ref().mId, info);
342 }
343 
FinishMakeCredential(const uint64_t & aTransactionId,const WebAuthnMakeCredentialResult & aResult)344 void U2F::FinishMakeCredential(const uint64_t& aTransactionId,
345                                const WebAuthnMakeCredentialResult& aResult) {
346   MOZ_ASSERT(NS_IsMainThread());
347 
348   // Check for a valid transaction.
349   if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
350     return;
351   }
352 
353   if (NS_WARN_IF(!mTransaction.ref().HasRegisterCallback())) {
354     RejectTransaction(NS_ERROR_ABORT);
355     return;
356   }
357 
358   CryptoBuffer clientDataBuf;
359   if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
360     RejectTransaction(NS_ERROR_ABORT);
361     return;
362   }
363 
364   CryptoBuffer regBuf;
365   if (NS_WARN_IF(!regBuf.Assign(aResult.RegBuffer()))) {
366     RejectTransaction(NS_ERROR_ABORT);
367     return;
368   }
369 
370   nsString clientDataBase64;
371   nsString registrationDataBase64;
372   nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
373   nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
374 
375   if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
376       NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
377     RejectTransaction(NS_ERROR_ABORT);
378     return;
379   }
380 
381   // Assemble a response object to return
382   RegisterResponse response;
383   response.mVersion.Construct(kRequiredU2FVersion);
384   response.mClientData.Construct(clientDataBase64);
385   response.mRegistrationData.Construct(registrationDataBase64);
386   response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
387 
388   // Keep the callback pointer alive.
389   nsMainThreadPtrHandle<U2FRegisterCallback> callback(
390       mTransaction.ref().GetRegisterCallback());
391 
392   ClearTransaction();
393   ExecuteCallback(response, callback);
394 }
395 
Sign(const nsAString & aAppId,const nsAString & aChallenge,const Sequence<RegisteredKey> & aRegisteredKeys,U2FSignCallback & aCallback,const Optional<Nullable<int32_t>> & opt_aTimeoutSeconds,ErrorResult & aRv)396 void U2F::Sign(const nsAString& aAppId, const nsAString& aChallenge,
397                const Sequence<RegisteredKey>& aRegisteredKeys,
398                U2FSignCallback& aCallback,
399                const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
400                ErrorResult& aRv) {
401   MOZ_ASSERT(NS_IsMainThread());
402 
403   if (mTransaction.isSome()) {
404     CancelTransaction(NS_ERROR_ABORT);
405   }
406 
407   nsMainThreadPtrHandle<U2FSignCallback> callback(
408       new nsMainThreadPtrHolder<U2FSignCallback>("U2F::Sign::callback",
409                                                  &aCallback));
410 
411   // Ensure we have a callback.
412   if (NS_WARN_IF(!callback)) {
413     return;
414   }
415 
416   // Evaluate the AppID
417   nsString adjustedAppId(aAppId);
418   if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Sign, adjustedAppId)) {
419     SignResponse response;
420     response.mErrorCode.Construct(
421         static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
422     ExecuteCallback(response, callback);
423     return;
424   }
425 
426   // Produce the AppParam from the current AppID
427   nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
428 
429   nsAutoString clientDataJSON;
430   nsresult rv =
431       AssembleClientData(mOrigin, kGetAssertion, aChallenge, clientDataJSON);
432   if (NS_WARN_IF(NS_FAILED(rv))) {
433     SignResponse response;
434     response.mErrorCode.Construct(
435         static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
436     ExecuteCallback(response, callback);
437     return;
438   }
439 
440   // Build the key list, if any
441   nsTArray<WebAuthnScopedCredential> permittedList;
442   RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
443                                        permittedList);
444 
445   auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
446 
447   CryptoBuffer rpIdHash, clientDataHash;
448   if (NS_FAILED(BuildTransactionHashes(cAppId, clientData, rpIdHash,
449                                        clientDataHash))) {
450     SignResponse response;
451     response.mErrorCode.Construct(
452         static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
453     ExecuteCallback(response, callback);
454     return;
455   }
456 
457   if (!MaybeCreateBackgroundActor()) {
458     SignResponse response;
459     response.mErrorCode.Construct(
460         static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
461     ExecuteCallback(response, callback);
462     return;
463   }
464 
465   ListenForVisibilityEvents();
466 
467   // Always blank for U2F
468   nsTArray<WebAuthnExtension> extensions;
469 
470   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
471 
472   WebAuthnGetAssertionInfo info(mOrigin, rpIdHash, clientDataHash,
473                                 adjustedTimeoutMillis, permittedList,
474                                 false, /* requireUserVerification */
475                                 extensions);
476 
477   MOZ_ASSERT(mTransaction.isNothing());
478   mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback))));
479   mChild->SendRequestSign(mTransaction.ref().mId, info);
480 }
481 
FinishGetAssertion(const uint64_t & aTransactionId,const WebAuthnGetAssertionResult & aResult)482 void U2F::FinishGetAssertion(const uint64_t& aTransactionId,
483                              const WebAuthnGetAssertionResult& aResult) {
484   MOZ_ASSERT(NS_IsMainThread());
485 
486   // Check for a valid transaction.
487   if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
488     return;
489   }
490 
491   if (NS_WARN_IF(!mTransaction.ref().HasSignCallback())) {
492     RejectTransaction(NS_ERROR_ABORT);
493     return;
494   }
495 
496   CryptoBuffer clientDataBuf;
497   if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) {
498     RejectTransaction(NS_ERROR_ABORT);
499     return;
500   }
501 
502   CryptoBuffer credBuf;
503   if (NS_WARN_IF(!credBuf.Assign(aResult.CredentialID()))) {
504     RejectTransaction(NS_ERROR_ABORT);
505     return;
506   }
507 
508   CryptoBuffer sigBuf;
509   if (NS_WARN_IF(!sigBuf.Assign(aResult.SigBuffer()))) {
510     RejectTransaction(NS_ERROR_ABORT);
511     return;
512   }
513 
514   // Assemble a response object to return
515   nsString clientDataBase64;
516   nsString signatureDataBase64;
517   nsString keyHandleBase64;
518   nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
519   nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
520   nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
521   if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
522       NS_WARN_IF(NS_FAILED(rvSignatureData) ||
523                  NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
524     RejectTransaction(NS_ERROR_ABORT);
525     return;
526   }
527 
528   SignResponse response;
529   response.mKeyHandle.Construct(keyHandleBase64);
530   response.mClientData.Construct(clientDataBase64);
531   response.mSignatureData.Construct(signatureDataBase64);
532   response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
533 
534   // Keep the callback pointer alive.
535   nsMainThreadPtrHandle<U2FSignCallback> callback(
536       mTransaction.ref().GetSignCallback());
537 
538   ClearTransaction();
539   ExecuteCallback(response, callback);
540 }
541 
ClearTransaction()542 void U2F::ClearTransaction() {
543   if (!NS_WARN_IF(mTransaction.isNothing())) {
544     StopListeningForVisibilityEvents();
545   }
546 
547   mTransaction.reset();
548 }
549 
RejectTransaction(const nsresult & aError)550 void U2F::RejectTransaction(const nsresult& aError) {
551   if (NS_WARN_IF(mTransaction.isNothing())) {
552     return;
553   }
554 
555   StopListeningForVisibilityEvents();
556 
557   // Clear out mTransaction before calling ExecuteCallback() below to allow
558   // reentrancy from microtask checkpoints.
559   Maybe<U2FTransaction> maybeTransaction(Move(mTransaction));
560   MOZ_ASSERT(mTransaction.isNothing() && maybeTransaction.isSome());
561 
562   U2FTransaction& transaction = maybeTransaction.ref();
563   ErrorCode code = ConvertNSResultToErrorCode(aError);
564 
565   if (transaction.HasRegisterCallback()) {
566     RegisterResponse response;
567     response.mErrorCode.Construct(static_cast<uint32_t>(code));
568     ExecuteCallback(response, transaction.GetRegisterCallback());
569   }
570 
571   if (transaction.HasSignCallback()) {
572     SignResponse response;
573     response.mErrorCode.Construct(static_cast<uint32_t>(code));
574     ExecuteCallback(response, transaction.GetSignCallback());
575   }
576 }
577 
CancelTransaction(const nsresult & aError)578 void U2F::CancelTransaction(const nsresult& aError) {
579   if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
580     mChild->SendRequestCancel(mTransaction.ref().mId);
581   }
582 
583   RejectTransaction(aError);
584 }
585 
RequestAborted(const uint64_t & aTransactionId,const nsresult & aError)586 void U2F::RequestAborted(const uint64_t& aTransactionId,
587                          const nsresult& aError) {
588   MOZ_ASSERT(NS_IsMainThread());
589 
590   if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
591     RejectTransaction(aError);
592   }
593 }
594 
595 }  // namespace dom
596 }  // namespace mozilla
597