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 "mozilla/dom/U2FTokenManager.h"
8
#include "mozilla/dom/U2FTokenTransport.h"
9
#include "mozilla/dom/U2FHIDTokenManager.h"
10
#include "mozilla/dom/U2FSoftTokenManager.h"
11
#include "mozilla/dom/PWebAuthnTransactionParent.h"
12
#include "mozilla/MozPromise.h"
13
#include "mozilla/dom/WebAuthnUtil.h"
14
#include "mozilla/ipc/BackgroundParent.h"
15
#include "mozilla/ClearOnShutdown.h"
16
#include "mozilla/Unused.h"
17
#include "nsTextFormatter.h"
18
19
#ifdef MOZ_WIDGET_ANDROID
20
# include "mozilla/dom/AndroidWebAuthnTokenManager.h"
21
#endif
22
23
// Not named "security.webauth.u2f_softtoken_counter" because setting that
24
// name causes the window.u2f object to disappear until preferences get
25
// reloaded, as its pref is a substring!
26
#define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter"
27
#define PREF_WEBAUTHN_SOFTTOKEN_ENABLED \
28
"security.webauth.webauthn_enable_softtoken"
29
#define PREF_WEBAUTHN_USBTOKEN_ENABLED \
30
"security.webauth.webauthn_enable_usbtoken"
31
#define PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION \
32
"security.webauth.webauthn_testing_allow_direct_attestation"
33
#define PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED \
34
"security.webauth.webauthn_enable_android_fido2"
35
namespace mozilla {
36
namespace dom {
37
38
/***********************************************************************
39
* Statics
40
**********************************************************************/
41
42
class U2FPrefManager;
43
44
namespace {
45
static mozilla::LazyLogModule gU2FTokenManagerLog("u2fkeymanager");
46
StaticRefPtr<U2FTokenManager> gU2FTokenManager;
47
StaticRefPtr<U2FPrefManager> gPrefManager;
48
static nsIThread* gBackgroundThread;
49
} // namespace
50
51
// Data for WebAuthn UI prompt notifications.
52
static const char16_t kRegisterPromptNotifcation[] =
53
u"{\"action\":\"register\",\"tid\":%llu,\"origin\":\"%s\"}";
54
static const char16_t kRegisterDirectPromptNotifcation[] =
55
u"{\"action\":\"register-direct\",\"tid\":%llu,\"origin\":\"%s\"}";
56
static const char16_t kSignPromptNotifcation[] =
57
u"{\"action\":\"sign\",\"tid\":%llu,\"origin\":\"%s\"}";
58
static const char16_t kCancelPromptNotifcation[] =
59
u"{\"action\":\"cancel\",\"tid\":%llu}";
60
61
class U2FPrefManager final : public nsIObserver {
62
private:
63
U2FPrefManager() : mPrefMutex("U2FPrefManager Mutex") { UpdateValues(); }
64
~U2FPrefManager() = default;
65
66
public:
67
NS_DECL_ISUPPORTS
68
69
static U2FPrefManager* GetOrCreate() {
70
MOZ_ASSERT(NS_IsMainThread());
71
if (!gPrefManager) {
72
gPrefManager = new U2FPrefManager();
73
Preferences::AddStrongObserver(gPrefManager,
74
PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
75
Preferences::AddStrongObserver(gPrefManager, PREF_U2F_NSSTOKEN_COUNTER);
76
Preferences::AddStrongObserver(gPrefManager,
77
PREF_WEBAUTHN_USBTOKEN_ENABLED);
78
Preferences::AddStrongObserver(gPrefManager,
79
PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED);
80
Preferences::AddStrongObserver(gPrefManager,
81
PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
82
ClearOnShutdown(&gPrefManager, ShutdownPhase::ShutdownThreads);
83
}
84
return gPrefManager;
85
}
86
87
static U2FPrefManager* Get() { return gPrefManager; }
88
89
bool GetSoftTokenEnabled() {
90
MutexAutoLock lock(mPrefMutex);
91
return mSoftTokenEnabled;
92
}
93
94
int GetSoftTokenCounter() {
95
MutexAutoLock lock(mPrefMutex);
96
return mSoftTokenCounter;
97
}
98
99
bool GetUsbTokenEnabled() {
100
MutexAutoLock lock(mPrefMutex);
101
return mUsbTokenEnabled;
102
}
103
104
bool GetAndroidFido2Enabled() {
105
MutexAutoLock lock(mPrefMutex);
106
return mAndroidFido2Enabled;
107
}
108
109
bool GetAllowDirectAttestationForTesting() {
110
MutexAutoLock lock(mPrefMutex);
111
return mAllowDirectAttestation;
112
}
113
114
NS_IMETHODIMP
115
Observe(nsISupports* aSubject, const char* aTopic,
116
const char16_t* aData) override {
117
UpdateValues();
118
return NS_OK;
119
}
120
121
private:
122
void UpdateValues() {
123
MOZ_ASSERT(NS_IsMainThread());
124
MutexAutoLock lock(mPrefMutex);
125
mSoftTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_SOFTTOKEN_ENABLED);
126
mSoftTokenCounter = Preferences::GetUint(PREF_U2F_NSSTOKEN_COUNTER);
127
mUsbTokenEnabled = Preferences::GetBool(PREF_WEBAUTHN_USBTOKEN_ENABLED);
128
mAndroidFido2Enabled =
129
Preferences::GetBool(PREF_WEBAUTHN_ANDROID_FIDO2_ENABLED);
130
mAllowDirectAttestation =
131
Preferences::GetBool(PREF_WEBAUTHN_ALLOW_DIRECT_ATTESTATION);
132
}
133
134
Mutex mPrefMutex;
135
bool mSoftTokenEnabled;
136
int mSoftTokenCounter;
137
bool mUsbTokenEnabled;
138
bool mAndroidFido2Enabled;
139
bool mAllowDirectAttestation;
140
};
141
142
NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
143
144
/***********************************************************************
145
* U2FManager Implementation
146
**********************************************************************/
147
148
NS_IMPL_ISUPPORTS(U2FTokenManager, nsIU2FTokenManager);
149
150
U2FTokenManager::U2FTokenManager()
151
: mTransactionParent(nullptr), mLastTransactionId(0) {
152
MOZ_ASSERT(XRE_IsParentProcess());
153
// Create on the main thread to make sure ClearOnShutdown() works.
154
MOZ_ASSERT(NS_IsMainThread());
155
// Create the preference manager while we're initializing.
156
U2FPrefManager::GetOrCreate();
157
}
158
159
// static
160
void U2FTokenManager::Initialize() {
161
if (!XRE_IsParentProcess()) {
162
return;
163
}
164
MOZ_ASSERT(NS_IsMainThread());
165
MOZ_ASSERT(!gU2FTokenManager);
166
gU2FTokenManager = new U2FTokenManager();
167
ClearOnShutdown(&gU2FTokenManager);
168
}
169
170
// static
171
U2FTokenManager* U2FTokenManager::Get() {
172
MOZ_ASSERT(XRE_IsParentProcess());
173
// We should only be accessing this on the background thread
174
MOZ_ASSERT(!NS_IsMainThread());
175
return gU2FTokenManager;
176
}
177
178
void U2FTokenManager::AbortTransaction(const uint64_t& aTransactionId,
179
const nsresult& aError) {
180
Unused << mTransactionParent->SendAbort(aTransactionId, aError);
181
ClearTransaction();
182
}
183
184
void U2FTokenManager::AbortOngoingTransaction() {
185
if (mLastTransactionId > 0 && mTransactionParent) {
186
// Send an abort to any other ongoing transaction
187
Unused << mTransactionParent->SendAbort(mLastTransactionId,
188
NS_ERROR_DOM_ABORT_ERR);
189
}
190
ClearTransaction();
191
}
192
193
void U2FTokenManager::MaybeClearTransaction(
194
PWebAuthnTransactionParent* aParent) {
195
// Only clear if we've been requested to do so by our current transaction
196
// parent.
197
if (mTransactionParent == aParent) {
198
ClearTransaction();
199
}
200
}
201
202
void U2FTokenManager::ClearTransaction() {
203
if (mLastTransactionId) {
204
// Remove any prompts we might be showing for the current transaction.
205
SendPromptNotification(kCancelPromptNotifcation, mLastTransactionId);
206
}
207
208
mTransactionParent = nullptr;
209
210
// Drop managers at the end of all transactions
211
if (mTokenManagerImpl) {
212
mTokenManagerImpl->Drop();
213
mTokenManagerImpl = nullptr;
214
}
215
216
// Forget promises, if necessary.
217
mRegisterPromise.DisconnectIfExists();
218
mSignPromise.DisconnectIfExists();
219
220
// Clear transaction id.
221
mLastTransactionId = 0;
222
223
// Forget any pending registration.
224
mPendingRegisterInfo.reset();
225
}
226
227
template <typename... T>
228
void U2FTokenManager::SendPromptNotification(const char16_t* aFormat,
229
T... aArgs) {
230
mozilla::ipc::AssertIsOnBackgroundThread();
231
232
nsAutoString json;
233
nsTextFormatter::ssprintf(json, aFormat, aArgs...);
234
235
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<nsString>(
236
"U2FTokenManager::RunSendPromptNotification", this,
237
&U2FTokenManager::RunSendPromptNotification, json));
238
239
MOZ_ALWAYS_SUCCEEDS(
240
GetMainThreadEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
241
}
242
243
void U2FTokenManager::RunSendPromptNotification(nsString aJSON) {
244
MOZ_ASSERT(NS_IsMainThread());
245
246
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
247
if (NS_WARN_IF(!os)) {
248
return;
249
}
250
251
nsCOMPtr<nsIU2FTokenManager> self = this;
252
MOZ_ALWAYS_SUCCEEDS(
253
os->NotifyObservers(self, "webauthn-prompt", aJSON.get()));
254
}
255
256
RefPtr<U2FTokenTransport> U2FTokenManager::GetTokenManagerImpl() {
257
MOZ_ASSERT(U2FPrefManager::Get());
258
mozilla::ipc::AssertIsOnBackgroundThread();
259
260
if (mTokenManagerImpl) {
261
return mTokenManagerImpl;
262
}
263
264
if (!gBackgroundThread) {
265
gBackgroundThread = NS_GetCurrentThread();
266
MOZ_ASSERT(gBackgroundThread, "This should never be null!");
267
}
268
269
auto pm = U2FPrefManager::Get();
270
271
#ifdef MOZ_WIDGET_ANDROID
272
// On Android, prefer the platform support if enabled.
273
if (pm->GetAndroidFido2Enabled()) {
274
return AndroidWebAuthnTokenManager::GetInstance();
275
}
276
#endif
277
278
// Prefer the HW token, even if the softtoken is enabled too.
279
// We currently don't support soft and USB tokens enabled at the
280
// same time as the softtoken would always win the race to register.
281
// We could support it for signing though...
282
if (pm->GetUsbTokenEnabled()) {
283
return new U2FHIDTokenManager();
284
}
285
286
if (pm->GetSoftTokenEnabled()) {
287
return new U2FSoftTokenManager(pm->GetSoftTokenCounter());
288
}
289
290
// TODO Use WebAuthnRequest to aggregate results from all transports,
291
// once we have multiple HW transport types.
292
293
return nullptr;
294
}
295
296
void U2FTokenManager::Register(
297
PWebAuthnTransactionParent* aTransactionParent,
298
const uint64_t& aTransactionId,
299
const WebAuthnMakeCredentialInfo& aTransactionInfo) {
300
MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthRegister"));
301
302
AbortOngoingTransaction();
303
mTransactionParent = aTransactionParent;
304
mTokenManagerImpl = GetTokenManagerImpl();
305
306
if (!mTokenManagerImpl) {
307
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR);
308
return;
309
}
310
311
mLastTransactionId = aTransactionId;
312
313
// Determine whether direct attestation was requested.
314
bool directAttestationRequested = false;
315
316
// On Android, let's always reject direct attestations until we have a
317
// mechanism to solicit user consent, from Bug 1550164
318
#ifndef MOZ_WIDGET_ANDROID
319
if (aTransactionInfo.Extra().isSome()) {
320
const auto& extra = aTransactionInfo.Extra().ref();
321
322
AttestationConveyancePreference attestation =
323
extra.attestationConveyancePreference();
324
325
directAttestationRequested =
326
attestation == AttestationConveyancePreference::Direct;
327
}
328
#endif // not MOZ_WIDGET_ANDROID
329
330
// Start a register request immediately if direct attestation
331
// wasn't requested or the test pref is set.
332
if (!directAttestationRequested ||
333
U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
334
// Force "none" attestation when "direct" attestation wasn't requested.
335
DoRegister(aTransactionInfo, !directAttestationRequested);
336
return;
337
}
338
339
// If the RP request direct attestation, ask the user for permission and
340
// store the transaction info until the user proceeds or cancels.
341
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
342
SendPromptNotification(kRegisterDirectPromptNotifcation, aTransactionId,
343
origin.get());
344
345
MOZ_ASSERT(mPendingRegisterInfo.isNothing());
346
mPendingRegisterInfo = Some(aTransactionInfo);
347
}
348
349
void U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
350
bool aForceNoneAttestation) {
351
mozilla::ipc::AssertIsOnBackgroundThread();
352
MOZ_ASSERT(mLastTransactionId > 0);
353
354
// Show a prompt that lets the user cancel the ongoing transaction.
355
NS_ConvertUTF16toUTF8 origin(aInfo.Origin());
356
SendPromptNotification(kRegisterPromptNotifcation, mLastTransactionId,
357
origin.get());
358
359
uint64_t tid = mLastTransactionId;
360
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
361
362
mTokenManagerImpl->Register(aInfo, aForceNoneAttestation)
363
->Then(
364
GetCurrentThreadSerialEventTarget(), __func__,
365
[tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
366
U2FTokenManager* mgr = U2FTokenManager::Get();
367
mgr->MaybeConfirmRegister(tid, aResult);
368
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
369
NS_LITERAL_STRING("U2FRegisterFinish"), 1);
370
Telemetry::AccumulateTimeDelta(
371
Telemetry::WEBAUTHN_CREATE_CREDENTIAL_MS, startTime);
372
},
373
[tid](nsresult rv) {
374
MOZ_ASSERT(NS_FAILED(rv));
375
U2FTokenManager* mgr = U2FTokenManager::Get();
376
mgr->MaybeAbortRegister(tid, rv);
377
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
378
NS_LITERAL_STRING("U2FRegisterAbort"), 1);
379
})
380
->Track(mRegisterPromise);
381
}
382
383
void U2FTokenManager::MaybeConfirmRegister(
384
const uint64_t& aTransactionId,
385
const WebAuthnMakeCredentialResult& aResult) {
386
MOZ_ASSERT(mLastTransactionId == aTransactionId);
387
mRegisterPromise.Complete();
388
389
Unused << mTransactionParent->SendConfirmRegister(aTransactionId, aResult);
390
ClearTransaction();
391
}
392
393
void U2FTokenManager::MaybeAbortRegister(const uint64_t& aTransactionId,
394
const nsresult& aError) {
395
MOZ_ASSERT(mLastTransactionId == aTransactionId);
396
mRegisterPromise.Complete();
397
AbortTransaction(aTransactionId, aError);
398
}
399
400
void U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
401
const uint64_t& aTransactionId,
402
const WebAuthnGetAssertionInfo& aTransactionInfo) {
403
MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthSign"));
404
405
AbortOngoingTransaction();
406
mTransactionParent = aTransactionParent;
407
mTokenManagerImpl = GetTokenManagerImpl();
408
409
if (!mTokenManagerImpl) {
410
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR);
411
return;
412
}
413
414
// Show a prompt that lets the user cancel the ongoing transaction.
415
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
416
SendPromptNotification(kSignPromptNotifcation, aTransactionId, origin.get());
417
418
uint64_t tid = mLastTransactionId = aTransactionId;
419
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
420
421
mTokenManagerImpl->Sign(aTransactionInfo)
422
->Then(
423
GetCurrentThreadSerialEventTarget(), __func__,
424
[tid, startTime](WebAuthnGetAssertionResult&& aResult) {
425
U2FTokenManager* mgr = U2FTokenManager::Get();
426
mgr->MaybeConfirmSign(tid, aResult);
427
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
428
NS_LITERAL_STRING("U2FSignFinish"), 1);
429
Telemetry::AccumulateTimeDelta(Telemetry::WEBAUTHN_GET_ASSERTION_MS,
430
startTime);
431
},
432
[tid](nsresult rv) {
433
MOZ_ASSERT(NS_FAILED(rv));
434
U2FTokenManager* mgr = U2FTokenManager::Get();
435
mgr->MaybeAbortSign(tid, rv);
436
Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
437
NS_LITERAL_STRING("U2FSignAbort"), 1);
438
})
439
->Track(mSignPromise);
440
}
441
442
void U2FTokenManager::MaybeConfirmSign(
443
const uint64_t& aTransactionId, const WebAuthnGetAssertionResult& aResult) {
444
MOZ_ASSERT(mLastTransactionId == aTransactionId);
445
mSignPromise.Complete();
446
447
Unused << mTransactionParent->SendConfirmSign(aTransactionId, aResult);
448
ClearTransaction();
449
}
450
451
void U2FTokenManager::MaybeAbortSign(const uint64_t& aTransactionId,
452
const nsresult& aError) {
453
MOZ_ASSERT(mLastTransactionId == aTransactionId);
454
mSignPromise.Complete();
455
AbortTransaction(aTransactionId, aError);
456
}
457
458
void U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent,
459
const uint64_t& aTransactionId) {
460
if (mTransactionParent != aParent || mLastTransactionId != aTransactionId) {
461
return;
462
}
463
464
mTokenManagerImpl->Cancel();
465
ClearTransaction();
466
}
467
468
// nsIU2FTokenManager
469
470
NS_IMETHODIMP
471
U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
472
bool aForceNoneAttestation) {
473
MOZ_ASSERT(XRE_IsParentProcess());
474
MOZ_ASSERT(NS_IsMainThread());
475
476
if (!gBackgroundThread) {
477
return NS_ERROR_FAILURE;
478
}
479
480
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>(
481
"U2FTokenManager::RunResumeRegister", this,
482
&U2FTokenManager::RunResumeRegister, aTransactionId,
483
aForceNoneAttestation));
484
485
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
486
}
487
488
void U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
489
bool aForceNoneAttestation) {
490
mozilla::ipc::AssertIsOnBackgroundThread();
491
492
if (NS_WARN_IF(mPendingRegisterInfo.isNothing())) {
493
return;
494
}
495
496
if (mLastTransactionId != aTransactionId) {
497
return;
498
}
499
500
// Resume registration and cleanup.
501
DoRegister(mPendingRegisterInfo.ref(), aForceNoneAttestation);
502
mPendingRegisterInfo.reset();
503
}
504
505
NS_IMETHODIMP
506
U2FTokenManager::Cancel(uint64_t aTransactionId) {
507
MOZ_ASSERT(XRE_IsParentProcess());
508
MOZ_ASSERT(NS_IsMainThread());
509
510
if (!gBackgroundThread) {
511
return NS_ERROR_FAILURE;
512
}
513
514
nsCOMPtr<nsIRunnable> r(
515
NewRunnableMethod<uint64_t>("U2FTokenManager::RunCancel", this,
516
&U2FTokenManager::RunCancel, aTransactionId));
517
518
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
519
}
520
521
void U2FTokenManager::RunCancel(uint64_t aTransactionId) {
522
mozilla::ipc::AssertIsOnBackgroundThread();
523
524
if (mLastTransactionId != aTransactionId) {
525
return;
526
}
527
528
// Cancel the request.
529
mTokenManagerImpl->Cancel();
530
531
// Reject the promise.
532
AbortTransaction(aTransactionId, NS_ERROR_DOM_ABORT_ERR);
533
}
534
535
} // namespace dom
536
} // namespace mozilla