summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/CTAPHIDTokenManager.h
blob: 41279335926aa9d85837284fefaa9def30f3d601 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_CTAPHIDTokenManager_h
#define mozilla_dom_CTAPHIDTokenManager_h

#include "mozilla/dom/U2FTokenTransport.h"
#include "authenticator/src/u2fhid-capi.h"
#include "authenticator/src/ctap2-capi.h"

/*
 * CTAPHIDTokenManager is a Rust implementation of a secure token manager
 * for the CTAP2, U2F and WebAuthn APIs, talking to HIDs.
 */

namespace mozilla::dom {

class Ctap2PubKeyCredentialDescriptor {
 public:
  explicit Ctap2PubKeyCredentialDescriptor(
      const nsTArray<WebAuthnScopedCredential>& aCredentials) {
    cred_descriptors = rust_ctap2_pkcd_new();

    for (auto& cred : aCredentials) {
      rust_ctap2_pkcd_add(cred_descriptors, cred.id().Elements(),
                          cred.id().Length(), cred.transports());
    }
  }

  rust_ctap2_pub_key_cred_descriptors* Get() { return cred_descriptors; }

  ~Ctap2PubKeyCredentialDescriptor() { rust_ctap2_pkcd_free(cred_descriptors); }

 private:
  rust_ctap2_pub_key_cred_descriptors* cred_descriptors;
};

class CTAPResult {
 public:
  explicit CTAPResult(uint64_t aTransactionId, rust_u2f_result* aResult)
      : mTransactionId(aTransactionId), mU2FResult(aResult) {
    MOZ_ASSERT(mU2FResult);
  }

  explicit CTAPResult(uint64_t aTransactionId,
                      rust_ctap2_register_result* aResult)
      : mTransactionId(aTransactionId), mRegisterResult(aResult) {
    MOZ_ASSERT(mRegisterResult);
  }

  explicit CTAPResult(uint64_t aTransactionId, rust_ctap2_sign_result* aResult)
      : mTransactionId(aTransactionId), mSignResult(aResult) {
    MOZ_ASSERT(mSignResult);
  }

  ~CTAPResult() {
    // Rust-API can handle possible NULL-pointers
    rust_u2f_res_free(mU2FResult);
    rust_ctap2_register_res_free(mRegisterResult);
    rust_ctap2_sign_res_free(mSignResult);
  }

  uint64_t GetTransactionId() { return mTransactionId; }

  bool IsError() { return NS_FAILED(GetError()); }

  nsresult GetError() {
    uint8_t res;
    if (mU2FResult) {
      res = rust_u2f_result_error(mU2FResult);
    } else if (mRegisterResult) {
      res = rust_ctap2_register_result_error(mRegisterResult);
    } else if (mSignResult) {
      res = rust_ctap2_sign_result_error(mSignResult);
    } else {
      return NS_ERROR_FAILURE;
    }

    switch (res) {
      case U2F_OK:
        return NS_OK;
      case U2F_ERROR_UKNOWN:
      case U2F_ERROR_CONSTRAINT:
        return NS_ERROR_DOM_UNKNOWN_ERR;
      case U2F_ERROR_NOT_SUPPORTED:
        return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
      case U2F_ERROR_INVALID_STATE:
        return NS_ERROR_DOM_INVALID_STATE_ERR;
      case U2F_ERROR_NOT_ALLOWED:
        return NS_ERROR_DOM_NOT_ALLOWED_ERR;
      case CTAP_ERROR_PIN_REQUIRED:
      case CTAP_ERROR_PIN_INVALID:
      case CTAP_ERROR_PIN_AUTH_BLOCKED:
      case CTAP_ERROR_PIN_BLOCKED:
        // This is not perfect, but we are reusing an existing error-code here.
        // We need to differentiate only PIN-errors from non-PIN errors
        // to know if the Popup-Dialog should be removed or stay
        // after the operation errors out. We don't want to define
        // new NS-errors at the moment, since it's all internal anyways.
        return NS_ERROR_DOM_OPERATION_ERR;
      default:
        // Generic error
        return NS_ERROR_FAILURE;
    }
  }

