Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/webauthn/U2FHIDTokenManager.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/U2FHIDTokenManager.h"
8
#include "mozilla/dom/WebAuthnUtil.h"
9
#include "mozilla/ipc/BackgroundParent.h"
10
#include "mozilla/StaticMutex.h"
11
12
namespace mozilla {
13
namespace dom {
14
15
static StaticMutex gInstanceMutex;
16
static U2FHIDTokenManager* gInstance;
17
static nsIThread* gPBackgroundThread;
18
19
static void
20
u2f_register_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
21
0
{
22
0
  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
23
0
24
0
  StaticMutexAutoLock lock(gInstanceMutex);
25
0
  if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
26
0
    return;
27
0
  }
28
0
29
0
  nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
30
0
      "U2FHIDTokenManager::HandleRegisterResult", gInstance,
31
0
      &U2FHIDTokenManager::HandleRegisterResult, std::move(rv)));
32
0
33
0
  MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
34
0
                                                   NS_DISPATCH_NORMAL));
35
0
}
36
37
static void
38
u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
39
0
{
40
0
  UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
41
0
42
0
  StaticMutexAutoLock lock(gInstanceMutex);
43
0
  if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
44
0
    return;
45
0
  }
46
0
47
0
  nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<U2FResult>&&>(
48
0
      "U2FHIDTokenManager::HandleSignResult", gInstance,
49
0
      &U2FHIDTokenManager::HandleSignResult, std::move(rv)));
50
0
51
0
  MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
52
0
                                                   NS_DISPATCH_NORMAL));
53
0
}
54
55
U2FHIDTokenManager::U2FHIDTokenManager()
56
0
{
57
0
  StaticMutexAutoLock lock(gInstanceMutex);
58
0
  mozilla::ipc::AssertIsOnBackgroundThread();
59
0
  MOZ_ASSERT(XRE_IsParentProcess());
60
0
  MOZ_ASSERT(!gInstance);
61
0
62
0
  mU2FManager = rust_u2f_mgr_new();
63
0
  gPBackgroundThread = NS_GetCurrentThread();
64
0
  MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
65
0
  gInstance = this;
66
0
}
67
68
void
69
U2FHIDTokenManager::Drop()
70
0
{
71
0
  {
72
0
    StaticMutexAutoLock lock(gInstanceMutex);
73
0
    mozilla::ipc::AssertIsOnBackgroundThread();
74
0
75
0
    mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
76
0
    mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
77
0
78
0
    gInstance = nullptr;
79
0
  }
80
0
81
0
  // Release gInstanceMutex before we call U2FManager::drop(). It will wait
82
0
  // for the work queue thread to join, and that requires the
83
0
  // u2f_{register,sign}_callback to lock and return.
84
0
  rust_u2f_mgr_free(mU2FManager);
85
0
  mU2FManager = nullptr;
86
0
87
0
  // Reset transaction ID so that queued runnables exit early.
88
0
  mTransaction.reset();
89
0
}
90
91
// A U2F Register operation causes a new key pair to be generated by the token.
92
// The token then returns the public key of the key pair, and a handle to the
93
// private key, which is a fancy way of saying "key wrapped private key", as
94
// well as the generated attestation certificate and a signature using that
95
// certificate's private key.
96
//
97
// The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
98
// the actual key wrap/unwrap operations.
99
//
100
// The format of the return registration data is as follows:
101
//
102
// Bytes  Value
103
// 1      0x05
104
// 65     public key
105
// 1      key handle length
106
// *      key handle
107
// ASN.1  attestation certificate
108
// *      attestation signature
109
//
110
RefPtr<U2FRegisterPromise>
111
U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo,
112
                             bool aForceNoneAttestation)
113
0
{
114
0
  mozilla::ipc::AssertIsOnBackgroundThread();
115
0
116
0
  uint64_t registerFlags = 0;
117
0
118
0
  if (aInfo.Extra().type() != WebAuthnMaybeMakeCredentialExtraInfo::Tnull_t) {
119
0
    const auto& extra = aInfo.Extra().get_WebAuthnMakeCredentialExtraInfo();
120
0
    const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
121
0
122
0
    // Set flags for credential creation.
123
0
    if (sel.requireResidentKey()) {
124
0
      registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY;
125
0
    }
126
0
    if (sel.requireUserVerification()) {
127
0
      registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
128
0
    }
129
0
    if (sel.requirePlatformAttachment()) {
130
0
      registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
131
0
    }
132
0
  }
133
0
134
0
  CryptoBuffer rpIdHash, clientDataHash;
135
0
  NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
136
0
  nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
137
0
                                       rpIdHash, clientDataHash);
