tor-browser

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

CredentialsContainer.cpp (10059B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/CredentialsContainer.h"
      8 
      9 #include "mozilla/StaticPrefs_dom.h"
     10 #include "mozilla/StaticPrefs_security.h"
     11 #include "mozilla/dom/Credential.h"
     12 #include "mozilla/dom/FeaturePolicyUtils.h"
     13 #include "mozilla/dom/Promise.h"
     14 #include "mozilla/dom/WebAuthnHandler.h"
     15 #include "mozilla/dom/WebIdentityHandler.h"
     16 #include "mozilla/dom/WindowContext.h"
     17 #include "mozilla/dom/WindowGlobalChild.h"
     18 #include "nsContentUtils.h"
     19 #include "nsIDocShell.h"
     20 
     21 namespace mozilla::dom {
     22 
     23 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CredentialsContainer, mParent,
     24                                      mWebAuthnHandler)
     25 NS_IMPL_CYCLE_COLLECTING_ADDREF(CredentialsContainer)
     26 NS_IMPL_CYCLE_COLLECTING_RELEASE(CredentialsContainer)
     27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CredentialsContainer)
     28  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     29  NS_INTERFACE_MAP_ENTRY(nsISupports)
     30 NS_INTERFACE_MAP_END
     31 
     32 already_AddRefed<Promise> CreatePromise(nsPIDOMWindowInner* aParent,
     33                                        ErrorResult& aRv) {
     34  MOZ_ASSERT(aParent);
     35  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
     36  if (NS_WARN_IF(!global)) {
     37    aRv.Throw(NS_ERROR_FAILURE);
     38    return nullptr;
     39  }
     40  RefPtr<Promise> promise = Promise::Create(global, aRv);
     41  if (NS_WARN_IF(aRv.Failed())) {
     42    return nullptr;
     43  }
     44  return promise.forget();
     45 }
     46 
     47 already_AddRefed<Promise> CreateAndRejectWithNotAllowed(
     48    nsPIDOMWindowInner* aParent, ErrorResult& aRv) {
     49  MOZ_ASSERT(aParent);
     50  RefPtr<Promise> promise = CreatePromise(aParent, aRv);
     51  if (!promise) {
     52    return nullptr;
     53  }
     54  promise->MaybeRejectWithNotAllowedError(
     55      "CredentialContainer request is not allowed."_ns);
     56  return promise.forget();
     57 }
     58 
     59 already_AddRefed<Promise> CreateAndRejectWithNotSupported(
     60    nsPIDOMWindowInner* aParent, ErrorResult& aRv) {
     61  MOZ_ASSERT(aParent);
     62  RefPtr<Promise> promise = CreatePromise(aParent, aRv);
     63  if (!promise) {
     64    return nullptr;
     65  }
     66  promise->MaybeRejectWithNotSupportedError(
     67      "CredentialContainer request is not supported."_ns);
     68  return promise.forget();
     69 }
     70 
     71 static bool IsInActiveTab(nsPIDOMWindowInner* aParent) {
     72  // Returns whether aParent is an inner window somewhere in the active tab.
     73  // The active tab is the selected (i.e. visible) tab in the focused window.
     74  MOZ_ASSERT(aParent);
     75 
     76  RefPtr<Document> doc = aParent->GetExtantDoc();
     77  if (NS_WARN_IF(!doc)) {
     78    return false;
     79  }
     80 
     81  return IsInActiveTab(doc);
     82 }
     83 
     84 static bool ConsumeUserActivation(nsPIDOMWindowInner* aParent) {
     85  // Returns whether aParent has transient activation, and consumes the
     86  // activation.
     87  MOZ_ASSERT(aParent);
     88 
     89  RefPtr<Document> doc = aParent->GetExtantDoc();
     90  if (NS_WARN_IF(!doc)) {
     91    return false;
     92  }
     93 
     94  return doc->ConsumeTransientUserGestureActivation();
     95 }
     96 
     97 // static
     98 bool CredentialsContainer::IsSameOriginWithAncestors(
     99    nsPIDOMWindowInner* aParent) {
    100  // This method returns true if aParent is either not in a frame / iframe, or
    101  // is in a frame or iframe and all ancestors for aParent are the same origin.
    102  // This is useful for Credential Management because we need to prohibit
    103  // iframes, but not break mochitests (which use iframes to embed the tests).
    104  MOZ_ASSERT(aParent);
    105 
    106  WindowGlobalChild* wgc = aParent->GetWindowGlobalChild();
    107 
    108  // If there's no WindowGlobalChild, the inner window has already been
    109  // destroyed, so fail safe and return false.
    110  if (!wgc) {
    111    return false;
    112  }
    113 
    114  // Check that all ancestors are the same origin, repeating until we find a
    115  // null parent
    116  for (WindowContext* parentContext =
    117           wgc->WindowContext()->GetParentWindowContext();
    118       parentContext; parentContext = parentContext->GetParentWindowContext()) {
    119    if (!wgc->IsSameOriginWith(parentContext)) {
    120      // same-origin policy is violated
    121      return false;
    122    }
    123  }
    124 
    125  return true;
    126 }
    127 
    128 CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner* aParent)
    129    : mParent(aParent) {
    130  MOZ_ASSERT(aParent);
    131 }
    132 
    133 CredentialsContainer::~CredentialsContainer() = default;
    134 
    135 void CredentialsContainer::EnsureWebAuthnHandler() {
    136  MOZ_ASSERT(NS_IsMainThread());
    137 
    138  if (!mWebAuthnHandler) {
    139    mWebAuthnHandler = new WebAuthnHandler(mParent);
    140  }
    141 }
    142 
    143 already_AddRefed<WebAuthnHandler> CredentialsContainer::GetWebAuthnHandler() {
    144  MOZ_ASSERT(NS_IsMainThread());
    145 
    146  EnsureWebAuthnHandler();
    147  RefPtr<WebAuthnHandler> ref = mWebAuthnHandler;
    148  return ref.forget();
    149 }
    150 
    151 JSObject* CredentialsContainer::WrapObject(JSContext* aCx,
    152                                           JS::Handle<JSObject*> aGivenProto) {
    153  return CredentialsContainer_Binding::Wrap(aCx, this, aGivenProto);
    154 }
    155 
    156 already_AddRefed<Promise> CredentialsContainer::Get(
    157    const CredentialRequestOptions& aOptions, ErrorResult& aRv) {
    158  uint64_t totalOptions = 0;
    159  if (aOptions.mPublicKey.WasPassed() &&
    160      StaticPrefs::security_webauth_webauthn()) {
    161    totalOptions += 1;
    162  }
    163  if (aOptions.mIdentity.WasPassed() &&
    164      StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
    165    totalOptions += 1;
    166  }
    167  if (totalOptions > 1) {
    168    return CreateAndRejectWithNotSupported(mParent, aRv);
    169  }
    170 
    171  bool conditionallyMediated =
    172      aOptions.mMediation == CredentialMediationRequirement::Conditional;
    173  if (aOptions.mPublicKey.WasPassed() &&
    174      StaticPrefs::security_webauth_webauthn()) {
    175    MOZ_ASSERT(mParent);
    176    if (!FeaturePolicyUtils::IsFeatureAllowed(
    177            mParent->GetExtantDoc(), u"publickey-credentials-get"_ns) ||
    178        !(IsInActiveTab(mParent) || conditionallyMediated)) {
    179      return CreateAndRejectWithNotAllowed(mParent, aRv);
    180    }
    181 
    182    if (conditionallyMediated &&
    183        !StaticPrefs::security_webauthn_enable_conditional_mediation()) {
    184      RefPtr<Promise> promise = CreatePromise(mParent, aRv);
    185      if (!promise) {
    186        return nullptr;
    187      }
    188      promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
    189          "mediation", "conditional", "CredentialMediationRequirement");
    190      return promise.forget();
    191    }
    192 
    193    EnsureWebAuthnHandler();
    194    return mWebAuthnHandler->GetAssertion(aOptions.mPublicKey.Value(),
    195                                          conditionallyMediated,
    196                                          aOptions.mSignal, aRv);
    197  }
    198 
    199  if (aOptions.mIdentity.WasPassed() &&
    200      StaticPrefs::dom_security_credentialmanagement_identity_enabled()) {
    201    RefPtr<Promise> promise = CreatePromise(mParent, aRv);
    202    if (!promise) {
    203      return nullptr;
    204    }
    205 
    206    if (conditionallyMediated) {
    207      promise->MaybeRejectWithTypeError<MSG_INVALID_ENUM_VALUE>(
    208          "mediation", "conditional", "CredentialMediationRequirement");
    209      return promise.forget();
    210    }
    211 
    212    WebIdentityHandler* identityHandler =
    213        mParent->GetOrCreateWebIdentityHandler();
    214    if (!identityHandler) {
    215      promise->MaybeRejectWithOperationError("");
    216      return promise.forget();
    217    }
    218    if (aOptions.mSignal.WasPassed()) {
    219      identityHandler->Follow(&aOptions.mSignal.Value());
    220    }
    221    identityHandler->GetCredential(aOptions, IsSameOriginWithAncestors(mParent),
    222                                   promise);
    223 
    224    return promise.forget();
    225  }
    226 
    227  return CreateAndRejectWithNotSupported(mParent, aRv);
    228 }
    229 
    230 already_AddRefed<Promise> CredentialsContainer::Create(
    231    const CredentialCreationOptions& aOptions, ErrorResult& aRv) {
    232  // Count the types of options provided. Must not be >1.
    233  uint64_t totalOptions = 0;
    234  if (aOptions.mPublicKey.WasPassed() &&
    235      StaticPrefs::security_webauth_webauthn()) {
    236    totalOptions += 1;
    237  }
    238  if (totalOptions > 1) {
    239    return CreateAndRejectWithNotSupported(mParent, aRv);
    240  }
    241 
    242  if (aOptions.mPublicKey.WasPassed() &&
    243      StaticPrefs::security_webauth_webauthn()) {
    244    MOZ_ASSERT(mParent);
    245    // In a cross-origin iframe this request consumes user activation, i.e.
    246    // subsequent requests cannot be made without further user interaction.
    247    // See step 2.2 of https://w3c.github.io/webauthn/#sctn-createCredential
    248    bool hasRequiredActivation =
    249        IsInActiveTab(mParent) &&
    250        (IsSameOriginWithAncestors(mParent) || ConsumeUserActivation(mParent));
    251    if (!FeaturePolicyUtils::IsFeatureAllowed(
    252            mParent->GetExtantDoc(), u"publickey-credentials-create"_ns) ||
    253        !hasRequiredActivation) {
    254      return CreateAndRejectWithNotAllowed(mParent, aRv);
    255    }
    256 
    257    EnsureWebAuthnHandler();
    258    return mWebAuthnHandler->MakeCredential(aOptions.mPublicKey.Value(),
    259                                            aOptions.mSignal, aRv);
    260  }
    261 
    262  return CreateAndRejectWithNotSupported(mParent, aRv);
    263 }
    264 
    265 already_AddRefed<Promise> CredentialsContainer::Store(
    266    const Credential& aCredential, ErrorResult& aRv) {
    267  nsString type;
    268  aCredential.GetType(type);
    269  if (type.EqualsLiteral("public-key") &&
    270      StaticPrefs::security_webauth_webauthn()) {
    271    if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
    272      return CreateAndRejectWithNotAllowed(mParent, aRv);
    273    }
    274 
    275    EnsureWebAuthnHandler();
    276    return mWebAuthnHandler->Store(aCredential, aRv);
    277  }
    278 
    279  return CreateAndRejectWithNotSupported(mParent, aRv);
    280 }
    281 
    282 already_AddRefed<Promise> CredentialsContainer::PreventSilentAccess(
    283    ErrorResult& aRv) {
    284  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
    285  if (NS_WARN_IF(!global)) {
    286    aRv.Throw(NS_ERROR_FAILURE);
    287    return nullptr;
    288  }
    289 
    290  RefPtr<Promise> promise = Promise::Create(global, aRv);
    291  if (NS_WARN_IF(aRv.Failed())) {
    292    return nullptr;
    293  }
    294 
    295  WebIdentityHandler* identityHandler =
    296      mParent->GetOrCreateWebIdentityHandler();
    297  if (!identityHandler) {
    298    promise->MaybeRejectWithOperationError("");
    299    return promise.forget();
    300  }
    301 
    302  identityHandler->PreventSilentAccess(promise);
    303  return promise.forget();
    304 }
    305 
    306 }  // namespace mozilla::dom