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