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