  bool CopyRegistration(nsTArray<uint8_t>& aBuffer) {
    return CopyBuffer(U2F_RESBUF_ID_REGISTRATION, aBuffer);
  }

  bool CopyKeyHandle(nsTArray<uint8_t>& aBuffer) {
    return CopyBuffer(U2F_RESBUF_ID_KEYHANDLE, aBuffer);
  }

  bool CopySignature(nsTArray<uint8_t>& aBuffer) {
    return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
  }

  bool CopyAppId(nsTArray<uint8_t>& aBuffer) {
    return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
  }

  bool CopyClientDataStr(nsCString& aBuffer) {
    if (mU2FResult) {
      return false;
    } else if (mRegisterResult) {
      size_t len;
      if (!rust_ctap2_register_result_client_data_len(mRegisterResult, &len)) {
        return false;
      }

      if (!aBuffer.SetLength(len, fallible)) {
        return false;
      }

      return rust_ctap2_register_result_client_data_copy(mRegisterResult,
                                                         aBuffer.Data());
    } else if (mSignResult) {
      size_t len;
      if (!rust_ctap2_sign_result_client_data_len(mSignResult, &len)) {
        return false;
      }

      if (!aBuffer.SetLength(len, fallible)) {
        return false;
      }

      return rust_ctap2_sign_result_client_data_copy(mSignResult,
                                                     aBuffer.Data());
    } else {
      return false;
    }
  }

  bool IsCtap2() {
    // If it's not an U2F result, we already know its CTAP2
    return !mU2FResult;
  }

  bool HasAppId() { return Contains(U2F_RESBUF_ID_APPID); }

  bool HasKeyHandle() { return Contains(U2F_RESBUF_ID_KEYHANDLE); }

  bool Ctap2GetNumberOfSignAssertions(size_t& len) {
    return rust_ctap2_sign_result_assertions_len(mSignResult, &len);
  }

  bool Ctap2CopyAttestation(nsTArray<uint8_t>& aBuffer) {
    if (!mRegisterResult) {
      return false;
    }

    size_t len;
    if (!rust_ctap2_register_result_attestation_len(mRegisterResult, &len)) {
      return false;
    }

    if (!aBuffer.SetLength(len, fallible)) {
      return false;
    }

    return rust_ctap2_register_result_attestation_copy(mRegisterResult,
                                                       aBuffer.Elements());
  }

  bool Ctap2CopyPubKeyCredential(nsTArray<uint8_t>& aBuffer, size_t index) {
    return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_PUBKEY_CRED_ID,
                                  aBuffer);
  }

  bool Ctap2CopySignature(nsTArray<uint8_t>& aBuffer, size_t index) {
    return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_SIGNATURE, aBuffer);
  }

  bool Ctap2CopyUserId(nsTArray<uint8_t>& aBuffer, size_t index) {
    return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_USER_ID, aBuffer);
  }

  bool Ctap2CopyAuthData(nsTArray<uint8_t>& aBuffer, size_t index) {
    return Ctap2SignResCopyBuffer(index, CTAP2_SIGN_RESULT_AUTH_DATA, aBuffer);
  }

  bool Ctap2HasPubKeyCredential(size_t index) {
    return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_PUBKEY_CRED_ID);
  }

  bool HasUserId(size_t index) {
    return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_USER_ID);
  }

  bool HasUserName(size_t index) {
    return Ctap2SignResContains(index, CTAP2_SIGN_RESULT_USER_NAME);
  }

  bool CopyUserName(nsCString& aBuffer, size_t index) {
    if (!mSignResult) {
      return false;
    }

    size_t len;
    if (!rust_ctap2_sign_result_username_len(mSignResult, index, &len)) {
      return false;
    }

    if (!aBuffer.SetLength(len, fallible)) {
      return false;
    }

    return rust_ctap2_sign_result_username_copy(mSignResult, index,
                                                aBuffer.Data());
  }

 private:
  bool Contains(uint8_t aResBufId) {
    if (mU2FResult) {
      return rust_u2f_resbuf_contains(mU2FResult, aResBufId);
    }
    return false;
  }
  bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
    if (!mU2FResult) {
      return false;
    }

    size_t len;
    if (!rust_u2f_resbuf_length(mU2FResult, aResBufID, &len)) {
      return false;
    }

    if (!aBuffer.SetLength(len, fallible)) {
      return false;
    }

    return rust_u2f_resbuf_copy(mU2FResult, aResBufID, aBuffer.Elements());
  }

  bool Ctap2SignResContains(size_t assertion_idx, uint8_t item_idx) {
    if (mSignResult) {
      return rust_ctap2_sign_result_item_contains(mSignResult, assertion_idx,
                                                  item_idx);
    }
    return false;
  }
  bool Ctap2SignResCopyBuffer(size_t assertion_idx, uint8_t item_idx,
                              nsTArray<uint8_t>& aBuffer) {
    if (!mSignResult) {
      return false;
    }

    size_t len;
    if (!rust_ctap2_sign_result_item_len(mSignResult, assertion_idx, item_idx,
                                         &len)) {
      return false;
    }

    if (!aBuffer.SetLength(len, fallible)) {
      return false;
    }

    return rust_ctap2_sign_result_item_copy(mSignResult, assertion_idx,
                                            item_idx, aBuffer.Elements());
  }

  uint64_t mTransactionId;
  rust_u2f_result* mU2FResult = nullptr;
  rust_ctap2_register_result* mRegisterResult = nullptr;
  rust_ctap2_sign_result* mSignResult = nullptr;
};