138
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
139
0
    return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
140
0
  }
141
0
142
0
  ClearPromises();
143
0
  mTransaction.reset();
144
0
  uint64_t tid = rust_u2f_mgr_register(mU2FManager,
145
0
                                       registerFlags,
146
0
                                       (uint64_t)aInfo.TimeoutMS(),
147
0
                                       u2f_register_callback,
148
0
                                       clientDataHash.Elements(),
149
0
                                       clientDataHash.Length(),
150
0
                                       rpIdHash.Elements(),
151
0
                                       rpIdHash.Length(),
152
0
                                       U2FKeyHandles(aInfo.ExcludeList()).Get());
153
0
154
0
  if (tid == 0) {
155
0
    return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
156
0
  }
157
0
158
0
  mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON(),
159
0
                                  aForceNoneAttestation));
160
0
161
0
  return mRegisterPromise.Ensure(__func__);
162
0
}
163
164
// A U2F Sign operation creates a signature over the "param" arguments (plus
165
// some other stuff) using the private key indicated in the key handle argument.
166
//
167
// The format of the signed data is as follows:
168
//
169
//  32    Application parameter
170
//  1     User presence (0x01)
171
//  4     Counter
172
//  32    Challenge parameter
173
//
174
// The format of the signature data is as follows:
175
//
176
//  1     User presence
177
//  4     Counter
178
//  *     Signature
179
//
180
RefPtr<U2FSignPromise>
181
U2FHIDTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
182
0
{
183
0
  mozilla::ipc::AssertIsOnBackgroundThread();
184
0
185
0
  CryptoBuffer rpIdHash, clientDataHash;
186
0
  NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
187
0
  nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
188
0
                                       rpIdHash, clientDataHash);
189
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
190
0
    return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
191
0
  }
192
0
193
0
  uint64_t signFlags = 0;
194
0
  nsTArray<nsTArray<uint8_t>> appIds;
195
0
  appIds.AppendElement(rpIdHash);
196
0
197
0
  if (aInfo.Extra().type() != WebAuthnMaybeGetAssertionExtraInfo::Tnull_t) {
198
0
    const auto& extra = aInfo.Extra().get_WebAuthnGetAssertionExtraInfo();
199
0
200
0
    // Set flags for credential requests.
201
0
    if (extra.RequireUserVerification()) {
202
0
      signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
203
0
    }
204
0
205
0
    // Process extensions.
206
0
    for (const WebAuthnExtension& ext: extra.Extensions()) {
207
0
      if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
208
0
        appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
209
0
      }
210
0
    }
211
0
  }
212
0
213
0
  ClearPromises();
214
0
  mTransaction.reset();
215
0
  uint64_t tid = rust_u2f_mgr_sign(mU2FManager,
216
0
                                   signFlags,
217
0
                                   (uint64_t)aInfo.TimeoutMS(),
218
0
                                   u2f_sign_callback,
219
0
                                   clientDataHash.Elements(),
220
0
                                   clientDataHash.Length(),
221
0
                                   U2FAppIds(appIds).Get(),
222
0
                                   U2FKeyHandles(aInfo.AllowList()).Get());
223
0
  if (tid == 0) {
224
0
    return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
225
0
  }
226
0
227
0
  mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON()));
228
0
229
0
  return mSignPromise.Ensure(__func__);
230
0
}
231
232
void
233
U2FHIDTokenManager::Cancel()
234
0
{
235
0
  mozilla::ipc::AssertIsOnBackgroundThread();
236
0
237
0
  ClearPromises();
238
0
  rust_u2f_mgr_cancel(mU2FManager);
239
0
  mTransaction.reset();
240
0
}
241
242
void
243
U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult)
244
0
{
245
0
  mozilla::ipc::AssertIsOnBackgroundThread();
246
0
247
0
  if (mTransaction.isNothing() ||
248
0
      aResult->GetTransactionId() != mTransaction.ref().mId) {
249
0
    return;
250
0
  }
251
0
252
0
  MOZ_ASSERT(!mRegisterPromise.IsEmpty());
253
0
254
0
  if (aResult->IsError()) {
255
0
    mRegisterPromise.Reject(aResult->GetError(), __func__);
256
0
    return;
257
0
  }
258
0
259
0
  nsTArray<uint8_t> registration;
260
0
  if (!aResult->CopyRegistration(registration)) {
261
0
    mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
262
0
    return;
263
0
  }
264
0
265
0
  // Decompose the U2F registration packet
266
0
  CryptoBuffer pubKeyBuf;
267
0
  CryptoBuffer keyHandle;
268
0
  CryptoBuffer attestationCertBuf;
269
0
  CryptoBuffer signatureBuf;
270
0
271
0
  CryptoBuffer regData;
272
0
  regData.Assign(registration);
273
0
274
0
  // Only handles attestation cert chains of length=1.
275
0
  nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandle,
276
0
                                                 attestationCertBuf, signatureBuf);
