Bug 1702375 [wpt PR 28327] - Update docs to point directly at RuntimeEnabledFeatures...
[gecko.git] / dom / webauthn / U2FHIDTokenManager.cpp
blob3b37347c5332f4fb6027fb8c1d435b3ce6d82d87
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "WebAuthnCoseIdentifiers.h"
8 #include "mozilla/dom/U2FHIDTokenManager.h"
9 #include "mozilla/dom/WebAuthnUtil.h"
10 #include "mozilla/ipc/BackgroundParent.h"
11 #include "mozilla/StaticMutex.h"
13 namespace mozilla {
14 namespace dom {
16 static StaticMutex gInstanceMutex;
17 static U2FHIDTokenManager* gInstance;
18 static nsIThread* gPBackgroundThread;
20 static void u2f_register_callback(uint64_t aTransactionId,
21 rust_u2f_result* aResult) {
22 UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
24 StaticMutexAutoLock lock(gInstanceMutex);
25 if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
26 return;
29 nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
30 "U2FHIDTokenManager::HandleRegisterResult", gInstance,
31 &U2FHIDTokenManager::HandleRegisterResult, std::move(rv)));
33 MOZ_ALWAYS_SUCCEEDS(
34 gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
37 static void u2f_sign_callback(uint64_t aTransactionId,
38 rust_u2f_result* aResult) {
39 UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
41 StaticMutexAutoLock lock(gInstanceMutex);
42 if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
43 return;
46 nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
47 "U2FHIDTokenManager::HandleSignResult", gInstance,
48 &U2FHIDTokenManager::HandleSignResult, std::move(rv)));
50 MOZ_ALWAYS_SUCCEEDS(
51 gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
54 U2FHIDTokenManager::U2FHIDTokenManager() {
55 StaticMutexAutoLock lock(gInstanceMutex);
56 mozilla::ipc::AssertIsOnBackgroundThread();
57 MOZ_ASSERT(XRE_IsParentProcess());
58 MOZ_ASSERT(!gInstance);
60 mU2FManager = rust_u2f_mgr_new();
61 gPBackgroundThread = NS_GetCurrentThread();
62 MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
63 gInstance = this;
66 void U2FHIDTokenManager::Drop() {
68 StaticMutexAutoLock lock(gInstanceMutex);
69 mozilla::ipc::AssertIsOnBackgroundThread();
71 mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
72 mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
74 gInstance = nullptr;
77 // Release gInstanceMutex before we call U2FManager::drop(). It will wait
78 // for the work queue thread to join, and that requires the
79 // u2f_{register,sign}_callback to lock and return.
80 rust_u2f_mgr_free(mU2FManager);
81 mU2FManager = nullptr;
83 // Reset transaction ID so that queued runnables exit early.
84 mTransaction.reset();
87 // A U2F Register operation causes a new key pair to be generated by the token.
88 // The token then returns the public key of the key pair, and a handle to the
89 // private key, which is a fancy way of saying "key wrapped private key", as
90 // well as the generated attestation certificate and a signature using that
91 // certificate's private key.
93 // The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
94 // the actual key wrap/unwrap operations.
96 // The format of the return registration data is as follows:
98 // Bytes Value
99 // 1 0x05
100 // 65 public key
101 // 1 key handle length
102 // * key handle
103 // ASN.1 attestation certificate
104 // * attestation signature
106 RefPtr<U2FRegisterPromise> U2FHIDTokenManager::Register(
107 const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
108 mozilla::ipc::AssertIsOnBackgroundThread();
110 uint64_t registerFlags = 0;
112 if (aInfo.Extra().isSome()) {
113 const auto& extra = aInfo.Extra().ref();
114 const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
116 UserVerificationRequirement userVerificaitonRequirement =
117 sel.userVerificationRequirement();
119 bool requireUserVerification =
120 userVerificaitonRequirement == UserVerificationRequirement::Required;
122 bool requirePlatformAttachment = false;
123 if (sel.authenticatorAttachment().isSome()) {
124 const AuthenticatorAttachment authenticatorAttachment =
125 sel.authenticatorAttachment().value();
126 if (authenticatorAttachment == AuthenticatorAttachment::Platform) {
127 requirePlatformAttachment = true;
131 // Set flags for credential creation.
132 if (sel.requireResidentKey()) {
133 registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
135 if (requireUserVerification) {
136 registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
138 if (requirePlatformAttachment) {
139 registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
142 nsTArray<CoseAlg> coseAlgos;
143 for (const auto& coseAlg : extra.coseAlgs()) {
144 switch (static_cast<CoseAlgorithmIdentifier>(coseAlg.alg())) {
145 case CoseAlgorithmIdentifier::ES256:
146 coseAlgos.AppendElement(coseAlg);
147 break;
148 default:
149 continue;
153 // Only if no algorithms were specified, default to the only CTAP 1 / U2F
154 // protocol-supported algorithm. Ultimately this logic must move into
155 // u2f-hid-rs in a fashion that doesn't break the tests.
156 if (extra.coseAlgs().IsEmpty()) {
157 coseAlgos.AppendElement(
158 static_cast<int32_t>(CoseAlgorithmIdentifier::ES256));
161 // If there are no acceptable/supported algorithms, reject the promise.
162 if (coseAlgos.IsEmpty()) {
163 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
164 __func__);
168 CryptoBuffer rpIdHash, clientDataHash;
169 NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
170 nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
171 clientDataHash);
172 if (NS_WARN_IF(NS_FAILED(rv))) {
173 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
174 __func__);
177 ClearPromises();
178 mTransaction.reset();
179 uint64_t tid = rust_u2f_mgr_register(
180 mU2FManager, registerFlags, (uint64_t)aInfo.TimeoutMS(),
181 u2f_register_callback, clientDataHash.Elements(), clientDataHash.Length(),
182 rpIdHash.Elements(), rpIdHash.Length(),
183 U2FKeyHandles(aInfo.ExcludeList()).Get());
185 if (tid == 0) {
186 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
187 __func__);
190 mTransaction = Some(Transaction(
191 tid, rpIdHash, Nothing(), aInfo.ClientDataJSON(), aForceNoneAttestation));
193 return mRegisterPromise.Ensure(__func__);
196 // A U2F Sign operation creates a signature over the "param" arguments (plus
197 // some other stuff) using the private key indicated in the key handle argument.
199 // The format of the signed data is as follows:
201 // 32 Application parameter
202 // 1 User presence (0x01)
203 // 4 Counter
204 // 32 Challenge parameter
206 // The format of the signature data is as follows:
208 // 1 User presence
209 // 4 Counter
210 // * Signature
212 RefPtr<U2FSignPromise> U2FHIDTokenManager::Sign(
213 const WebAuthnGetAssertionInfo& aInfo) {
214 mozilla::ipc::AssertIsOnBackgroundThread();
216 CryptoBuffer rpIdHash, clientDataHash;
217 NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
218 nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
219 clientDataHash);
220 if (NS_WARN_IF(NS_FAILED(rv))) {
221 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
224 uint64_t signFlags = 0;
225 nsTArray<nsTArray<uint8_t>> appIds;
226 appIds.AppendElement(rpIdHash.InfallibleClone());
228 Maybe<nsTArray<uint8_t>> appIdHashExt = Nothing();
230 if (aInfo.Extra().isSome()) {
231 const auto& extra = aInfo.Extra().ref();
233 UserVerificationRequirement userVerificaitonReq =
234 extra.userVerificationRequirement();
236 // Set flags for credential requests.
237 if (userVerificaitonReq == UserVerificationRequirement::Required) {
238 signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
241 // Process extensions.
242 for (const WebAuthnExtension& ext : extra.Extensions()) {
243 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
244 appIdHashExt = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone());
245 appIds.AppendElement(appIdHashExt->Clone());
250 ClearPromises();
251 mTransaction.reset();
252 uint64_t tid = rust_u2f_mgr_sign(
253 mU2FManager, signFlags, (uint64_t)aInfo.TimeoutMS(), u2f_sign_callback,
254 clientDataHash.Elements(), clientDataHash.Length(),
255 U2FAppIds(appIds).Get(), U2FKeyHandles(aInfo.AllowList()).Get());
256 if (tid == 0) {
257 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
260 mTransaction =
261 Some(Transaction(tid, std::move(rpIdHash), std::move(appIdHashExt),
262 aInfo.ClientDataJSON()));
264 return mSignPromise.Ensure(__func__);
267 void U2FHIDTokenManager::Cancel() {
268 mozilla::ipc::AssertIsOnBackgroundThread();
270 ClearPromises();
271 rust_u2f_mgr_cancel(mU2FManager);
272 mTransaction.reset();
275 void U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult) {
276 mozilla::ipc::AssertIsOnBackgroundThread();
278 if (mTransaction.isNothing() ||
279 aResult->GetTransactionId() != mTransaction.ref().mId) {
280 return;
283 MOZ_ASSERT(!mRegisterPromise.IsEmpty());
285 if (aResult->IsError()) {
286 mRegisterPromise.Reject(aResult->GetError(), __func__);
287 return;
290 nsTArray<uint8_t> registration;
291 if (!aResult->CopyRegistration(registration)) {
292 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
293 return;
296 // Decompose the U2F registration packet
297 CryptoBuffer pubKeyBuf;
298 CryptoBuffer keyHandle;
299 CryptoBuffer attestationCertBuf;
300 CryptoBuffer signatureBuf;
302 CryptoBuffer regData;
303 regData.Assign(registration);
305 // Only handles attestation cert chains of length=1.
306 nsresult rv = U2FDecomposeRegistrationResponse(
307 regData, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf);
308 if (NS_WARN_IF(NS_FAILED(rv))) {
309 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
310 return;
313 CryptoBuffer rpIdHashBuf;
314 if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
315 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
316 return;
319 CryptoBuffer attObj;
320 rv = AssembleAttestationObject(
321 rpIdHashBuf, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf,
322 mTransaction.ref().mForceNoneAttestation, attObj);
323 if (NS_FAILED(rv)) {
324 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
325 return;
328 nsTArray<WebAuthnExtensionResult> extensions;
329 WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
330 attObj, keyHandle, regData, extensions);
331 mRegisterPromise.Resolve(std::move(result), __func__);
334 void U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult) {
335 mozilla::ipc::AssertIsOnBackgroundThread();
337 if (mTransaction.isNothing() ||
338 aResult->GetTransactionId() != mTransaction.ref().mId) {
339 return;
342 MOZ_ASSERT(!mSignPromise.IsEmpty());
344 if (aResult->IsError()) {
345 mSignPromise.Reject(aResult->GetError(), __func__);
346 return;
349 nsTArray<uint8_t> hashChosenByAuthenticator;
350 if (!aResult->CopyAppId(hashChosenByAuthenticator)) {
351 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
352 return;
355 nsTArray<uint8_t> keyHandle;
356 if (!aResult->CopyKeyHandle(keyHandle)) {
357 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
358 return;
361 nsTArray<uint8_t> signature;
362 if (!aResult->CopySignature(signature)) {
363 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
364 return;
367 CryptoBuffer rawSignatureBuf;
368 if (!rawSignatureBuf.Assign(signature)) {
369 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
370 return;
373 nsTArray<WebAuthnExtensionResult> extensions;
375 if (mTransaction.ref().mAppIdHash.isSome()) {
376 bool usedAppId =
377 (hashChosenByAuthenticator == mTransaction.ref().mAppIdHash.ref());
378 extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
381 CryptoBuffer signatureBuf;
382 CryptoBuffer counterBuf;
383 uint8_t flags = 0;
384 nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
385 signatureBuf);
386 if (NS_WARN_IF(NS_FAILED(rv))) {
387 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
388 return;
391 CryptoBuffer chosenAppIdBuf;
392 if (!chosenAppIdBuf.Assign(hashChosenByAuthenticator)) {
393 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
394 return;
397 // Preserve the two LSBs of the flags byte, UP and RFU1.
398 // See <https://github.com/fido-alliance/fido-2-specs/pull/519>
399 flags &= 0b11;
401 CryptoBuffer emptyAttestationData;
402 CryptoBuffer authenticatorData;
403 rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
404 emptyAttestationData, authenticatorData);
405 if (NS_WARN_IF(NS_FAILED(rv))) {
406 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
407 return;
410 nsTArray<uint8_t> userHandle;
412 WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
413 keyHandle, signatureBuf, authenticatorData,
414 extensions, rawSignatureBuf, userHandle);
415 mSignPromise.Resolve(std::move(result), __func__);
418 } // namespace dom
419 } // namespace mozilla