class CTAPHIDTokenManager final : public U2FTokenTransport {
 public:
  explicit CTAPHIDTokenManager();

  // TODO(MS): Once we completely switch over to CTAPHIDTokenManager and remove
  // the old U2F version, this needs to be renamed to CTAPRegisterPromise. Same
  // for Sign.
  virtual RefPtr<U2FRegisterPromise> Register(
      const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation,
      void status_callback(rust_ctap2_status_update_res*)) override;

  virtual RefPtr<U2FSignPromise> Sign(
      const WebAuthnGetAssertionInfo& aInfo,
      void status_callback(rust_ctap2_status_update_res*)) override;

  void Cancel() override;
  void Drop() override;

  void HandleRegisterResult(UniquePtr<CTAPResult>&& aResult);
  void HandleSignResult(UniquePtr<CTAPResult>&& aResult);

 private:
  ~CTAPHIDTokenManager() = default;

  void HandleRegisterResultCtap1(UniquePtr<CTAPResult>&& aResult);
  void HandleRegisterResultCtap2(UniquePtr<CTAPResult>&& aResult);
  void HandleSignResultCtap1(UniquePtr<CTAPResult>&& aResult);
  void HandleSignResultCtap2(UniquePtr<CTAPResult>&& aResult);
  mozilla::Maybe<WebAuthnGetAssertionResultWrapper>
  HandleSelectedSignResultCtap2(UniquePtr<CTAPResult>&& aResult, size_t index);
  void ClearPromises() {
    mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
    mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
  }

  class Transaction {
   public:
    Transaction(uint64_t aId, const nsTArray<uint8_t>& aRpIdHash,
                const Maybe<nsTArray<uint8_t>>& aAppIdHash,
                const nsCString& aClientDataJSON,
                bool aForceNoneAttestation = false)
        : mId(aId),
          mRpIdHash(aRpIdHash.Clone()),
          mClientDataJSON(aClientDataJSON),
          mForceNoneAttestation(aForceNoneAttestation) {
      if (aAppIdHash) {
        mAppIdHash = Some(aAppIdHash->Clone());
      } else {
        mAppIdHash = Nothing();
      }
    }

    // The transaction ID.
    uint64_t mId;

    // The RP ID hash.
    nsTArray<uint8_t> mRpIdHash;

    // The App ID hash, if the AppID extension was set
    Maybe<nsTArray<uint8_t>> mAppIdHash;

    // The clientData JSON.
    nsCString mClientDataJSON;

    // Whether we'll force "none" attestation.
    bool mForceNoneAttestation;
  };

  rust_ctap_manager* mCTAPManager;
  Maybe<Transaction> mTransaction;
  MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
  MozPromiseHolder<U2FSignPromise> mSignPromise;
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_CTAPHIDTokenManager_h