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"
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
)) {
29 nsCOMPtr
<nsIRunnable
> r(NewRunnableMethod
<UniquePtr
<U2FResult
>&&>(
30 "U2FHIDTokenManager::HandleRegisterResult", gInstance
,
31 &U2FHIDTokenManager::HandleRegisterResult
, std::move(rv
)));
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
)) {
46 nsCOMPtr
<nsIRunnable
> r(NewRunnableMethod
<UniquePtr
<U2FResult
>&&>(
47 "U2FHIDTokenManager::HandleSignResult", gInstance
,
48 &U2FHIDTokenManager::HandleSignResult
, std::move(rv
)));
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!");
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__
);
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.
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:
101 // 1 key handle length
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
);
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
,
168 CryptoBuffer rpIdHash
, clientDataHash
;
169 NS_ConvertUTF16toUTF8
rpId(aInfo
.RpId());
170 nsresult rv
= BuildTransactionHashes(rpId
, aInfo
.ClientDataJSON(), rpIdHash
,
172 if (NS_WARN_IF(NS_FAILED(rv
))) {
173 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
,
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());
186 return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
,
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)
204 // 32 Challenge parameter
206 // The format of the signature data is as follows:
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
,
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());
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());
257 return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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();
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
) {
283 MOZ_ASSERT(!mRegisterPromise
.IsEmpty());
285 if (aResult
->IsError()) {
286 mRegisterPromise
.Reject(aResult
->GetError(), __func__
);
290 nsTArray
<uint8_t> registration
;
291 if (!aResult
->CopyRegistration(registration
)) {
292 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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__
);
313 CryptoBuffer rpIdHashBuf
;
314 if (!rpIdHashBuf
.Assign(mTransaction
.ref().mRpIdHash
)) {
315 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
320 rv
= AssembleAttestationObject(
321 rpIdHashBuf
, pubKeyBuf
, keyHandle
, attestationCertBuf
, signatureBuf
,
322 mTransaction
.ref().mForceNoneAttestation
, attObj
);
324 mRegisterPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
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
) {
342 MOZ_ASSERT(!mSignPromise
.IsEmpty());
344 if (aResult
->IsError()) {
345 mSignPromise
.Reject(aResult
->GetError(), __func__
);
349 nsTArray
<uint8_t> hashChosenByAuthenticator
;
350 if (!aResult
->CopyAppId(hashChosenByAuthenticator
)) {
351 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
355 nsTArray
<uint8_t> keyHandle
;
356 if (!aResult
->CopyKeyHandle(keyHandle
)) {
357 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
361 nsTArray
<uint8_t> signature
;
362 if (!aResult
->CopySignature(signature
)) {
363 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
367 CryptoBuffer rawSignatureBuf
;
368 if (!rawSignatureBuf
.Assign(signature
)) {
369 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
373 nsTArray
<WebAuthnExtensionResult
> extensions
;
375 if (mTransaction
.ref().mAppIdHash
.isSome()) {
377 (hashChosenByAuthenticator
== mTransaction
.ref().mAppIdHash
.ref());
378 extensions
.AppendElement(WebAuthnExtensionResultAppId(usedAppId
));
381 CryptoBuffer signatureBuf
;
382 CryptoBuffer counterBuf
;
384 nsresult rv
= U2FDecomposeSignResponse(rawSignatureBuf
, flags
, counterBuf
,
386 if (NS_WARN_IF(NS_FAILED(rv
))) {
387 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
391 CryptoBuffer chosenAppIdBuf
;
392 if (!chosenAppIdBuf
.Assign(hashChosenByAuthenticator
)) {
393 mSignPromise
.Reject(NS_ERROR_DOM_UNKNOWN_ERR
, __func__
);
397 // Preserve the two LSBs of the flags byte, UP and RFU1.
398 // See <https://github.com/fido-alliance/fido-2-specs/pull/519>
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__
);
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__
);
419 } // namespace mozilla