277
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
278
0
    mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
279
0
    return;
280
0
  }
281
0
282
0
  CryptoBuffer rpIdHashBuf;
283
0
  if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
284
0
    mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
285
0
    return;
286
0
  }
287
0
288
0
  CryptoBuffer attObj;
289
0
  rv = AssembleAttestationObject(rpIdHashBuf, pubKeyBuf, keyHandle,
290
0
                                 attestationCertBuf, signatureBuf,
291
0
                                 mTransaction.ref().mForceNoneAttestation,
292
0
                                 attObj);
293
0
  if (NS_FAILED(rv)) {
294
0
    mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
295
0
    return;
296
0
  }
297
0
298
0
  WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
299
0
                                      attObj, keyHandle, regData);
300
0
  mRegisterPromise.Resolve(std::move(result), __func__);
301
0
}
302
303
void
304
U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
305
0
{
306
0
  mozilla::ipc::AssertIsOnBackgroundThread();
307
0
308
0
  if (mTransaction.isNothing() ||
309
0
      aResult->GetTransactionId() != mTransaction.ref().mId) {
310
0
    return;
311
0
  }
312
0
313
0
  MOZ_ASSERT(!mSignPromise.IsEmpty());
314
0
315
0
  if (aResult->IsError()) {
316
0
    mSignPromise.Reject(aResult->GetError(), __func__);
317
0
    return;
318
0
  }
319
0
320
0
  nsTArray<uint8_t> appId;
321
0
  if (!aResult->CopyAppId(appId)) {
322
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
323
0
    return;
324
0
  }
325
0
326
0
  nsTArray<uint8_t> keyHandle;
327
0
  if (!aResult->CopyKeyHandle(keyHandle)) {
328
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
329
0
    return;
330
0
  }
331
0
332
0
  nsTArray<uint8_t> signature;
333
0
  if (!aResult->CopySignature(signature)) {
334
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
335
0
    return;
336
0
  }
337
0
338
0
  CryptoBuffer rawSignatureBuf;
339
0
  if (!rawSignatureBuf.Assign(signature)) {
340
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
341
0
    return;
342
0
  }
343
0
344
0
  nsTArray<WebAuthnExtensionResult> extensions;
345
0
346
0
  if (appId != mTransaction.ref().mRpIdHash) {
347
0
    // Indicate to the RP that we used the FIDO appId.
348
0
    extensions.AppendElement(WebAuthnExtensionResultAppId(true));
349
0
  }
350
0
351
0
  CryptoBuffer signatureBuf;
352
0
  CryptoBuffer counterBuf;
353
0
  uint8_t flags = 0;
354
0
  nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
355
0
                                         signatureBuf);
356
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
357
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
358
0
    return;
359
0
  }
360
0
361
0
  CryptoBuffer chosenAppIdBuf;
362
0
  if (!chosenAppIdBuf.Assign(appId)) {
363
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
364
0
    return;
365
0
  }
366
0
367
0
  // Preserve the two LSBs of the flags byte, UP and RFU1.
368
0
  // See <https://github.com/fido-alliance/fido-2-specs/pull/519>
369
0
  flags &= 0b11;
370
0
371
0
  CryptoBuffer emptyAttestationData;
372
0
  CryptoBuffer authenticatorData;
373
0
  rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
374
0
                                 emptyAttestationData, authenticatorData);
375
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
376
0
    mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
377
0
    return;
378
0
  }
379
0
380
0
  WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
381
0
                                    keyHandle, signatureBuf, authenticatorData,
382
0
                                    extensions, rawSignatureBuf);
383
0
  mSignPromise.Resolve(std::move(result), __func__);
384
0
}
385
386
}
387
}