LCOV - code coverage report
Current view: top level - dom/webauthn - WebAuthnManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 1 384 0.3 %
Date: 2017-07-14 16:53:18 Functions: 0 29 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       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 "nsICryptoHash.h"
       9             : #include "nsNetCID.h"
      10             : #include "nsThreadUtils.h"
      11             : #include "mozilla/ClearOnShutdown.h"
      12             : #include "mozilla/dom/AuthenticatorAttestationResponse.h"
      13             : #include "mozilla/dom/Promise.h"
      14             : #include "mozilla/dom/WebAuthnManager.h"
      15             : #include "mozilla/dom/WebAuthnUtil.h"
      16             : #include "mozilla/dom/PWebAuthnTransaction.h"
      17             : #include "mozilla/dom/WebAuthnTransactionChild.h"
      18             : #include "mozilla/dom/WebCryptoCommon.h"
      19             : #include "mozilla/ipc/PBackgroundChild.h"
      20             : #include "mozilla/ipc/BackgroundChild.h"
      21             : 
      22             : using namespace mozilla::ipc;
      23             : 
      24             : namespace mozilla {
      25             : namespace dom {
      26             : 
      27             : /***********************************************************************
      28             :  * Statics
      29             :  **********************************************************************/
      30             : 
      31             : namespace {
      32           3 : StaticRefPtr<WebAuthnManager> gWebAuthnManager;
      33             : static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
      34             : }
      35             : 
      36           0 : NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback);
      37             : 
      38             : /***********************************************************************
      39             :  * Utility Functions
      40             :  **********************************************************************/
      41             : 
      42             : template<class OOS>
      43             : static nsresult
      44           0 : GetAlgorithmName(const OOS& aAlgorithm,
      45             :                  /* out */ nsString& aName)
      46             : {
      47           0 :   MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
      48             : 
      49           0 :   if (aAlgorithm.IsString()) {
      50             :     // If string, then treat as algorithm name
      51           0 :     aName.Assign(aAlgorithm.GetAsString());
      52             :   } else {
      53             :     // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
      54             :   }
      55             : 
      56           0 :   if (!NormalizeToken(aName, aName)) {
      57           0 :     return NS_ERROR_DOM_SYNTAX_ERR;
      58             :   }
      59             : 
      60           0 :   return NS_OK;
      61             : }
      62             : 
      63             : static nsresult
      64           0 : HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
      65             :             /* out */ CryptoBuffer& aOut)
      66             : {
      67           0 :   MOZ_ASSERT(aHashService);
      68             : 
      69           0 :   nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
      70           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      71           0 :     return rv;
      72             :   }
      73             : 
      74           0 :   rv = aHashService->Update(
      75           0 :     reinterpret_cast<const uint8_t*>(aIn.BeginReading()),aIn.Length());
      76           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      77           0 :     return rv;
      78             :   }
      79             : 
      80           0 :   nsAutoCString fullHash;
      81             :   // Passing false below means we will get a binary result rather than a
      82             :   // base64-encoded string.
      83           0 :   rv = aHashService->Finish(false, fullHash);
      84           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      85           0 :     return rv;
      86             :   }
      87             : 
      88           0 :   aOut.Assign(fullHash);
      89           0 :   return rv;
      90             : }
      91             : 
      92             : static nsresult
      93           0 : AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
      94             :                    /* out */ nsACString& aJsonOut)
      95             : {
      96           0 :   MOZ_ASSERT(NS_IsMainThread());
      97             : 
      98           0 :   nsString challengeBase64;
      99           0 :   nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
     100           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     101           0 :     return NS_ERROR_FAILURE;
     102             :   }
     103             : 
     104           0 :   CollectedClientData clientDataObject;
     105           0 :   clientDataObject.mChallenge.Assign(challengeBase64);
     106           0 :   clientDataObject.mOrigin.Assign(aOrigin);
     107           0 :   clientDataObject.mHashAlg.Assign(NS_LITERAL_STRING("S256"));
     108             : 
     109           0 :   nsAutoString temp;
     110           0 :   if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
     111           0 :     return NS_ERROR_FAILURE;
     112             :   }
     113             : 
     114           0 :   aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
     115           0 :   return NS_OK;
     116             : }
     117             : 
     118             : nsresult
     119           0 : GetOrigin(nsPIDOMWindowInner* aParent,
     120             :           /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost)
     121             : {
     122           0 :   nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
     123           0 :   MOZ_ASSERT(doc);
     124             : 
     125           0 :   nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
     126           0 :   nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
     127           0 :   if (NS_WARN_IF(NS_FAILED(rv)) ||
     128           0 :       NS_WARN_IF(aOrigin.IsEmpty())) {
     129           0 :     return NS_ERROR_FAILURE;
     130             :   }
     131             : 
     132           0 :   if (aOrigin.EqualsLiteral("null")) {
     133             :     // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
     134             :     // DOMException whose name is "NotAllowedError", and terminate this
     135             :     // algorithm
     136           0 :     MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
     137           0 :     return NS_ERROR_DOM_NOT_ALLOWED_ERR;
     138             :   }
     139             : 
     140           0 :   nsCOMPtr<nsIURI> originUri;
     141           0 :   if (NS_FAILED(principal->GetURI(getter_AddRefs(originUri)))) {
     142           0 :     return NS_ERROR_FAILURE;
     143             :   }
     144           0 :   if (NS_FAILED(originUri->GetAsciiHost(aHost))) {
     145           0 :     return NS_ERROR_FAILURE;
     146             :   }
     147             : 
     148           0 :   return NS_OK;
     149             : }
     150             : 
     151             : nsresult
     152           0 : RelaxSameOrigin(nsPIDOMWindowInner* aParent,
     153             :                 const nsAString& aInputRpId,
     154             :                 /* out */ nsACString& aRelaxedRpId)
     155             : {
     156           0 :   MOZ_ASSERT(aParent);
     157           0 :   nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
     158           0 :   MOZ_ASSERT(doc);
     159           0 :   nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
     160           0 :   nsCOMPtr<nsIURI> uri;
     161           0 :   if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) {
     162           0 :     return NS_ERROR_FAILURE;
     163             :   }
     164           0 :   nsAutoCString originHost;
     165           0 :   if (NS_FAILED(uri->GetAsciiHost(originHost))) {
     166           0 :     return NS_ERROR_FAILURE;
     167             :   }
     168           0 :   nsCOMPtr<nsIDocument> document = aParent->GetDoc();
     169           0 :   if (!document || !document->IsHTMLDocument()) {
     170           0 :     return NS_ERROR_FAILURE;
     171             :   }
     172           0 :   nsHTMLDocument* html = document->AsHTMLDocument();
     173           0 :   if (NS_WARN_IF(!html)) {
     174           0 :     return NS_ERROR_FAILURE;
     175             :   }
     176             : 
     177           0 :   if (!html->IsRegistrableDomainSuffixOfOrEqualTo(aInputRpId, originHost)) {
     178           0 :     return NS_ERROR_DOM_SECURITY_ERR;
     179             :   }
     180             : 
     181           0 :   aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
     182           0 :   return NS_OK;
     183             : }
     184             : 
     185             : /***********************************************************************
     186             :  * WebAuthnManager Implementation
     187             :  **********************************************************************/
     188             : 
     189           0 : WebAuthnManager::WebAuthnManager()
     190             : {
     191           0 :   MOZ_ASSERT(NS_IsMainThread());
     192           0 : }
     193             : 
     194             : void
     195           0 : WebAuthnManager::MaybeClearTransaction()
     196             : {
     197           0 :   mClientData.reset();
     198           0 :   mInfo.reset();
     199           0 :   mTransactionPromise = nullptr;
     200           0 :   if (mChild) {
     201           0 :     RefPtr<WebAuthnTransactionChild> c;
     202           0 :     mChild.swap(c);
     203           0 :     c->Send__delete__(c);
     204             :   }
     205           0 : }
     206             : 
     207           0 : WebAuthnManager::~WebAuthnManager()
     208             : {
     209           0 :   MaybeClearTransaction();
     210           0 : }
     211             : 
     212             : already_AddRefed<MozPromise<nsresult, nsresult, false>>
     213           0 : WebAuthnManager::GetOrCreateBackgroundActor()
     214             : {
     215           0 :   bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
     216           0 :   if (NS_WARN_IF(!ok)) {
     217           0 :     ActorFailed();
     218             :   }
     219           0 :   return mPBackgroundCreationPromise.Ensure(__func__);
     220             : }
     221             : 
     222             : //static
     223             : WebAuthnManager*
     224           0 : WebAuthnManager::GetOrCreate()
     225             : {
     226           0 :   MOZ_ASSERT(NS_IsMainThread());
     227           0 :   if (gWebAuthnManager) {
     228           0 :     return gWebAuthnManager;
     229             :   }
     230             : 
     231           0 :   gWebAuthnManager = new WebAuthnManager();
     232           0 :   ClearOnShutdown(&gWebAuthnManager);
     233           0 :   return gWebAuthnManager;
     234             : }
     235             : 
     236             : //static
     237             : WebAuthnManager*
     238           0 : WebAuthnManager::Get()
     239             : {
     240           0 :   MOZ_ASSERT(NS_IsMainThread());
     241           0 :   return gWebAuthnManager;
     242             : }
     243             : 
     244             : already_AddRefed<Promise>
     245           0 : WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
     246             :                                 const MakeCredentialOptions& aOptions)
     247             : {
     248           0 :   MOZ_ASSERT(aParent);
     249             : 
     250           0 :   MaybeClearTransaction();
     251             : 
     252           0 :   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
     253             : 
     254           0 :   ErrorResult rv;
     255           0 :   RefPtr<Promise> promise = Promise::Create(global, rv);
     256           0 :   if(rv.Failed()) {
     257           0 :     return nullptr;
     258             :   }
     259             : 
     260           0 :   nsString origin;
     261           0 :   nsCString rpId;
     262           0 :   rv = GetOrigin(aParent, origin, rpId);
     263           0 :   if (NS_WARN_IF(rv.Failed())) {
     264           0 :     promise->MaybeReject(rv);
     265           0 :     return promise.forget();
     266             :   }
     267             : 
     268             :   // If timeoutSeconds was specified, check if its value lies within a
     269             :   // reasonable range as defined by the platform and if not, correct it to the
     270             :   // closest value lying within that range.
     271             : 
     272           0 :   double adjustedTimeout = 30.0;
     273           0 :   if (aOptions.mTimeout.WasPassed()) {
     274           0 :     adjustedTimeout = aOptions.mTimeout.Value();
     275           0 :     adjustedTimeout = std::max(15.0, adjustedTimeout);
     276           0 :     adjustedTimeout = std::min(120.0, adjustedTimeout);
     277             :   }
     278             : 
     279           0 :   if (aOptions.mRp.mId.WasPassed()) {
     280             :     // If rpId is specified, then invoke the procedure used for relaxing the
     281             :     // same-origin restriction by setting the document.domain attribute, using
     282             :     // rpId as the given value but without changing the current document’s
     283             :     // domain. If no errors are thrown, set rpId to the value of host as
     284             :     // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
     285             :     // Otherwise, reject promise with a DOMException whose name is
     286             :     // "SecurityError", and terminate this algorithm.
     287             : 
     288           0 :     if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRp.mId.Value(), rpId))) {
     289           0 :       promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     290           0 :       return promise.forget();
     291             :     }
     292             :   }
     293             : 
     294           0 :   CryptoBuffer rpIdHash;
     295           0 :   if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
     296           0 :     promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
     297           0 :     return promise.forget();
     298             :   }
     299             : 
     300             :   nsresult srv;
     301             :   nsCOMPtr<nsICryptoHash> hashService =
     302           0 :     do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
     303           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     304           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     305           0 :     return promise.forget();
     306             :   }
     307             : 
     308           0 :   srv = HashCString(hashService, rpId, rpIdHash);
     309           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     310           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     311           0 :     return promise.forget();
     312             :   }
     313             : 
     314             :   // Process each element of cryptoParameters using the following steps, to
     315             :   // produce a new sequence normalizedParameters.
     316           0 :   nsTArray<PublicKeyCredentialParameters> normalizedParams;
     317           0 :   for (size_t a = 0; a < aOptions.mParameters.Length(); ++a) {
     318             :     // Let current be the currently selected element of
     319             :     // cryptoParameters.
     320             : 
     321             :     // If current.type does not contain a PublicKeyCredentialType
     322             :     // supported by this implementation, then stop processing current and move
     323             :     // on to the next element in cryptoParameters.
     324           0 :     if (aOptions.mParameters[a].mType != PublicKeyCredentialType::Public_key) {
     325           0 :       continue;
     326             :     }
     327             : 
     328             :     // Let normalizedAlgorithm be the result of normalizing an algorithm using
     329             :     // the procedure defined in [WebCryptoAPI], with alg set to
     330             :     // current.algorithm and op set to 'generateKey'. If an error occurs during
     331             :     // this procedure, then stop processing current and move on to the next
     332             :     // element in cryptoParameters.
     333             : 
     334           0 :     nsString algName;
     335           0 :     if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlgorithm,
     336             :                                    algName))) {
     337           0 :       continue;
     338             :     }
     339             : 
     340             :     // Add a new object of type PublicKeyCredentialParameters to
     341             :     // normalizedParameters, with type set to current.type and algorithm set to
     342             :     // normalizedAlgorithm.
     343           0 :     PublicKeyCredentialParameters normalizedObj;
     344           0 :     normalizedObj.mType = aOptions.mParameters[a].mType;
     345           0 :     normalizedObj.mAlgorithm.SetAsString().Assign(algName);
     346             : 
     347           0 :     if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
     348           0 :       promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
     349           0 :       return promise.forget();
     350             :     }
     351             :   }
     352             : 
     353             :   // If normalizedAlgorithm is empty and cryptoParameters was not empty, cancel
     354             :   // the timer started in step 2, reject promise with a DOMException whose name
     355             :   // is "NotSupportedError", and terminate this algorithm.
     356           0 :   if (normalizedParams.IsEmpty() && !aOptions.mParameters.IsEmpty()) {
     357           0 :     promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     358           0 :     return promise.forget();
     359             :   }
     360             : 
     361             :   // TODO: The following check should not be here. This is checking for
     362             :   // parameters specific to the soft key, and should be put in the soft key
     363             :   // manager in the parent process. Still need to serialize
     364             :   // PublicKeyCredentialParameters first.
     365             : 
     366             :   // Check if at least one of the specified combinations of
     367             :   // PublicKeyCredentialParameters and cryptographic parameters is supported. If
     368             :   // not, return an error code equivalent to NotSupportedError and terminate the
     369             :   // operation.
     370             : 
     371           0 :   bool isValidCombination = false;
     372             : 
     373           0 :   for (size_t a = 0; a < normalizedParams.Length(); ++a) {
     374           0 :     if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key &&
     375           0 :         normalizedParams[a].mAlgorithm.IsString() &&
     376           0 :         normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
     377             :           WEBCRYPTO_NAMED_CURVE_P256)) {
     378           0 :       isValidCombination = true;
     379           0 :       break;
     380             :     }
     381             :   }
     382           0 :   if (!isValidCombination) {
     383           0 :     promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     384           0 :     return promise.forget();
     385             :   }
     386             : 
     387             :   // If excludeList is undefined, set it to the empty list.
     388             :   //
     389             :   // If extensions was specified, process any extensions supported by this
     390             :   // client platform, to produce the extension data that needs to be sent to the
     391             :   // authenticator. If an error is encountered while processing an extension,
     392             :   // skip that extension and do not produce any extension data for it. Call the
     393             :   // result of this processing clientExtensions.
     394             :   //
     395             :   // Currently no extensions are supported
     396             :   //
     397             :   // Use attestationChallenge, callerOrigin and rpId, along with the token
     398             :   // binding key associated with callerOrigin (if any), to create a ClientData
     399             :   // structure representing this request. Choose a hash algorithm for hashAlg
     400             :   // and compute the clientDataJSON and clientDataHash.
     401             : 
     402           0 :   CryptoBuffer challenge;
     403           0 :   if (!challenge.Assign(aOptions.mChallenge)) {
     404           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     405           0 :     return promise.forget();
     406             :   }
     407             : 
     408           0 :   nsAutoCString clientDataJSON;
     409           0 :   srv = AssembleClientData(origin, challenge, clientDataJSON);
     410           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     411           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     412           0 :     return promise.forget();
     413             :   }
     414             : 
     415           0 :   CryptoBuffer clientDataHash;
     416           0 :   if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
     417           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     418           0 :     return promise.forget();
     419             :   }
     420             : 
     421           0 :   srv = HashCString(hashService, clientDataJSON, clientDataHash);
     422           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     423           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     424           0 :     return promise.forget();
     425             :   }
     426             : 
     427           0 :   nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
     428           0 :   if (aOptions.mExcludeList.WasPassed()) {
     429           0 :     for (const auto& s: aOptions.mExcludeList.Value()) {
     430           0 :       WebAuthnScopedCredentialDescriptor c;
     431           0 :       CryptoBuffer cb;
     432           0 :       cb.Assign(s.mId);
     433           0 :       c.id() = cb;
     434           0 :       excludeList.AppendElement(c);
     435             :     }
     436             :   }
     437             : 
     438             :   // TODO: Add extension list building
     439           0 :   nsTArray<WebAuthnExtension> extensions;
     440             : 
     441             :   WebAuthnTransactionInfo info(rpIdHash,
     442             :                                clientDataHash,
     443             :                                adjustedTimeout,
     444             :                                excludeList,
     445           0 :                                extensions);
     446           0 :   RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
     447             :   p->Then(GetMainThreadSerialEventTarget(), __func__,
     448           0 :           []() {
     449           0 :             WebAuthnManager* mgr = WebAuthnManager::Get();
     450           0 :             if (!mgr) {
     451           0 :               return;
     452             :             }
     453           0 :             mgr->StartRegister();
     454             :           },
     455           0 :           []() {
     456             :             // This case can't actually happen, we'll have crashed if the child
     457             :             // failed to create.
     458           0 :           });
     459           0 :   mTransactionPromise = promise;
     460           0 :   mClientData = Some(clientDataJSON);
     461           0 :   mCurrentParent = aParent;
     462           0 :   mInfo = Some(info);
     463           0 :   return promise.forget();
     464             : }
     465             : 
     466             : void
     467           0 : WebAuthnManager::StartRegister() {
     468           0 :   if (mChild) {
     469           0 :     mChild->SendRequestRegister(mInfo.ref());
     470             :   }
     471           0 : }
     472             : 
     473             : void
     474           0 : WebAuthnManager::StartSign() {
     475           0 :   if (mChild) {
     476           0 :     mChild->SendRequestSign(mInfo.ref());
     477             :   }
     478           0 : }
     479             : 
     480             : already_AddRefed<Promise>
     481           0 : WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
     482             :                               const PublicKeyCredentialRequestOptions& aOptions)
     483             : {
     484           0 :   MOZ_ASSERT(aParent);
     485             : 
     486           0 :   MaybeClearTransaction();
     487             : 
     488           0 :   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
     489             : 
     490           0 :   ErrorResult rv;
     491           0 :   RefPtr<Promise> promise = Promise::Create(global, rv);
     492           0 :   if(rv.Failed()) {
     493           0 :     return nullptr;
     494             :   }
     495             : 
     496           0 :   nsString origin;
     497           0 :   nsCString rpId;
     498           0 :   rv = GetOrigin(aParent, origin, rpId);
     499           0 :   if (NS_WARN_IF(rv.Failed())) {
     500           0 :     promise->MaybeReject(rv);
     501           0 :     return promise.forget();
     502             :   }
     503             : 
     504             :   // If timeoutSeconds was specified, check if its value lies within a
     505             :   // reasonable range as defined by the platform and if not, correct it to the
     506             :   // closest value lying within that range.
     507             : 
     508           0 :   uint32_t adjustedTimeout = 30000;
     509           0 :   if (aOptions.mTimeout.WasPassed()) {
     510           0 :     adjustedTimeout = aOptions.mTimeout.Value();
     511           0 :     adjustedTimeout = std::max(15000u, adjustedTimeout);
     512           0 :     adjustedTimeout = std::min(120000u, adjustedTimeout);
     513             :   }
     514             : 
     515           0 :   if (aOptions.mRpId.WasPassed()) {
     516             :     // If rpId is specified, then invoke the procedure used for relaxing the
     517             :     // same-origin restriction by setting the document.domain attribute, using
     518             :     // rpId as the given value but without changing the current document’s
     519             :     // domain. If no errors are thrown, set rpId to the value of host as
     520             :     // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
     521             :     // Otherwise, reject promise with a DOMException whose name is
     522             :     // "SecurityError", and terminate this algorithm.
     523             : 
     524           0 :     if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRpId.Value(), rpId))) {
     525           0 :       promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     526           0 :       return promise.forget();
     527             :     }
     528             :   }
     529             : 
     530           0 :   CryptoBuffer rpIdHash;
     531           0 :   if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
     532           0 :     promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
     533           0 :     return promise.forget();
     534             :   }
     535             : 
     536             :   nsresult srv;
     537             :   nsCOMPtr<nsICryptoHash> hashService =
     538           0 :     do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
     539           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     540           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     541           0 :     return promise.forget();
     542             :   }
     543             : 
     544           0 :   srv = HashCString(hashService, rpId, rpIdHash);
     545           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     546           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     547           0 :     return promise.forget();
     548             :   }
     549             : 
     550             :   // Use assertionChallenge, callerOrigin and rpId, along with the token binding
     551             :   // key associated with callerOrigin (if any), to create a ClientData structure
     552             :   // representing this request. Choose a hash algorithm for hashAlg and compute
     553             :   // the clientDataJSON and clientDataHash.
     554           0 :   CryptoBuffer challenge;
     555           0 :   if (!challenge.Assign(aOptions.mChallenge)) {
     556           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     557           0 :     return promise.forget();
     558             :   }
     559             : 
     560           0 :   nsAutoCString clientDataJSON;
     561           0 :   srv = AssembleClientData(origin, challenge, clientDataJSON);
     562           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     563           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     564           0 :     return promise.forget();
     565             :   }
     566             : 
     567           0 :   CryptoBuffer clientDataHash;
     568           0 :   if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
     569           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     570           0 :     return promise.forget();
     571             :   }
     572             : 
     573           0 :   srv = HashCString(hashService, clientDataJSON, clientDataHash);
     574           0 :   if (NS_WARN_IF(NS_FAILED(srv))) {
     575           0 :     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     576           0 :     return promise.forget();
     577             :   }
     578             : 
     579             :   // Note: we only support U2F-style authentication for now, so we effectively
     580             :   // require an AllowList.
     581           0 :   if (aOptions.mAllowList.Length() < 1) {
     582           0 :     promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     583           0 :     return promise.forget();
     584             :   }
     585             : 
     586           0 :   nsTArray<WebAuthnScopedCredentialDescriptor> allowList;
     587           0 :   for (const auto& s: aOptions.mAllowList) {
     588           0 :     WebAuthnScopedCredentialDescriptor c;
     589           0 :     CryptoBuffer cb;
     590           0 :     cb.Assign(s.mId);
     591           0 :     c.id() = cb;
     592           0 :     allowList.AppendElement(c);
     593             :   }
     594             : 
     595             :   // TODO: Add extension list building
     596             :   // If extensions was specified, process any extensions supported by this
     597             :   // client platform, to produce the extension data that needs to be sent to the
     598             :   // authenticator. If an error is encountered while processing an extension,
     599             :   // skip that extension and do not produce any extension data for it. Call the
     600             :   // result of this processing clientExtensions.
     601           0 :   nsTArray<WebAuthnExtension> extensions;
     602             : 
     603             :   WebAuthnTransactionInfo info(rpIdHash,
     604             :                                clientDataHash,
     605             :                                adjustedTimeout,
     606             :                                allowList,
     607           0 :                                extensions);
     608           0 :   RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
     609             :   p->Then(GetMainThreadSerialEventTarget(), __func__,
     610           0 :           []() {
     611           0 :             WebAuthnManager* mgr = WebAuthnManager::Get();
     612           0 :             if (!mgr) {
     613           0 :               return;
     614             :             }
     615           0 :             mgr->StartSign();
     616             :           },
     617           0 :           []() {
     618             :             // This case can't actually happen, we'll have crashed if the child
     619             :             // failed to create.
     620           0 :           });
     621             : 
     622             :   // Only store off the promise if we've succeeded in sending the IPC event.
     623           0 :   mTransactionPromise = promise;
     624           0 :   mClientData = Some(clientDataJSON);
     625           0 :   mCurrentParent = aParent;
     626           0 :   mInfo = Some(info);
     627           0 :   return promise.forget();
     628             : }
     629             : 
     630             : void
     631           0 : WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
     632             : {
     633           0 :   MOZ_ASSERT(mTransactionPromise);
     634           0 :   MOZ_ASSERT(mInfo.isSome());
     635             : 
     636           0 :   CryptoBuffer regData;
     637           0 :   if (NS_WARN_IF(!regData.Assign(aRegBuffer.Elements(), aRegBuffer.Length()))) {
     638           0 :     mTransactionPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
     639           0 :     return;
     640             :   }
     641             : 
     642             :   // Decompose the U2F registration packet
     643           0 :   CryptoBuffer pubKeyBuf;
     644           0 :   CryptoBuffer keyHandleBuf;
     645           0 :   CryptoBuffer attestationCertBuf;
     646           0 :   CryptoBuffer signatureBuf;
     647             : 
     648           0 :   nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
     649           0 :                                                  attestationCertBuf, signatureBuf);
     650           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     651           0 :     Cancel(rv);
     652           0 :     return;
     653             :   }
     654             : 
     655           0 :   CryptoBuffer clientDataBuf;
     656           0 :   if (!clientDataBuf.Assign(mClientData.ref())) {
     657           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     658           0 :     return;
     659             :   }
     660             : 
     661           0 :   CryptoBuffer rpIdHashBuf;
     662           0 :   if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) {
     663           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     664           0 :     return;
     665             :   }
     666             : 
     667           0 :   CryptoBuffer authenticatorDataBuf;
     668           0 :   rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, rpIdHashBuf,
     669             :                                     signatureBuf);
     670           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     671           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     672           0 :     return;
     673             :   }
     674             : 
     675             :   // Create a new PublicKeyCredential object and populate its fields with the
     676             :   // values returned from the authenticator as well as the clientDataJSON
     677             :   // computed earlier.
     678             :   RefPtr<AuthenticatorAttestationResponse> attestation =
     679           0 :       new AuthenticatorAttestationResponse(mCurrentParent);
     680           0 :   attestation->SetClientDataJSON(clientDataBuf);
     681           0 :   attestation->SetAttestationObject(regData);
     682             : 
     683           0 :   RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mCurrentParent);
     684           0 :   credential->SetRawId(keyHandleBuf);
     685           0 :   credential->SetResponse(attestation);
     686             : 
     687           0 :   mTransactionPromise->MaybeResolve(credential);
     688           0 :   MaybeClearTransaction();
     689             : }
     690             : 
     691             : void
     692           0 : WebAuthnManager::FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
     693             :                                     nsTArray<uint8_t>& aSigBuffer)
     694             : {
     695           0 :   MOZ_ASSERT(mTransactionPromise);
     696           0 :   MOZ_ASSERT(mInfo.isSome());
     697             : 
     698           0 :   CryptoBuffer signatureData;
     699           0 :   if (NS_WARN_IF(!signatureData.Assign(aSigBuffer.Elements(), aSigBuffer.Length()))) {
     700           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     701           0 :     return;
     702             :   }
     703             : 
     704           0 :   CryptoBuffer clientDataBuf;
     705           0 :   if (!clientDataBuf.Assign(mClientData.ref())) {
     706           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     707           0 :     return;
     708             :   }
     709             : 
     710           0 :   CryptoBuffer rpIdHashBuf;
     711           0 :   if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) {
     712           0 :     Cancel(NS_ERROR_OUT_OF_MEMORY);
     713           0 :     return;
     714             :   }
     715             : 
     716           0 :   CryptoBuffer authenticatorDataBuf;
     717           0 :   nsresult rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, rpIdHashBuf,
     718           0 :                                              signatureData);
     719           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     720           0 :     Cancel(rv);
     721           0 :     return;
     722             :   }
     723             : 
     724           0 :   CryptoBuffer credentialBuf;
     725           0 :   if (!credentialBuf.Assign(aCredentialId)) {
     726           0 :     Cancel(rv);
     727           0 :     return;
     728             :   }
     729             : 
     730             :   // If any authenticator returns success:
     731             : 
     732             :   // Create a new PublicKeyCredential object named value and populate its fields
     733             :   // with the values returned from the authenticator as well as the
     734             :   // clientDataJSON computed earlier.
     735             :   RefPtr<AuthenticatorAssertionResponse> assertion =
     736           0 :     new AuthenticatorAssertionResponse(mCurrentParent);
     737           0 :   assertion->SetClientDataJSON(clientDataBuf);
     738           0 :   assertion->SetAuthenticatorData(authenticatorDataBuf);
     739           0 :   assertion->SetSignature(signatureData);
     740             : 
     741             :   RefPtr<PublicKeyCredential> credential =
     742           0 :     new PublicKeyCredential(mCurrentParent);
     743           0 :   credential->SetRawId(credentialBuf);
     744           0 :   credential->SetResponse(assertion);
     745             : 
     746           0 :   mTransactionPromise->MaybeResolve(credential);
     747           0 :   MaybeClearTransaction();
     748             : }
     749             : 
     750             : void
     751           0 : WebAuthnManager::Cancel(const nsresult& aError)
     752             : {
     753           0 :   if (mTransactionPromise) {
     754           0 :     mTransactionPromise->MaybeReject(aError);
     755             :   }
     756           0 :   MaybeClearTransaction();
     757           0 : }
     758             : 
     759             : void
     760           0 : WebAuthnManager::ActorCreated(PBackgroundChild* aActor)
     761             : {
     762           0 :   MOZ_ASSERT(aActor);
     763             : 
     764           0 :   RefPtr<WebAuthnTransactionChild> mgr(new WebAuthnTransactionChild());
     765             :   PWebAuthnTransactionChild* constructedMgr =
     766           0 :     aActor->SendPWebAuthnTransactionConstructor(mgr);
     767             : 
     768           0 :   if (NS_WARN_IF(!constructedMgr)) {
     769           0 :     ActorFailed();
     770           0 :     return;
     771             :   }
     772           0 :   MOZ_ASSERT(constructedMgr == mgr);
     773           0 :   mChild = mgr.forget();
     774           0 :   mPBackgroundCreationPromise.Resolve(NS_OK, __func__);
     775             : }
     776             : 
     777             : void
     778           0 : WebAuthnManager::ActorDestroyed()
     779             : {
     780           0 :   mChild = nullptr;
     781           0 : }
     782             : 
     783             : void
     784           0 : WebAuthnManager::ActorFailed()
     785             : {
     786           0 :   MOZ_CRASH("We shouldn't be here!");
     787             : }
     788             : 
     789             : }
     790             : }

Generated by: LCOV version 1.13