Bug 1769952 - Fix running raptor on a Win10-64 VM r=sparky
[gecko.git] / dom / webauthn / U2FHIDTokenManager.cpp
blob5c3ae22650e6b2859097cad670f3180f532f13b7
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::dom {
15 static StaticMutex gInstanceMutex MOZ_UNANNOTATED;
16 static U2FHIDTokenManager* gInstance;
17 static nsIThread* gPBackgroundThread;
19 static void u2f_register_callback(uint64_t aTransactionId,
20 rust_u2f_result* aResult) {
21 UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
23 StaticMutexAutoLock lock(gInstanceMutex);
24 if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
25 return;
28 nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
29 "U2FHIDTokenManager::HandleRegisterResult", gInstance,
30 &U2FHIDTokenManager::HandleRegisterResult, std::move(rv)));
32 MOZ_ALWAYS_SUCCEEDS(
33 gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
36 static void u2f_sign_callback(uint64_t aTransactionId,
37 rust_u2f_result* aResult) {
38 UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
40 StaticMutexAutoLock lock(gInstanceMutex);
41 if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
42 return;
45 nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
46 "U2FHIDTokenManager::HandleSignResult", gInstance,
47 &U2FHIDTokenManager::HandleSignResult, std::move(rv)));
49 MOZ_ALWAYS_SUCCEEDS(
50 gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
53 U2FHIDTokenManager::U2FHIDTokenManager() {
54 StaticMutexAutoLock lock(gInstanceMutex);
55 mozilla::ipc::AssertIsOnBackgroundThread();
56 MOZ_ASSERT(XRE_IsParentProcess());
57 MOZ_ASSERT(!gInstance);
59 mU2FManager = rust_u2f_mgr_new();
60 gPBackgroundThread = NS_GetCurrentThread();
61 MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
62 gInstance = this;
65 void U2FHIDTokenManager::Drop() {
67 StaticMutexAutoLock lock(gInstanceMutex);
68 mozilla::ipc::AssertIsOnBackgroundThread();
70 mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
71 mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
73 gInstance = nullptr;
76 // Release gInstanceMutex before we call U2FManager::drop(). It will wait
77 // for the work queue thread to join, and that requires the
78 // u2f_{register,sign}_callback to lock and return.
79 rust_u2f_mgr_free(mU2FManager);
80 mU2FManager = nullptr;
82 // Reset transaction ID so that queued runnables exit early.
83 mTransaction.reset();
86 // A U2F Register operation causes a new key pair to be generated by the token.
87 // The token then returns the public key of the key pair, and a handle to the
88 // private key, which is a fancy way of saying "key wrapped private key", as
89 // well as the generated attestation certificate and a signature using that
90 // certificate's private key.
92 // The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
93 // the actual key wrap/unwrap operations.
95 // The format of the return registration data is as follows:
97 // Bytes Value
98 // 1 0x05
99 // 65 public key
100 // 1 key handle length
101 // * key handle
102 // ASN.1 attestation certificate
103 // * attestation signature
105 RefPtr<U2FRegisterPromise> U2FHIDTokenManager::Register(
106 const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
107 mozilla::ipc::AssertIsOnBackgroundThread();
109 uint64_t registerFlags = 0;
111 if (aInfo.Extra().isSome()) {
112 const auto& extra = aInfo.Extra().ref();
113 const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
115 UserVerificationRequirement userVerificaitonRequirement =
116 sel.userVerificationRequirement();
118 bool requireUserVerification =
119 userVerificaitonRequirement == UserVerificationRequirement::Required;
121 bool requirePlatformAttachment = false;
122 if (sel.authenticatorAttachment().isSome()) {
123 const AuthenticatorAttachment authenticatorAttachment =
124 sel.authenticatorAttachment().value();
125 if (authenticatorAttachment == AuthenticatorAttachment::Platform) {
126 requirePlatformAttachment = true;
130 // Set flags for credential creation.
131 if (sel.requireResidentKey()) {
132 registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
134 if (requireUserVerification) {
135 registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
137 if (requirePlatformAttachment) {
138 registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
141 nsTArray<CoseAlg> coseAlgos;
142 for (const auto& coseAlg : extra.coseAlgs()) {
143 switch (static_cast<CoseAlgorithmIdentifier>(coseAlg.alg())) {
144 case CoseAlgorithmIdentifier::ES256:
145 coseAlgos.AppendElement(coseAlg);
146 break;
147 default:
148 continue;
152 // Only if no algorithms were specified, default to the only CTAP 1 / U2F
153 // protocol-supported algorithm. Ultimately this logic must move into
154 // u2f-hid-rs in a fashion that doesn't break the tests.
155 if (extra.coseAlgs().IsEmpty()) {
156 coseAlgos.AppendElement(
157 static_cast<int32_t>(CoseAlgorithmIdentifier::ES256));
160 // If there are no acceptable/supported algorithms, reject the promise.
161 if (coseAlgos.IsEmpty()) {
162 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
163 __func__);
167 CryptoBuffer rpIdHash, clientDataHash;
168 NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
169 nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
170 clientDataHash);
171 if (NS_WARN_IF(NS_FAILED(rv))) {
172 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
173 __func__);
176 ClearPromises();
177 mTransaction.reset();
178 uint64_t tid = rust_u2f_mgr_register(
179 mU2FManager, registerFlags, (uint64_t)aInfo.TimeoutMS(),
180 u2f_register_callback, clientDataHash.Elements(), clientDataHash.Length(),
181 rpIdHash.Elements(), rpIdHash.Length(),
182 U2FKeyHandles(aInfo.ExcludeList()).Get());
184 if (tid == 0) {
185 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
186 __func__);
189 mTransaction = Some(Transaction(
190 tid, rpIdHash, Nothing(), aInfo.ClientDataJSON(), aForceNoneAttestation));
192 return mRegisterPromise.Ensure(__func__);
195 // A U2F Sign operation creates a signature over the "param" arguments (plus
196 // some other stuff) using the private key indicated in the key handle argument.
198 // The format of the signed data is as follows:
200 // 32 Application parameter
201 // 1 User presence (0x01)
202 // 4 Counter
203 // 32 Challenge parameter
205 // The format of the signature data is as follows:
207 // 1 User presence
208 // 4 Counter
209 // * Signature
211 RefPtr<U2FSignPromise> U2FHIDTokenManager::Sign(
212 const WebAuthnGetAssertionInfo& aInfo) {
213 mozilla::ipc::AssertIsOnBackgroundThread();
215 CryptoBuffer rpIdHash, clientDataHash;
216 NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
217 nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash,
218 clientDataHash);
219 if (NS_WARN_IF(NS_FAILED(rv))) {
220 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
223 uint64_t signFlags = 0;
224 nsTArray<nsTArray<uint8_t>> appIds;
225 appIds.AppendElement(rpIdHash.InfallibleClone());
227 Maybe<nsTArray<uint8_t>> appIdHashExt = Nothing();
229 if (aInfo.Extra().isSome()) {
230 const auto& extra = aInfo.Extra().ref();
232 UserVerificationRequirement userVerificaitonReq =
233 extra.userVerificationRequirement();
235 // Set flags for credential requests.
236 if (userVerificaitonReq == UserVerificationRequirement::Required) {
237 signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
240 // Process extensions.
241 for (const WebAuthnExtension& ext : extra.Extensions()) {
242 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
243 appIdHashExt = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone());
244 appIds.AppendElement(appIdHashExt->Clone());
249 ClearPromises();
250 mTransaction.reset();
251 uint64_t tid = rust_u2f_mgr_sign(
252 mU2FManager, signFlags, (uint64_t)aInfo.TimeoutMS(), u2f_sign_callback,
253 clientDataHash.Elements(), clientDataHash.Length(),
254 U2FAppIds(appIds).Get(), U2FKeyHandles(aInfo.AllowList()).Get());
255 if (tid == 0) {
256 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
259 mTransaction =
260 Some(Transaction(tid, std::move(rpIdHash), std::move(appIdHashExt),
261 aInfo.ClientDataJSON()));
263 return mSignPromise.Ensure(__func__);
266 void U2FHIDTokenManager::Cancel() {
267 mozilla::ipc::AssertIsOnBackgroundThread();
269 ClearPromises();
270 rust_u2f_mgr_cancel(mU2FManager);
271 mTransaction.reset();
274 void U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult) {
275 mozilla::ipc::AssertIsOnBackgroundThread();
277 if (mTransaction.isNothing() ||
278 aResult->GetTransactionId() != mTransaction.ref().mId) {
279 return;
282 MOZ_ASSERT(!mRegisterPromise.IsEmpty());
284 if (aResult->IsError()) {
285 mRegisterPromise.Reject(aResult->GetError(), __func__);
286 return;
289 nsTArray<uint8_t> registration;
290 if (!aResult->CopyRegistration(registration)) {
291 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
292 return;
295 // Decompose the U2F registration packet
296 CryptoBuffer pubKeyBuf;
297 CryptoBuffer keyHandle;
298 CryptoBuffer attestationCertBuf;
299 CryptoBuffer signatureBuf;
301 CryptoBuffer regData;
302 regData.Assign(registration);
304 // Only handles attestation cert chains of length=1.
305 nsresult rv = U2FDecomposeRegistrationResponse(
306 regData, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf);
307 if (NS_WARN_IF(NS_FAILED(rv))) {
308 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
309 return;
312 CryptoBuffer rpIdHashBuf;
313 if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
314 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
315 return;
318 CryptoBuffer attObj;
319 rv = AssembleAttestationObject(
320 rpIdHashBuf, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf,
321 mTransaction.ref().mForceNoneAttestation, attObj);
322 if (NS_FAILED(rv)) {
323 mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
324 return;
327 nsTArray<WebAuthnExtensionResult> extensions;
328 WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
329 attObj, keyHandle, regData, extensions);
330 mRegisterPromise.Resolve(std::move(result), __func__);
333 void U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult) {
334 mozilla::ipc::AssertIsOnBackgroundThread();
336 if (mTransaction.isNothing() ||
337 aResult->GetTransactionId() != mTransaction.ref().mId) {
338 return;
341 MOZ_ASSERT(!mSignPromise.IsEmpty());
343 if (aResult->IsError()) {
344 mSignPromise.Reject(aResult->GetError(), __func__);
345 return;
348 nsTArray<uint8_t> hashChosenByAuthenticator;
349 if (!aResult->CopyAppId(hashChosenByAuthenticator)) {
350 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
351 return;
354 nsTArray<uint8_t> keyHandle;
355 if (!aResult->CopyKeyHandle(keyHandle)) {
356 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
357 return;
360 nsTArray<uint8_t> signature;
361 if (!aResult->CopySignature(signature)) {
362 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
363 return;
366 CryptoBuffer rawSignatureBuf;
367 if (!rawSignatureBuf.Assign(signature)) {
368 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
369 return;
372 nsTArray<WebAuthnExtensionResult> extensions;
374 if (mTransaction.ref().mAppIdHash.isSome()) {
375 bool usedAppId =
376 (hashChosenByAuthenticator == mTransaction.ref().mAppIdHash.ref());
377 extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
380 CryptoBuffer signatureBuf;
381 CryptoBuffer counterBuf;
382 uint8_t flags = 0;
383 nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
384 signatureBuf);
385 if (NS_WARN_IF(NS_FAILED(rv))) {
386 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
387 return;
390 CryptoBuffer chosenAppIdBuf;
391 if (!chosenAppIdBuf.Assign(hashChosenByAuthenticator)) {
392 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
393 return;
396 // Preserve the two LSBs of the flags byte, UP and RFU1.
397 // See <https://github.com/fido-alliance/fido-2-specs/pull/519>
398 flags &= 0b11;
400 CryptoBuffer emptyAttestationData;
401 CryptoBuffer authenticatorData;
402 rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
403 emptyAttestationData, authenticatorData);
404 if (NS_WARN_IF(NS_FAILED(rv))) {
405 mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
406 return;
409 nsTArray<uint8_t> userHandle;
411 WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
412 keyHandle, signatureBuf, authenticatorData,
413 extensions, rawSignatureBuf, userHandle);
414 mSignPromise.Resolve(std::move(result), __func__);
417 } // namespace mozilla::dom