tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebAuthnService.cpp (13799B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "WebAuthnService.h"
      6 
      7 #include "WebAuthnEnumStrings.h"
      8 #include "WebAuthnTransportIdentifiers.h"
      9 #include "mozilla/Services.h"
     10 #include "mozilla/StaticPrefs_security.h"
     11 #include "nsIObserverService.h"
     12 #include "nsTextFormatter.h"
     13 #include "nsThreadUtils.h"
     14 
     15 namespace mozilla::dom {
     16 
     17 already_AddRefed<nsIWebAuthnService> NewWebAuthnService() {
     18  nsCOMPtr<nsIWebAuthnService> webauthnService(new WebAuthnService());
     19  return webauthnService.forget();
     20 }
     21 
     22 NS_IMPL_ISUPPORTS(WebAuthnService, nsIWebAuthnService)
     23 
     24 void WebAuthnService::ShowAttestationConsentPrompt(
     25    const nsString& aOrigin, uint64_t aTransactionId,
     26    uint64_t aBrowsingContextId) {
     27  RefPtr<WebAuthnService> self = this;
     28 #ifdef MOZ_WIDGET_ANDROID
     29  // We don't have a way to prompt the user for consent on Android, so just
     30  // assume consent not granted.
     31  nsCOMPtr<nsIRunnable> runnable(
     32      NS_NewRunnableFunction(__func__, [self, aTransactionId]() {
     33        self->SetHasAttestationConsent(
     34            aTransactionId,
     35            StaticPrefs::security_webauthn_always_allow_direct_attestation());
     36      }));
     37 #else
     38  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
     39      __func__, [self, aOrigin, aTransactionId, aBrowsingContextId]() {
     40        if (StaticPrefs::security_webauthn_always_allow_direct_attestation()) {
     41          self->SetHasAttestationConsent(aTransactionId, true);
     42          return;
     43        }
     44        nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     45        if (!os) {
     46          return;
     47        }
     48        const nsLiteralString jsonFmt =
     49            u"{\"prompt\": {\"type\":\"attestation-consent\"},"_ns
     50            u"\"origin\": \"%S\","_ns
     51            u"\"tid\": %llu, \"browsingContextId\": %llu}"_ns;
     52        nsString json;
     53        nsTextFormatter::ssprintf(json, jsonFmt.get(), aOrigin.get(),
     54                                  aTransactionId, aBrowsingContextId);
     55        MOZ_ALWAYS_SUCCEEDS(
     56            os->NotifyObservers(nullptr, "webauthn-prompt", json.get()));
     57      }));
     58 #endif
     59  NS_DispatchToMainThread(runnable.forget());
     60 }
     61 
     62 NS_IMETHODIMP
     63 WebAuthnService::MakeCredential(uint64_t aTransactionId,
     64                                uint64_t aBrowsingContextId,
     65                                nsIWebAuthnRegisterArgs* aArgs,
     66                                nsIWebAuthnRegisterPromise* aPromise) {
     67  MOZ_ASSERT(aArgs);
     68  MOZ_ASSERT(aPromise);
     69 
     70  auto guard = mTransactionState.Lock();
     71  ResetLocked(guard);
     72  *guard = Some(TransactionState{.service = DefaultService(),
     73                                 .transactionId = aTransactionId,
     74                                 .parentRegisterPromise = Some(aPromise)});
     75 
     76  // We may need to show an attestation consent prompt before we return a
     77  // credential to WebAuthnTransactionParent, so we insert a new promise that
     78  // chains to `aPromise` here.
     79 
     80  nsString attestation;
     81  (void)aArgs->GetAttestationConveyancePreference(attestation);
     82  bool attestationRequested = !attestation.EqualsLiteral(
     83      MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE);
     84 
     85  nsString origin;
     86  (void)aArgs->GetOrigin(origin);
     87 
     88  RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
     89      new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
     90 
     91  RefPtr<WebAuthnService> self = this;
     92  RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
     93  promise
     94      ->Then(GetCurrentSerialEventTarget(), __func__,
     95             [self, origin, aTransactionId, aBrowsingContextId,
     96              attestationRequested](
     97                 const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) {
     98               auto guard = self->mTransactionState.Lock();
     99               if (guard->isNothing()) {
    100                 return;
    101               }
    102               MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
    103               MOZ_ASSERT(guard->ref().registerResult.isNothing());
    104               MOZ_ASSERT(guard->ref().childRegisterRequest.Exists());
    105 
    106               guard->ref().childRegisterRequest.Complete();
    107 
    108               if (aValue.IsReject()) {
    109                 guard->ref().parentRegisterPromise.ref()->Reject(
    110                     aValue.RejectValue());
    111                 guard->reset();
    112                 return;
    113               }
    114 
    115               nsIWebAuthnRegisterResult* result = aValue.ResolveValue();
    116               // We can return whatever result we have if the authenticator
    117               // handled attestation consent for us.
    118               bool attestationConsentPromptShown = false;
    119               (void)result->GetAttestationConsentPromptShown(
    120                   &attestationConsentPromptShown);
    121               if (attestationConsentPromptShown) {
    122                 guard->ref().parentRegisterPromise.ref()->Resolve(result);
    123                 guard->reset();
    124                 return;
    125               }
    126               // If the RP requested attestation and the response contains
    127               // identifying information, then we need to show a consent
    128               // prompt.
    129               bool resultIsIdentifying = true;
    130               (void)result->HasIdentifyingAttestation(&resultIsIdentifying);
    131               if (attestationRequested && resultIsIdentifying) {
    132                 guard->ref().registerResult = Some(result);
    133                 self->ShowAttestationConsentPrompt(origin, aTransactionId,
    134                                                    aBrowsingContextId);
    135                 return;
    136               }
    137               // In all other cases we strip out identifying information.
    138               result->Anonymize();
    139               guard->ref().parentRegisterPromise.ref()->Resolve(result);
    140               guard->reset();
    141             })
    142      ->Track(guard->ref().childRegisterRequest);
    143 
    144  nsresult rv = guard->ref().service->MakeCredential(
    145      aTransactionId, aBrowsingContextId, aArgs, promiseHolder);
    146  if (NS_FAILED(rv)) {
    147    promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
    148  }
    149  return NS_OK;
    150 }
    151 
    152 NS_IMETHODIMP
    153 WebAuthnService::GetAssertion(uint64_t aTransactionId,
    154                              uint64_t aBrowsingContextId,
    155                              nsIWebAuthnSignArgs* aArgs,
    156                              nsIWebAuthnSignPromise* aPromise) {
    157  MOZ_ASSERT(aArgs);
    158  MOZ_ASSERT(aPromise);
    159 
    160  auto guard = mTransactionState.Lock();
    161  ResetLocked(guard);
    162  *guard = Some(TransactionState{.service = DefaultService(),
    163                                 .transactionId = aTransactionId});
    164  nsresult rv;
    165 
    166 #if defined(XP_MACOSX)
    167  if (__builtin_available(macos 14.5, *)) {
    168    // This branch is intentionally empty; using `!__builtin_available(...)`
    169    // results in a compiler warning.
    170  } else {
    171    // On macOS < 14.5 the security key API doesn't handle the AppID extension.
    172    // So we'll use authenticator-rs if it's likely that the request requires
    173    // AppID. We consider it likely if 1) the AppID extension is present, 2)
    174    // the allow list is non-empty, and 3) none of the allowed credentials use
    175    // the "internal" or "hybrid" transport.
    176    nsString appId;
    177    rv = aArgs->GetAppId(appId);
    178    if (rv == NS_OK) {  // AppID is set
    179      uint8_t transportSet = 0;
    180      nsTArray<uint8_t> allowListTransports;
    181      (void)aArgs->GetAllowListTransports(allowListTransports);
    182      for (const uint8_t& transport : allowListTransports) {
    183        transportSet |= transport;
    184      }
    185      uint8_t passkeyTransportMask =
    186          MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL |
    187          MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID;
    188      if (allowListTransports.Length() > 0 &&
    189          (transportSet & passkeyTransportMask) == 0) {
    190        guard->ref().service = AuthrsService();
    191      }
    192    }
    193  }
    194 #endif
    195 
    196  rv = guard->ref().service->GetAssertion(aTransactionId, aBrowsingContextId,
    197                                          aArgs, aPromise);
    198  if (NS_FAILED(rv)) {
    199    return rv;
    200  }
    201 
    202  // If this is a conditionally mediated request, notify observers that there
    203  // is a pending transaction. This is mainly useful in tests.
    204  bool conditionallyMediated;
    205  (void)aArgs->GetConditionallyMediated(&conditionallyMediated);
    206  if (conditionallyMediated) {
    207    nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(__func__, []() {
    208      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    209      if (os) {
    210        os->NotifyObservers(nullptr, "webauthn:conditional-get-pending",
    211                            nullptr);
    212      }
    213    }));
    214    NS_DispatchToMainThread(runnable.forget());
    215  }
    216 
    217  return NS_OK;
    218 }
    219 
    220 NS_IMETHODIMP
    221 WebAuthnService::GetIsUVPAA(bool* aAvailable) {
    222  return DefaultService()->GetIsUVPAA(aAvailable);
    223 }
    224 
    225 NS_IMETHODIMP
    226 WebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
    227                                          const nsAString& aOrigin,
    228                                          uint64_t* aRv) {
    229  return SelectedService()->HasPendingConditionalGet(aBrowsingContextId,
    230                                                     aOrigin, aRv);
    231 }
    232 
    233 NS_IMETHODIMP
    234 WebAuthnService::GetAutoFillEntries(
    235    uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
    236  return SelectedService()->GetAutoFillEntries(aTransactionId, aRv);
    237 }
    238 
    239 NS_IMETHODIMP
    240 WebAuthnService::SelectAutoFillEntry(uint64_t aTransactionId,
    241                                     const nsTArray<uint8_t>& aCredentialId) {
    242  return SelectedService()->SelectAutoFillEntry(aTransactionId, aCredentialId);
    243 }
    244 
    245 NS_IMETHODIMP
    246 WebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
    247  return SelectedService()->ResumeConditionalGet(aTransactionId);
    248 }
    249 
    250 void WebAuthnService::ResetLocked(
    251    const TransactionStateMutex::AutoLock& aGuard) {
    252  if (aGuard->isSome()) {
    253    aGuard->ref().childRegisterRequest.DisconnectIfExists();
    254    if (aGuard->ref().parentRegisterPromise.isSome()) {
    255      aGuard->ref().parentRegisterPromise.ref()->Reject(NS_ERROR_DOM_ABORT_ERR);
    256    }
    257    aGuard->ref().service->Reset();
    258  }
    259  aGuard->reset();
    260 }
    261 
    262 NS_IMETHODIMP
    263 WebAuthnService::Reset() {
    264  auto guard = mTransactionState.Lock();
    265  ResetLocked(guard);
    266  return NS_OK;
    267 }
    268 
    269 NS_IMETHODIMP
    270 WebAuthnService::Cancel(uint64_t aTransactionId) {
    271  return SelectedService()->Cancel(aTransactionId);
    272 }
    273 
    274 NS_IMETHODIMP
    275 WebAuthnService::PinCallback(uint64_t aTransactionId, const nsACString& aPin) {
    276  return SelectedService()->PinCallback(aTransactionId, aPin);
    277 }
    278 
    279 NS_IMETHODIMP
    280 WebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
    281                                          bool aHasConsent) {
    282  auto guard = this->mTransactionState.Lock();
    283  if (guard->isNothing() || guard->ref().transactionId != aTransactionId) {
    284    // This could happen if the transaction was reset just when the prompt was
    285    // receiving user input.
    286    return NS_OK;
    287  }
    288 
    289  MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
    290  MOZ_ASSERT(guard->ref().registerResult.isSome());
    291  MOZ_ASSERT(!guard->ref().childRegisterRequest.Exists());
    292 
    293  if (!aHasConsent) {
    294    guard->ref().registerResult.ref()->Anonymize();
    295  }
    296  guard->ref().parentRegisterPromise.ref()->Resolve(
    297      guard->ref().registerResult.ref());
    298 
    299  guard->reset();
    300  return NS_OK;
    301 }
    302 
    303 NS_IMETHODIMP
    304 WebAuthnService::SelectionCallback(uint64_t aTransactionId, uint64_t aIndex) {
    305  return SelectedService()->SelectionCallback(aTransactionId, aIndex);
    306 }
    307 
    308 NS_IMETHODIMP
    309 WebAuthnService::AddVirtualAuthenticator(
    310    const nsACString& aProtocol, const nsACString& aTransport,
    311    bool aHasResidentKey, bool aHasUserVerification, bool aIsUserConsenting,
    312    bool aIsUserVerified, nsACString& aRetval) {
    313  return SelectedService()->AddVirtualAuthenticator(
    314      aProtocol, aTransport, aHasResidentKey, aHasUserVerification,
    315      aIsUserConsenting, aIsUserVerified, aRetval);
    316 }
    317 
    318 NS_IMETHODIMP
    319 WebAuthnService::RemoveVirtualAuthenticator(
    320    const nsACString& aAuthenticatorId) {
    321  return SelectedService()->RemoveVirtualAuthenticator(aAuthenticatorId);
    322 }
    323 
    324 NS_IMETHODIMP
    325 WebAuthnService::AddCredential(const nsACString& aAuthenticatorId,
    326                               const nsACString& aCredentialId,
    327                               bool aIsResidentCredential,
    328                               const nsACString& aRpId,
    329                               const nsACString& aPrivateKey,
    330                               const nsACString& aUserHandle,
    331                               uint32_t aSignCount) {
    332  return SelectedService()->AddCredential(aAuthenticatorId, aCredentialId,
    333                                          aIsResidentCredential, aRpId,
    334                                          aPrivateKey, aUserHandle, aSignCount);
    335 }
    336 
    337 NS_IMETHODIMP
    338 WebAuthnService::GetCredentials(
    339    const nsACString& aAuthenticatorId,
    340    nsTArray<RefPtr<nsICredentialParameters>>& aRetval) {
    341  return SelectedService()->GetCredentials(aAuthenticatorId, aRetval);
    342 }
    343 
    344 NS_IMETHODIMP
    345 WebAuthnService::RemoveCredential(const nsACString& aAuthenticatorId,
    346                                  const nsACString& aCredentialId) {
    347  return SelectedService()->RemoveCredential(aAuthenticatorId, aCredentialId);
    348 }
    349 
    350 NS_IMETHODIMP
    351 WebAuthnService::RemoveAllCredentials(const nsACString& aAuthenticatorId) {
    352  return SelectedService()->RemoveAllCredentials(aAuthenticatorId);
    353 }
    354 
    355 NS_IMETHODIMP
    356 WebAuthnService::SetUserVerified(const nsACString& aAuthenticatorId,
    357                                 bool aIsUserVerified) {
    358  return SelectedService()->SetUserVerified(aAuthenticatorId, aIsUserVerified);
    359 }
    360 
    361 NS_IMETHODIMP
    362 WebAuthnService::Listen() { return SelectedService()->Listen(); }
    363 
    364 NS_IMETHODIMP
    365 WebAuthnService::RunCommand(const nsACString& aCmd) {
    366  return SelectedService()->RunCommand(aCmd);
    367 }
    368 
    369 }  // namespace mozilla::dom