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
)) {
28 nsCOMPtr
<nsIRunnable
> r(NewRunnableMethod
<UniquePtr
<U2FResult
>&&>(
29 "U2FHIDTokenManager::HandleRegisterResult", gInstance
,
30 &U2FHIDTokenManager::HandleRegisterResult
, std::move(rv
)));
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
)) {
45 nsCOMPtr
<nsIRunnable
> r(NewRunnableMethod
<UniquePtr
<U2FResult
>&&>(
46 "U2FHIDTokenManager::HandleSignResult", gInstance
,
47 &U2FHIDTokenManager::HandleSignResult
, std::move(rv
)));
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!");
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__
);
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.
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:
100 // 1 key handle length
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
);
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
,
167 CryptoBuffer rpIdHash
, clientDataHash
;
168 NS_ConvertUTF16toUTF8
rpId(aInfo
.RpId());
169 nsresult rv
= BuildTransactionHashes(rpId
, aInfo
.ClientDataJSON(), rpIdHash
,
171 if (NS_WARN_IF(NS_FAILED(rv
))) {
172 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
,
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());
185 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
,
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)
203 // 32 Challenge parameter
205 // The format of the signature data is as follows:
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
,
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());
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());
256 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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();
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
) {
282 MOZ_ASSERT(!mRegisterPromise
.IsEmpty());
284 if (aResult
->IsError()) {
285 mRegisterPromise
.Reject(aResult
->GetError(), __func__
);
289 nsTArray
<uint8_t> registration
;
290 if (!aResult
->CopyRegistration(registration
)) {
291 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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__
);
312 CryptoBuffer rpIdHashBuf
;
313 if (!rpIdHashBuf
.Assign(mTransaction
.ref().mRpIdHash
)) {
314 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
319 rv
= AssembleAttestationObject(
320 rpIdHashBuf
, pubKeyBuf
, keyHandle
, attestationCertBuf
, signatureBuf
,
321 mTransaction
.ref().mForceNoneAttestation
, attObj
);
323 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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
) {
341 MOZ_ASSERT(!mSignPromise
.IsEmpty());
343 if (aResult
->IsError()) {
344 mSignPromise
.Reject(aResult
->GetError(), __func__
);
348 nsTArray
<uint8_t> hashChosenByAuthenticator
;
349 if (!aResult
->CopyAppId(hashChosenByAuthenticator
)) {
350 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
354 nsTArray
<uint8_t> keyHandle
;
355 if (!aResult
->CopyKeyHandle(keyHandle
)) {
356 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
360 nsTArray
<uint8_t> signature
;
361 if (!aResult
->CopySignature(signature
)) {
362 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
366 CryptoBuffer rawSignatureBuf
;
367 if (!rawSignatureBuf
.Assign(signature
)) {
368 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
372 nsTArray
<WebAuthnExtensionResult
> extensions
;
374 if (mTransaction
.ref().mAppIdHash
.isSome()) {
376 (hashChosenByAuthenticator
== mTransaction
.ref().mAppIdHash
.ref());
377 extensions
.AppendElement(WebAuthnExtensionResultAppId(usedAppId
));
380 CryptoBuffer signatureBuf
;
381 CryptoBuffer counterBuf
;
383 nsresult rv
= U2FDecomposeSignResponse(rawSignatureBuf
, flags
, counterBuf
,
385 if (NS_WARN_IF(NS_FAILED(rv
))) {
386 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
390 CryptoBuffer chosenAppIdBuf
;
391 if (!chosenAppIdBuf
.Assign(hashChosenByAuthenticator
)) {
392 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
396 // Preserve the two LSBs of the flags byte, UP and RFU1.
397 // See <https://github.com/fido-alliance/fido-2-specs/pull/519>
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__
);
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