Coverage Report

Created: 2018-09-25 14:53

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