Source code

Revision control

Other Tools

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/. */
6
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"
12
13
namespace mozilla {
14
namespace dom {
15
16
static StaticMutex gInstanceMutex;
17
static U2FHIDTokenManager* gInstance;
18
static nsIThread* gPBackgroundThread;
19
20
static void u2f_register_callback(uint64_t aTransactionId,
21
rust_u2f_result* aResult) {
22
UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
23
24
StaticMutexAutoLock lock(gInstanceMutex);
25
if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
26
return;
27
}
28
29
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
30
"U2FHIDTokenManager::HandleRegisterResult", gInstance,
31
&U2FHIDTokenManager::HandleRegisterResult, std::move(rv)));
32
33
MOZ_ALWAYS_SUCCEEDS(
34
gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
35
}
36
37
static void u2f_sign_callback(uint64_t aTransactionId,
38
rust_u2f_result* aResult) {
39
UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
40
41
StaticMutexAutoLock lock(gInstanceMutex);
42
if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
43
return;
44
}
45
46
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
47
"U2FHIDTokenManager::HandleSignResult", gInstance,
48
&U2FHIDTokenManager::HandleSignResult, std::move(rv)));
49
50
MOZ_ALWAYS_SUCCEEDS(
51
gPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
52
}
53
54
U2FHIDTokenManager::U2FHIDTokenManager() {
55
StaticMutexAutoLock lock(gInstanceMutex);
56
mozilla::ipc::AssertIsOnBackgroundThread();
57
MOZ_ASSERT(XRE_IsParentProcess());
58
MOZ_ASSERT(!gInstance);
59
60
mU2FManager = rust_u2f_mgr_new();
61
gPBackgroundThread = NS_GetCurrentThread();
62
MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
63
gInstance = this;
64
}
65
66
void U2FHIDTokenManager::Drop() {
67
{
68
StaticMutexAutoLock lock(gInstanceMutex);
69
mozilla::ipc::AssertIsOnBackgroundThread();
70
71
mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
72
mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
73
74
gInstance = nullptr;
75
}
76
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;
82
83
// Reset transaction ID so that queued runnables exit early.
84
mTransaction.reset();
85
}
86
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.
92
//
93
// The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
94
// the actual key wrap/unwrap operations.
95
//
96
// The format of the return registration data is as follows:
97
//
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
105
//
106
RefPtr<U2FRegisterPromise> U2FHIDTokenManager::Register(
107
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
108
mozilla::ipc::AssertIsOnBackgroundThread();
109
110
uint64_t registerFlags = 0;
111
112
if (aInfo.Extra().isSome()) {
113
const auto& extra = aInfo.Extra().ref();
114
const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
115
116
UserVerificationRequirement userVerificaitonRequirement =
117
sel.userVerificationRequirement();
118
119
bool requireUserVerification =
120
userVerificaitonRequirement == UserVerificationRequirement::Required;
121
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;
128
}
129
}
130
131
// Set flags for credential creation.
132
if (sel.requireResidentKey()) {
133
registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
134
}
135
if (requireUserVerification) {
136
registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
137
}
138
if (requirePlatformAttachment) {
139
registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
140
}
141
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;
150
}
151
}
152
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));
159
}
160
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__);
165
}
166
}
167
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__);
175
}
176
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());
184
185
if (tid == 0) {
186
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR,
187
__func__);
188
}
189
190
mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON(),
191
aForceNoneAttestation));
192
193
return mRegisterPromise.Ensure(__func__);
194
}
195
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.
198
//
199
// The format of the signed data is as follows:
200
//
201
// 32 Application parameter
202
// 1 User presence (0x01)
203
// 4 Counter
204
// 32 Challenge parameter
205
//
206
// The format of the signature data is as follows:
207
//
208
// 1 User presence
209
// 4 Counter
210
// * Signature
211
//
212
RefPtr<U2FSignPromise> U2FHIDTokenManager::Sign(
213
const WebAuthnGetAssertionInfo& aInfo) {
214
mozilla::ipc::AssertIsOnBackgroundThread();
215
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__);
222
}
223
224
uint64_t signFlags = 0;
225
nsTArray<nsTArray<uint8_t>> appIds;
226
appIds.AppendElement(rpIdHash);
227
228
if (aInfo.Extra().isSome()) {
229
const auto& extra = aInfo.Extra().ref();
230
231
UserVerificationRequirement userVerificaitonReq =
232
extra.userVerificationRequirement();
233
234
// Set flags for credential requests.
235
if (userVerificaitonReq == UserVerificationRequirement::Required) {
236
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
237
}
238
239
// Process extensions.
240
for (const WebAuthnExtension& ext : extra.Extensions()) {
241
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
242
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
243
}
244
}
245
}
246
247
ClearPromises();
248
mTransaction.reset();
249
uint64_t tid = rust_u2f_mgr_sign(
250
mU2FManager, signFlags, (uint64_t)aInfo.TimeoutMS(), u2f_sign_callback,
251
clientDataHash.Elements(), clientDataHash.Length(),
252
U2FAppIds(appIds).Get(), U2FKeyHandles(aInfo.AllowList()).Get());
253
if (tid == 0) {
254
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
255
}
256
257
mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON()));
258
259
return mSignPromise.Ensure(__func__);
260
}
261
262
void U2FHIDTokenManager::Cancel() {
263
mozilla::ipc::AssertIsOnBackgroundThread();
264
265
ClearPromises();
266
rust_u2f_mgr_cancel(mU2FManager);
267
mTransaction.reset();
268
}
269
270
void U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult) {
271
mozilla::ipc::AssertIsOnBackgroundThread();
272
273
if (mTransaction.isNothing() ||
274
aResult->GetTransactionId() != mTransaction.ref().mId) {
275
return;
276
}
277
278
MOZ_ASSERT(!mRegisterPromise.IsEmpty());
279
280
if (aResult->IsError()) {
281
mRegisterPromise.Reject(aResult->GetError(), __func__);
282
return;
283
}
284
285
nsTArray<uint8_t> registration;
286
if (!aResult->CopyRegistration(registration)) {
287
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
288
return;
289
}
290
291
// Decompose the U2F registration packet
292
CryptoBuffer pubKeyBuf;
293
CryptoBuffer keyHandle;
294
CryptoBuffer attestationCertBuf;
295
CryptoBuffer signatureBuf;
296
297
CryptoBuffer regData;
298
regData.Assign(registration);
299
300
// Only handles attestation cert chains of length=1.
301
nsresult rv = U2FDecomposeRegistrationResponse(
302
regData, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf);
303
if (NS_WARN_IF(NS_FAILED(rv))) {
304
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
305
return;
306
}
307
308
CryptoBuffer rpIdHashBuf;
309
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
310
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
311
return;
312
}
313
314
CryptoBuffer attObj;
315
rv = AssembleAttestationObject(
316
rpIdHashBuf, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf,
317
mTransaction.ref().mForceNoneAttestation, attObj);
318
if (NS_FAILED(rv)) {
319
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
320
return;
321
}
322
323
nsTArray<WebAuthnExtensionResult> extensions;
324
WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
325
attObj, keyHandle, regData, extensions);
326
mRegisterPromise.Resolve(std::move(result), __func__);
327
}
328
329
void U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult) {
330
mozilla::ipc::AssertIsOnBackgroundThread();
331
332
if (mTransaction.isNothing() ||
333
aResult->GetTransactionId() != mTransaction.ref().mId) {
334
return;
335
}
336
337
MOZ_ASSERT(!mSignPromise.IsEmpty());
338
339
if (aResult->IsError()) {
340
mSignPromise.Reject(aResult->GetError(), __func__);
341
return;
342
}
343
344
nsTArray<uint8_t> appId;
345
if (!aResult->CopyAppId(appId)) {
346
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
347
return;
348
}
349
350
nsTArray<uint8_t> keyHandle;
351
if (!aResult->CopyKeyHandle(keyHandle)) {
352
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
353
return;
354
}
355
356
nsTArray<uint8_t> signature;
357
if (!aResult->CopySignature(signature)) {
358
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
359
return;
360
}
361
362
CryptoBuffer rawSignatureBuf;
363
if (!rawSignatureBuf.Assign(signature)) {
364
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
365
return;
366
}
367
368
nsTArray<WebAuthnExtensionResult> extensions;
369
370
if (appId != mTransaction.ref().mRpIdHash) {
371
// Indicate to the RP that we used the FIDO appId.
372
extensions.AppendElement(WebAuthnExtensionResultAppId(true));
373
}
374
375
CryptoBuffer signatureBuf;
376
CryptoBuffer counterBuf;
377
uint8_t flags = 0;
378
nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
379
signatureBuf);
380
if (NS_WARN_IF(NS_FAILED(rv))) {
381
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
382
return;
383
}
384
385
CryptoBuffer chosenAppIdBuf;
386
if (!chosenAppIdBuf.Assign(appId)) {
387
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
388
return;
389
}
390
391
// Preserve the two LSBs of the flags byte, UP and RFU1.
393
flags &= 0b11;
394
395
CryptoBuffer emptyAttestationData;
396
CryptoBuffer authenticatorData;
397
rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
398
emptyAttestationData, authenticatorData);
399
if (NS_WARN_IF(NS_FAILED(rv))) {
400
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
401
return;
402
}
403
404
nsTArray<uint8_t> userHandle;
405
406
WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
407
keyHandle, signatureBuf, authenticatorData,
408
extensions, rawSignatureBuf, userHandle);
409
mSignPromise.Resolve(std::move(result), __func__);
410
}
411
412
} // namespace dom
413
} // namespace mozilla