Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
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
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "hasht.h"
8 : #include "mozilla/dom/CallbackFunction.h"
9 : #include "mozilla/dom/ContentChild.h"
10 : #include "mozilla/dom/CryptoBuffer.h"
11 : #include "mozilla/dom/NSSU2FTokenRemote.h"
12 : #include "mozilla/dom/U2F.h"
13 : #include "mozilla/Preferences.h"
14 : #include "mozilla/ReentrantMonitor.h"
15 : #include "mozilla/SizePrintfMacros.h"
16 : #include "nsContentUtils.h"
17 : #include "nsINSSU2FToken.h"
18 : #include "nsNetCID.h"
19 : #include "nsNSSComponent.h"
20 : #include "nsThreadUtils.h"
21 : #include "nsURLParsers.h"
22 : #include "nsXPCOMCIDInternal.h"
23 : #include "pk11pub.h"
24 :
25 : using mozilla::dom::ContentChild;
26 :
27 : namespace mozilla {
28 : namespace dom {
29 :
30 : #define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
31 : #define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f_enable_usbtoken"
32 :
33 3 : NS_NAMED_LITERAL_CSTRING(kPoolName, "WebAuth_U2F-IO");
34 3 : NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
35 3 : NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
36 :
37 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
38 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
39 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
40 0 : NS_INTERFACE_MAP_END
41 :
42 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
43 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
44 :
45 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
46 :
47 : static mozilla::LazyLogModule gU2FLog("u2f");
48 :
49 : static nsresult
50 0 : AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
51 : const nsAString& aChallenge, CryptoBuffer& aClientData)
52 : {
53 0 : MOZ_ASSERT(NS_IsMainThread());
54 0 : U2FClientData clientDataObject;
55 0 : clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
56 0 : clientDataObject.mChallenge.Construct(aChallenge);
57 0 : clientDataObject.mOrigin.Construct(aOrigin);
58 :
59 0 : nsAutoString json;
60 0 : if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
61 0 : return NS_ERROR_FAILURE;
62 : }
63 :
64 0 : if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
65 0 : return NS_ERROR_OUT_OF_MEMORY;
66 : }
67 :
68 0 : return NS_OK;
69 : }
70 :
71 0 : U2FStatus::U2FStatus()
72 : : mCount(0)
73 : , mIsStopped(false)
74 0 : , mReentrantMonitor("U2FStatus")
75 0 : {}
76 :
77 0 : U2FStatus::~U2FStatus()
78 0 : {}
79 :
80 : void
81 0 : U2FStatus::WaitGroupAdd()
82 : {
83 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
84 :
85 0 : mCount += 1;
86 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
87 : ("U2FStatus::WaitGroupAdd, now %d", mCount));
88 0 : }
89 :
90 : void
91 0 : U2FStatus::WaitGroupDone()
92 : {
93 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
94 :
95 0 : MOZ_ASSERT(mCount > 0);
96 0 : mCount -= 1;
97 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
98 : ("U2FStatus::WaitGroupDone, now %d", mCount));
99 0 : if (mCount == 0) {
100 0 : mReentrantMonitor.NotifyAll();
101 : }
102 0 : }
103 :
104 : void
105 0 : U2FStatus::WaitGroupWait()
106 : {
107 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
108 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
109 : ("U2FStatus::WaitGroupWait, now %d", mCount));
110 :
111 0 : while (mCount > 0) {
112 0 : mReentrantMonitor.Wait();
113 : }
114 :
115 0 : MOZ_ASSERT(mCount == 0);
116 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
117 : ("U2FStatus::Wait completed, now count=%d stopped=%d", mCount,
118 : mIsStopped));
119 0 : }
120 :
121 : void
122 0 : U2FStatus::Stop(const ErrorCode aErrorCode)
123 : {
124 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
125 0 : MOZ_ASSERT(!mIsStopped);
126 0 : mIsStopped = true;
127 0 : mErrorCode = aErrorCode;
128 :
129 : // TODO: Let WaitGroupWait exit early upon a Stop. Requires consideration of
130 : // threads calling IsStopped() followed by WaitGroupDone(). Right now, Stop
131 : // prompts work tasks to end early, but it could also prompt an immediate
132 : // "Go ahead" to the thread waiting at WaitGroupWait.
133 0 : }
134 :
135 : void
136 0 : U2FStatus::Stop(const ErrorCode aErrorCode, const nsAString& aResponse)
137 : {
138 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
139 0 : Stop(aErrorCode);
140 0 : mResponse = aResponse;
141 0 : }
142 :
143 : bool
144 0 : U2FStatus::IsStopped()
145 : {
146 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
147 0 : return mIsStopped;
148 : }
149 :
150 : ErrorCode
151 0 : U2FStatus::GetErrorCode()
152 : {
153 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
154 0 : MOZ_ASSERT(mIsStopped);
155 0 : return mErrorCode;
156 : }
157 :
158 : nsString
159 0 : U2FStatus::GetResponse()
160 : {
161 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
162 0 : MOZ_ASSERT(mIsStopped);
163 0 : return mResponse;
164 : }
165 :
166 0 : U2FTask::U2FTask(const nsAString& aOrigin,
167 : const nsAString& aAppId,
168 : const Authenticator& aAuthenticator,
169 0 : nsISerialEventTarget* aEventTarget)
170 : : Runnable("dom::U2FTask")
171 : , mOrigin(aOrigin)
172 : , mAppId(aAppId)
173 : , mAuthenticator(aAuthenticator)
174 0 : , mEventTarget(aEventTarget)
175 0 : {}
176 :
177 0 : U2FTask::~U2FTask()
178 0 : {}
179 :
180 : RefPtr<U2FPromise>
181 0 : U2FTask::Execute()
182 : {
183 0 : RefPtr<U2FPromise> p = mPromise.Ensure(__func__);
184 :
185 0 : nsCOMPtr<nsIRunnable> r(this);
186 :
187 : // TODO: Use a thread pool here, but we have to solve the PContentChild issues
188 : // of being in a worker thread.
189 0 : mEventTarget->Dispatch(r.forget());
190 0 : return p;
191 : }
192 :
193 0 : U2FPrepTask::U2FPrepTask(const Authenticator& aAuthenticator,
194 0 : nsISerialEventTarget* aEventTarget)
195 : : Runnable("dom::U2FPrepTask")
196 : , mAuthenticator(aAuthenticator)
197 0 : , mEventTarget(aEventTarget)
198 0 : {}
199 :
200 0 : U2FPrepTask::~U2FPrepTask()
201 0 : {}
202 :
203 : RefPtr<U2FPrepPromise>
204 0 : U2FPrepTask::Execute()
205 : {
206 0 : RefPtr<U2FPrepPromise> p = mPromise.Ensure(__func__);
207 :
208 0 : nsCOMPtr<nsIRunnable> r(this);
209 :
210 : // TODO: Use a thread pool here, but we have to solve the PContentChild issues
211 : // of being in a worker thread.
212 0 : mEventTarget->Dispatch(r.forget());
213 0 : return p;
214 : }
215 :
216 0 : U2FIsRegisteredTask::U2FIsRegisteredTask(const Authenticator& aAuthenticator,
217 : const LocalRegisteredKey& aRegisteredKey,
218 : const CryptoBuffer& aAppParam,
219 0 : nsISerialEventTarget* aEventTarget)
220 : : U2FPrepTask(aAuthenticator, aEventTarget)
221 : , mRegisteredKey(aRegisteredKey)
222 0 : , mAppParam(aAppParam)
223 0 : {}
224 :
225 0 : U2FIsRegisteredTask::~U2FIsRegisteredTask()
226 0 : {}
227 :
228 : NS_IMETHODIMP
229 0 : U2FIsRegisteredTask::Run()
230 : {
231 0 : bool isCompatible = false;
232 0 : nsresult rv = mAuthenticator->IsCompatibleVersion(mRegisteredKey.mVersion,
233 0 : &isCompatible);
234 0 : if (NS_FAILED(rv)) {
235 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
236 0 : return NS_ERROR_FAILURE;
237 : }
238 :
239 0 : if (!isCompatible) {
240 0 : mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
241 0 : return NS_ERROR_FAILURE;
242 : }
243 :
244 : // Decode the key handle
245 0 : CryptoBuffer keyHandle;
246 0 : rv = keyHandle.FromJwkBase64(mRegisteredKey.mKeyHandle);
247 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
248 0 : mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
249 0 : return NS_ERROR_FAILURE;
250 : }
251 :
252 : // We ignore mTransports, as it is intended to be used for sorting the
253 : // available devices by preference, but is not an exclusion factor.
254 :
255 0 : bool isRegistered = false;
256 0 : rv = mAuthenticator->IsRegistered(keyHandle.Elements(), keyHandle.Length(),
257 0 : mAppParam.Elements(), mAppParam.Length(),
258 0 : &isRegistered);
259 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
260 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
261 0 : return NS_ERROR_FAILURE;
262 : }
263 :
264 0 : if (isRegistered) {
265 0 : mPromise.Reject(ErrorCode::DEVICE_INELIGIBLE, __func__);
266 0 : return NS_OK;
267 : }
268 :
269 0 : mPromise.Resolve(mAuthenticator, __func__);
270 0 : return NS_OK;
271 : }
272 :
273 0 : U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin,
274 : const nsAString& aAppId,
275 : const Authenticator& aAuthenticator,
276 : const CryptoBuffer& aAppParam,
277 : const CryptoBuffer& aChallengeParam,
278 : const LocalRegisterRequest& aRegisterEntry,
279 0 : nsISerialEventTarget* aEventTarget)
280 : : U2FTask(aOrigin, aAppId, aAuthenticator, aEventTarget)
281 : , mAppParam(aAppParam)
282 : , mChallengeParam(aChallengeParam)
283 0 : , mRegisterEntry(aRegisterEntry)
284 0 : {}
285 :
286 0 : U2FRegisterTask::~U2FRegisterTask()
287 0 : {}
288 :
289 : NS_IMETHODIMP
290 0 : U2FRegisterTask::Run()
291 : {
292 0 : bool isCompatible = false;
293 0 : nsresult rv = mAuthenticator->IsCompatibleVersion(mRegisterEntry.mVersion,
294 0 : &isCompatible);
295 0 : if (NS_FAILED(rv)) {
296 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
297 0 : return NS_ERROR_FAILURE;
298 : }
299 :
300 0 : if (!isCompatible) {
301 0 : mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
302 0 : return NS_ERROR_FAILURE;
303 : }
304 :
305 : uint8_t* buffer;
306 : uint32_t bufferlen;
307 0 : rv = mAuthenticator->Register(mAppParam.Elements(),
308 0 : mAppParam.Length(),
309 : mChallengeParam.Elements(),
310 0 : mChallengeParam.Length(),
311 0 : &buffer, &bufferlen);
312 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
313 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
314 0 : return NS_ERROR_FAILURE;
315 : }
316 :
317 0 : MOZ_ASSERT(buffer);
318 0 : CryptoBuffer regData;
319 0 : if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
320 0 : free(buffer);
321 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
322 0 : return NS_ERROR_OUT_OF_MEMORY;
323 : }
324 0 : free(buffer);
325 :
326 : // Assemble a response object to return
327 0 : nsString clientDataBase64;
328 0 : nsString registrationDataBase64;
329 0 : nsresult rvClientData = mRegisterEntry.mClientData.ToJwkBase64(clientDataBase64);
330 0 : nsresult rvRegistrationData = regData.ToJwkBase64(registrationDataBase64);
331 :
332 0 : if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
333 0 : NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
334 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
335 0 : return NS_ERROR_FAILURE;
336 : }
337 :
338 0 : RegisterResponse response;
339 0 : response.mClientData.Construct(clientDataBase64);
340 0 : response.mRegistrationData.Construct(registrationDataBase64);
341 0 : response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
342 :
343 0 : nsString responseStr;
344 0 : if (NS_WARN_IF(!response.ToJSON(responseStr))) {
345 0 : return NS_ERROR_FAILURE;
346 : }
347 0 : mPromise.Resolve(responseStr, __func__);
348 0 : return NS_OK;
349 : }
350 :
351 0 : U2FSignTask::U2FSignTask(const nsAString& aOrigin,
352 : const nsAString& aAppId,
353 : const nsAString& aVersion,
354 : const Authenticator& aAuthenticator,
355 : const CryptoBuffer& aAppParam,
356 : const CryptoBuffer& aChallengeParam,
357 : const CryptoBuffer& aClientData,
358 : const CryptoBuffer& aKeyHandle,
359 0 : nsISerialEventTarget* aEventTarget)
360 : : U2FTask(aOrigin, aAppId, aAuthenticator, aEventTarget)
361 : , mVersion(aVersion)
362 : , mAppParam(aAppParam)
363 : , mChallengeParam(aChallengeParam)
364 : , mClientData(aClientData)
365 0 : , mKeyHandle(aKeyHandle)
366 0 : {}
367 :
368 0 : U2FSignTask::~U2FSignTask()
369 0 : {}
370 :
371 : NS_IMETHODIMP
372 0 : U2FSignTask::Run()
373 : {
374 0 : bool isCompatible = false;
375 0 : nsresult rv = mAuthenticator->IsCompatibleVersion(mVersion, &isCompatible);
376 0 : if (NS_FAILED(rv)) {
377 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
378 0 : return NS_ERROR_FAILURE;
379 : }
380 :
381 0 : if (!isCompatible) {
382 0 : mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
383 0 : return NS_ERROR_FAILURE;
384 : }
385 :
386 0 : bool isRegistered = false;
387 0 : rv = mAuthenticator->IsRegistered(mKeyHandle.Elements(), mKeyHandle.Length(),
388 0 : mAppParam.Elements(), mAppParam.Length(),
389 0 : &isRegistered);
390 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
391 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
392 0 : return NS_ERROR_FAILURE;
393 : }
394 :
395 0 : if (!isRegistered) {
396 0 : mPromise.Reject(ErrorCode::DEVICE_INELIGIBLE, __func__);
397 0 : return NS_OK;
398 : }
399 :
400 0 : CryptoBuffer signatureData;
401 : uint8_t* buffer;
402 : uint32_t bufferlen;
403 0 : rv = mAuthenticator->Sign(mAppParam.Elements(), mAppParam.Length(),
404 0 : mChallengeParam.Elements(), mChallengeParam.Length(),
405 0 : mKeyHandle.Elements(), mKeyHandle.Length(),
406 0 : &buffer, &bufferlen);
407 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
408 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
409 0 : return NS_ERROR_FAILURE;
410 : }
411 :
412 0 : MOZ_ASSERT(buffer);
413 0 : if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
414 0 : free(buffer);
415 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
416 0 : return NS_ERROR_OUT_OF_MEMORY;
417 : }
418 0 : free(buffer);
419 :
420 : // Assemble a response object to return
421 0 : nsString clientDataBase64;
422 0 : nsString signatureDataBase64;
423 0 : nsString keyHandleBase64;
424 0 : nsresult rvClientData = mClientData.ToJwkBase64(clientDataBase64);
425 0 : nsresult rvSignatureData = signatureData.ToJwkBase64(signatureDataBase64);
426 0 : nsresult rvKeyHandle = mKeyHandle.ToJwkBase64(keyHandleBase64);
427 0 : if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
428 0 : NS_WARN_IF(NS_FAILED(rvSignatureData) ||
429 : NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
430 0 : mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
431 0 : return NS_ERROR_FAILURE;
432 : }
433 :
434 0 : SignResponse response;
435 0 : response.mKeyHandle.Construct(keyHandleBase64);
436 0 : response.mClientData.Construct(clientDataBase64);
437 0 : response.mSignatureData.Construct(signatureDataBase64);
438 0 : response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
439 :
440 0 : nsString responseStr;
441 0 : if (NS_WARN_IF(!response.ToJSON(responseStr))) {
442 0 : return NS_ERROR_FAILURE;
443 : }
444 0 : mPromise.Resolve(responseStr, __func__);
445 0 : return NS_OK;
446 : }
447 :
448 0 : U2FRunnable::U2FRunnable(const nsAString& aOrigin, const nsAString& aAppId,
449 0 : nsISerialEventTarget* aEventTarget)
450 : : Runnable("dom::U2FRunnable")
451 : , mOrigin(aOrigin)
452 : , mAppId(aAppId)
453 0 : , mEventTarget(aEventTarget)
454 0 : {}
455 :
456 0 : U2FRunnable::~U2FRunnable()
457 0 : {}
458 :
459 : // EvaluateAppIDAndRunTask determines whether the supplied FIDO AppID is valid for
460 : // the current FacetID, e.g., the current origin.
461 : // See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
462 : // for a description of the algorithm.
463 : ErrorCode
464 0 : U2FRunnable::EvaluateAppID()
465 : {
466 : nsCOMPtr<nsIURLParser> urlParser =
467 0 : do_GetService(NS_STDURLPARSER_CONTRACTID);
468 :
469 0 : MOZ_ASSERT(urlParser);
470 :
471 : uint32_t facetSchemePos;
472 : int32_t facetSchemeLen;
473 : uint32_t facetAuthPos;
474 : int32_t facetAuthLen;
475 : // Facet is the specification's way of referring to the web origin.
476 0 : nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
477 0 : nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
478 : &facetSchemePos, &facetSchemeLen,
479 : &facetAuthPos, &facetAuthLen,
480 0 : nullptr, nullptr); // ignore path
481 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
482 0 : return ErrorCode::BAD_REQUEST;
483 : }
484 :
485 0 : nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
486 0 : nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
487 :
488 : uint32_t appIdSchemePos;
489 : int32_t appIdSchemeLen;
490 : uint32_t appIdAuthPos;
491 : int32_t appIdAuthLen;
492 : // AppID is user-supplied. It's quite possible for this parse to fail.
493 0 : nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(mAppId);
494 0 : rv = urlParser->ParseURL(appIdUrl.get(), mAppId.Length(),
495 : &appIdSchemePos, &appIdSchemeLen,
496 : &appIdAuthPos, &appIdAuthLen,
497 0 : nullptr, nullptr); // ignore path
498 0 : if (NS_FAILED(rv)) {
499 0 : return ErrorCode::BAD_REQUEST;
500 : }
501 :
502 0 : nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
503 0 : nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
504 :
505 : // If the facetId (origin) is not HTTPS, reject
506 0 : if (!facetScheme.LowerCaseEqualsLiteral("https")) {
507 0 : return ErrorCode::BAD_REQUEST;
508 : }
509 :
510 : // If the appId is empty or null, overwrite it with the facetId and accept
511 0 : if (mAppId.IsEmpty() || mAppId.EqualsLiteral("null")) {
512 0 : mAppId.Assign(mOrigin);
513 0 : return ErrorCode::OK;
514 : }
515 :
516 : // if the appId URL is not HTTPS, reject.
517 0 : if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
518 0 : return ErrorCode::BAD_REQUEST;
519 : }
520 :
521 : // If the facetId and the appId auths match, accept
522 0 : if (facetAuth == appIdAuth) {
523 0 : return ErrorCode::OK;
524 : }
525 :
526 : // TODO(Bug 1244959) Implement the remaining algorithm.
527 0 : return ErrorCode::BAD_REQUEST;
528 : }
529 :
530 0 : U2FRegisterRunnable::U2FRegisterRunnable(const nsAString& aOrigin,
531 : const nsAString& aAppId,
532 : const Sequence<RegisterRequest>& aRegisterRequests,
533 : const Sequence<RegisteredKey>& aRegisteredKeys,
534 : const Sequence<Authenticator>& aAuthenticators,
535 : U2FRegisterCallback* aCallback,
536 0 : nsISerialEventTarget* aEventTarget)
537 : : U2FRunnable(aOrigin, aAppId, aEventTarget)
538 : , mAuthenticators(aAuthenticators)
539 : // U2FRegisterCallback does not support threadsafe refcounting, and must be
540 : // used and destroyed on main.
541 : , mCallback(new nsMainThreadPtrHolder<U2FRegisterCallback>(
542 0 : "U2FRegisterRunnable::mCallback", aCallback))
543 : {
544 0 : MOZ_ASSERT(NS_IsMainThread());
545 :
546 : // The WebIDL dictionary types RegisterRequest and RegisteredKey cannot
547 : // be copied to this thread, so store them serialized.
548 0 : for (const RegisterRequest& req : aRegisterRequests) {
549 : // Check for required attributes
550 0 : if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed()) {
551 0 : continue;
552 : }
553 :
554 0 : LocalRegisterRequest localReq;
555 0 : localReq.mVersion = req.mVersion.Value();
556 0 : localReq.mChallenge = req.mChallenge.Value();
557 :
558 0 : nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
559 0 : localReq.mChallenge, localReq.mClientData);
560 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
561 0 : continue;
562 : }
563 :
564 0 : mRegisterRequests.AppendElement(localReq);
565 : }
566 :
567 0 : for (const RegisteredKey& key : aRegisteredKeys) {
568 : // Check for required attributes
569 0 : if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed()) {
570 0 : continue;
571 : }
572 :
573 0 : LocalRegisteredKey localKey;
574 0 : localKey.mVersion = key.mVersion.Value();
575 0 : localKey.mKeyHandle = key.mKeyHandle.Value();
576 0 : if (key.mAppId.WasPassed()) {
577 0 : localKey.mAppId.SetValue(key.mAppId.Value());
578 : }
579 :
580 0 : mRegisteredKeys.AppendElement(localKey);
581 : }
582 0 : }
583 :
584 0 : U2FRegisterRunnable::~U2FRegisterRunnable()
585 : {
586 0 : nsNSSShutDownPreventionLock locker;
587 :
588 0 : if (isAlreadyShutDown()) {
589 0 : return;
590 : }
591 0 : shutdown(ShutdownCalledFrom::Object);
592 0 : }
593 :
594 : void
595 0 : U2FRegisterRunnable::SetTimeout(const int32_t aTimeoutMillis)
596 : {
597 0 : opt_mTimeoutSeconds.SetValue(aTimeoutMillis);
598 0 : }
599 :
600 : void
601 0 : U2FRegisterRunnable::SendResponse(const RegisterResponse& aResponse)
602 : {
603 0 : MOZ_ASSERT(NS_IsMainThread());
604 :
605 0 : ErrorResult rv;
606 0 : mCallback->Call(aResponse, rv);
607 0 : NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
608 : // Useful exceptions already got reported.
609 0 : rv.SuppressException();
610 0 : }
611 :
612 : NS_IMETHODIMP
613 0 : U2FRegisterRunnable::Run()
614 : {
615 0 : MOZ_ASSERT(!NS_IsMainThread());
616 :
617 0 : nsNSSShutDownPreventionLock locker;
618 0 : if (isAlreadyShutDown()) {
619 0 : return NS_ERROR_FAILURE;
620 : }
621 :
622 : // Create a Status object to keep track of when we're done
623 0 : RefPtr<U2FStatus> status = new U2FStatus();
624 :
625 : // Evaluate the AppID
626 0 : ErrorCode appIdResult = EvaluateAppID();
627 0 : if (appIdResult != ErrorCode::OK) {
628 0 : status->Stop(appIdResult);
629 : }
630 :
631 : // Produce the AppParam from the current AppID
632 0 : nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
633 0 : CryptoBuffer appParam;
634 0 : if (!appParam.SetLength(SHA256_LENGTH, fallible)) {
635 0 : return NS_ERROR_OUT_OF_MEMORY;
636 : }
637 :
638 : // Note: This could use nsICryptoHash to avoid having to interact with NSS
639 : // directly.
640 : SECStatus srv;
641 0 : srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
642 0 : reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
643 0 : cAppId.Length());
644 0 : if (srv != SECSuccess) {
645 0 : return NS_ERROR_FAILURE;
646 : }
647 :
648 : // First, we must determine if any of the RegisteredKeys are already
649 : // registered, e.g., in the whitelist.
650 0 : for (LocalRegisteredKey key : mRegisteredKeys) {
651 0 : nsTArray<RefPtr<U2FPrepPromise>> prepPromiseList;
652 0 : for (const Authenticator& token : mAuthenticators) {
653 : RefPtr<U2FIsRegisteredTask> compTask =
654 0 : new U2FIsRegisteredTask(token, key, appParam, mEventTarget);
655 0 : prepPromiseList.AppendElement(compTask->Execute());
656 : }
657 :
658 : // Treat each call to Promise::All as a work unit, as it completes together
659 0 : status->WaitGroupAdd();
660 :
661 0 : U2FPrepPromise::All(mEventTarget, prepPromiseList)
662 : ->Then(mEventTarget, __func__,
663 0 : [&status] (const nsTArray<Authenticator>& aTokens) {
664 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
665 : ("ALL: None of the RegisteredKeys were recognized. n=%" PRIuSIZE,
666 : aTokens.Length()));
667 :
668 0 : status->WaitGroupDone();
669 0 : },
670 0 : [&status] (ErrorCode aErrorCode) {
671 0 : status->Stop(aErrorCode);
672 0 : status->WaitGroupDone();
673 0 : });
674 : }
675 :
676 : // Wait for all the IsRegistered tasks to complete
677 0 : status->WaitGroupWait();
678 :
679 : // Check to see whether we're supposed to stop, because one of the keys was
680 : // recognized.
681 0 : if (status->IsStopped()) {
682 0 : status->WaitGroupAdd();
683 0 : mEventTarget->Dispatch(NS_NewRunnableFunction(
684 : "dom::U2FRegisterRunnable::Run",
685 0 : [&status, this] () {
686 0 : RegisterResponse response;
687 : response.mErrorCode.Construct(
688 0 : static_cast<uint32_t>(status->GetErrorCode()));
689 0 : SendResponse(response);
690 0 : status->WaitGroupDone();
691 0 : }));
692 :
693 : // Don't exit until the main thread runnable completes
694 0 : status->WaitGroupWait();
695 0 : return NS_OK;
696 : }
697 :
698 : // Now proceed to actually register a new key.
699 0 : for (LocalRegisterRequest req : mRegisterRequests) {
700 : // Hash the ClientData into the ChallengeParam
701 0 : CryptoBuffer challengeParam;
702 0 : if (!challengeParam.SetLength(SHA256_LENGTH, fallible)) {
703 0 : continue;
704 : }
705 :
706 0 : srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
707 0 : req.mClientData.Elements(), req.mClientData.Length());
708 0 : if (srv != SECSuccess) {
709 0 : continue;
710 : }
711 :
712 0 : for (const Authenticator& token : mAuthenticators) {
713 : RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, mAppId,
714 : token, appParam,
715 : challengeParam,
716 : req,
717 0 : mEventTarget);
718 0 : status->WaitGroupAdd();
719 :
720 0 : registerTask->Execute()->Then(mEventTarget, __func__,
721 0 : [&status] (nsString aResponse) {
722 0 : if (!status->IsStopped()) {
723 0 : status->Stop(ErrorCode::OK, aResponse);
724 : }
725 0 : status->WaitGroupDone();
726 0 : },
727 0 : [&status] (ErrorCode aErrorCode) {
728 : // Ignore the failing error code, as we only want the first success.
729 : // U2F devices don't provide much for error codes anyway, so if
730 : // they all fail we'll return DEVICE_INELIGIBLE.
731 0 : status->WaitGroupDone();
732 0 : });
733 : }
734 : }
735 :
736 : // Wait until the first key is successfuly generated
737 0 : status->WaitGroupWait();
738 :
739 : // If none of the tasks completed, then nothing could satisfy.
740 0 : if (!status->IsStopped()) {
741 0 : status->Stop(ErrorCode::BAD_REQUEST);
742 : }
743 :
744 : // Transmit back to the JS engine from the Main Thread
745 0 : status->WaitGroupAdd();
746 0 : mEventTarget->Dispatch(NS_NewRunnableFunction(
747 : "dom::U2FRegisterRunnable::Run",
748 0 : [&status, this] () {
749 0 : RegisterResponse response;
750 0 : if (status->GetErrorCode() == ErrorCode::OK) {
751 0 : response.Init(status->GetResponse());
752 : } else {
753 : response.mErrorCode.Construct(
754 0 : static_cast<uint32_t>(status->GetErrorCode()));
755 : }
756 0 : SendResponse(response);
757 0 : status->WaitGroupDone();
758 0 : }));
759 :
760 : // TODO: Add timeouts, Bug 1301793
761 0 : status->WaitGroupWait();
762 0 : return NS_OK;
763 : }
764 :
765 0 : U2FSignRunnable::U2FSignRunnable(const nsAString& aOrigin,
766 : const nsAString& aAppId,
767 : const nsAString& aChallenge,
768 : const Sequence<RegisteredKey>& aRegisteredKeys,
769 : const Sequence<Authenticator>& aAuthenticators,
770 : U2FSignCallback* aCallback,
771 0 : nsISerialEventTarget* aEventTarget)
772 : : U2FRunnable(aOrigin, aAppId, aEventTarget)
773 : , mAuthenticators(aAuthenticators)
774 : // U2FSignCallback does not support threadsafe refcounting, and must be used
775 : // and destroyed on main.
776 : , mCallback(new nsMainThreadPtrHolder<U2FSignCallback>(
777 0 : "U2FSignRunnable::mCallback", aCallback))
778 : {
779 0 : MOZ_ASSERT(NS_IsMainThread());
780 :
781 : // Convert WebIDL objects to generic structs to pass between threads
782 0 : for (const RegisteredKey& key : aRegisteredKeys) {
783 : // Check for required attributes
784 0 : if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed()) {
785 0 : continue;
786 : }
787 :
788 0 : LocalRegisteredKey localKey;
789 0 : localKey.mVersion = key.mVersion.Value();
790 0 : localKey.mKeyHandle = key.mKeyHandle.Value();
791 0 : if (key.mAppId.WasPassed()) {
792 0 : localKey.mAppId.SetValue(key.mAppId.Value());
793 : }
794 :
795 0 : mRegisteredKeys.AppendElement(localKey);
796 : }
797 :
798 : // Assemble a clientData object
799 0 : nsresult rv = AssembleClientData(aOrigin, kGetAssertion, aChallenge,
800 0 : mClientData);
801 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
802 0 : MOZ_LOG(gU2FLog, LogLevel::Warning,
803 : ("Failed to AssembleClientData for the U2FSignRunnable."));
804 0 : return;
805 : }
806 : }
807 :
808 0 : U2FSignRunnable::~U2FSignRunnable()
809 : {
810 0 : nsNSSShutDownPreventionLock locker;
811 :
812 0 : if (isAlreadyShutDown()) {
813 0 : return;
814 : }
815 0 : shutdown(ShutdownCalledFrom::Object);
816 0 : }
817 :
818 : void
819 0 : U2FSignRunnable::SetTimeout(const int32_t aTimeoutMillis)
820 : {
821 0 : opt_mTimeoutSeconds.SetValue(aTimeoutMillis);
822 0 : }
823 :
824 : void
825 0 : U2FSignRunnable::SendResponse(const SignResponse& aResponse)
826 : {
827 0 : MOZ_ASSERT(NS_IsMainThread());
828 :
829 0 : ErrorResult rv;
830 0 : mCallback->Call(aResponse, rv);
831 0 : NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
832 : // Useful exceptions already got reported.
833 0 : rv.SuppressException();
834 0 : }
835 :
836 : NS_IMETHODIMP
837 0 : U2FSignRunnable::Run()
838 : {
839 0 : MOZ_ASSERT(!NS_IsMainThread());
840 :
841 0 : nsNSSShutDownPreventionLock locker;
842 0 : if (isAlreadyShutDown()) {
843 0 : return NS_ERROR_FAILURE;
844 : }
845 :
846 : // Create a Status object to keep track of when we're done
847 0 : RefPtr<U2FStatus> status = new U2FStatus();
848 :
849 : // Evaluate the AppID
850 0 : ErrorCode appIdResult = EvaluateAppID();
851 0 : if (appIdResult != ErrorCode::OK) {
852 0 : status->Stop(appIdResult);
853 : }
854 :
855 : // Hash the AppID and the ClientData into the AppParam and ChallengeParam
856 0 : nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
857 0 : CryptoBuffer appParam;
858 0 : CryptoBuffer challengeParam;
859 0 : if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
860 0 : !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
861 0 : return NS_ERROR_OUT_OF_MEMORY;
862 : }
863 :
864 : SECStatus srv;
865 0 : srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
866 0 : reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
867 0 : cAppId.Length());
868 0 : if (srv != SECSuccess) {
869 0 : return NS_ERROR_FAILURE;
870 : }
871 :
872 0 : srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
873 0 : mClientData.Elements(), mClientData.Length());
874 0 : if (srv != SECSuccess) {
875 0 : return NS_ERROR_FAILURE;
876 : }
877 :
878 : // Search the signing requests for one a token can fulfill
879 0 : for (LocalRegisteredKey key : mRegisteredKeys) {
880 : // Do not permit an individual RegisteredKey to assert a different AppID
881 0 : if (!key.mAppId.IsNull() && mAppId != key.mAppId.Value()) {
882 0 : continue;
883 : }
884 :
885 : // Decode the key handle
886 0 : CryptoBuffer keyHandle;
887 0 : nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle);
888 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
889 0 : continue;
890 : }
891 :
892 : // We ignore mTransports, as it is intended to be used for sorting the
893 : // available devices by preference, but is not an exclusion factor.
894 :
895 0 : for (const Authenticator& token : mAuthenticators) {
896 : RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, mAppId,
897 : key.mVersion, token,
898 : appParam, challengeParam,
899 : mClientData, keyHandle,
900 0 : mEventTarget);
901 0 : status->WaitGroupAdd();
902 :
903 0 : signTask->Execute()->Then(mEventTarget, __func__,
904 0 : [&status] (nsString aResponse) {
905 0 : if (!status->IsStopped()) {
906 0 : status->Stop(ErrorCode::OK, aResponse);
907 : }
908 0 : status->WaitGroupDone();
909 0 : },
910 0 : [&status] (ErrorCode aErrorCode) {
911 : // Ignore the failing error code, as we only want the first success.
912 : // U2F devices don't provide much for error codes anyway, so if
913 : // they all fail we'll return DEVICE_INELIGIBLE.
914 0 : status->WaitGroupDone();
915 0 : });
916 : }
917 : }
918 :
919 : // Wait for the authenticators to finish
920 0 : status->WaitGroupWait();
921 :
922 : // If none of the tasks completed, then nothing could satisfy.
923 0 : if (!status->IsStopped()) {
924 0 : status->Stop(ErrorCode::DEVICE_INELIGIBLE);
925 : }
926 :
927 : // Transmit back to the JS engine from the Main Thread
928 0 : status->WaitGroupAdd();
929 0 : mEventTarget->Dispatch(NS_NewRunnableFunction(
930 : "dom::U2FSignRunnable::Run",
931 0 : [&status, this] () {
932 0 : SignResponse response;
933 0 : if (status->GetErrorCode() == ErrorCode::OK) {
934 0 : response.Init(status->GetResponse());
935 : } else {
936 : response.mErrorCode.Construct(
937 0 : static_cast<uint32_t>(status->GetErrorCode()));
938 : }
939 0 : SendResponse(response);
940 0 : status->WaitGroupDone();
941 0 : }));
942 :
943 : // TODO: Add timeouts, Bug 1301793
944 0 : status->WaitGroupWait();
945 0 : return NS_OK;
946 : }
947 :
948 0 : U2F::U2F()
949 0 : : mInitialized(false)
950 0 : {}
951 :
952 0 : U2F::~U2F()
953 : {
954 0 : nsNSSShutDownPreventionLock locker;
955 :
956 0 : if (isAlreadyShutDown()) {
957 0 : return;
958 : }
959 0 : shutdown(ShutdownCalledFrom::Object);
960 0 : }
961 :
962 : /* virtual */ JSObject*
963 0 : U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
964 : {
965 0 : return U2FBinding::Wrap(aCx, this, aGivenProto);
966 : }
967 :
968 : void
969 0 : U2F::Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv)
970 : {
971 0 : MOZ_ASSERT(!mInitialized);
972 0 : MOZ_ASSERT(!mParent);
973 0 : mParent = do_QueryInterface(aParent);
974 0 : MOZ_ASSERT(mParent);
975 :
976 0 : nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
977 0 : MOZ_ASSERT(doc);
978 :
979 0 : nsIPrincipal* principal = doc->NodePrincipal();
980 0 : aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
981 0 : if (NS_WARN_IF(aRv.Failed())) {
982 0 : return;
983 : }
984 :
985 0 : if (NS_WARN_IF(mOrigin.IsEmpty())) {
986 0 : aRv.Throw(NS_ERROR_FAILURE);
987 0 : return;
988 : }
989 :
990 0 : if (!EnsureNSSInitializedChromeOrContent()) {
991 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
992 : ("Failed to get NSS context for U2F"));
993 0 : aRv.Throw(NS_ERROR_FAILURE);
994 0 : return;
995 : }
996 :
997 : // This only functions in e10s mode
998 0 : if (XRE_IsParentProcess()) {
999 0 : MOZ_LOG(gU2FLog, LogLevel::Debug,
1000 : ("Is non-e10s Process, U2F not available"));
1001 0 : aRv.Throw(NS_ERROR_FAILURE);
1002 0 : return;
1003 : }
1004 :
1005 : // Monolithically insert compatible nsIU2FToken objects into mAuthenticators.
1006 : // In future functionality expansions, this is where we could add a dynamic
1007 : // add/remove interface.
1008 0 : if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
1009 0 : if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
1010 : mozilla::fallible)) {
1011 0 : aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1012 0 : return;
1013 : }
1014 : }
1015 :
1016 0 : mEventTarget = doc->EventTargetFor(TaskCategory::Other);
1017 :
1018 0 : mInitialized = true;
1019 : }
1020 :
1021 : void
1022 0 : U2F::Register(const nsAString& aAppId,
1023 : const Sequence<RegisterRequest>& aRegisterRequests,
1024 : const Sequence<RegisteredKey>& aRegisteredKeys,
1025 : U2FRegisterCallback& aCallback,
1026 : const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
1027 : ErrorResult& aRv)
1028 : {
1029 0 : MOZ_ASSERT(NS_IsMainThread());
1030 :
1031 0 : if (!mInitialized) {
1032 0 : aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1033 0 : return;
1034 : }
1035 :
1036 0 : RefPtr<SharedThreadPool> pool = SharedThreadPool::Get(kPoolName);
1037 : RefPtr<U2FRegisterRunnable> task = new U2FRegisterRunnable(mOrigin, aAppId,
1038 : aRegisterRequests,
1039 : aRegisteredKeys,
1040 : mAuthenticators,
1041 : &aCallback,
1042 0 : mEventTarget);
1043 0 : pool->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
1044 : }
1045 :
1046 : void
1047 0 : U2F::Sign(const nsAString& aAppId,
1048 : const nsAString& aChallenge,
1049 : const Sequence<RegisteredKey>& aRegisteredKeys,
1050 : U2FSignCallback& aCallback,
1051 : const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
1052 : ErrorResult& aRv)
1053 : {
1054 0 : MOZ_ASSERT(NS_IsMainThread());
1055 :
1056 0 : if (!mInitialized) {
1057 0 : aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1058 0 : return;
1059 : }
1060 :
1061 0 : RefPtr<SharedThreadPool> pool = SharedThreadPool::Get(kPoolName);
1062 : RefPtr<U2FSignRunnable> task = new U2FSignRunnable(mOrigin, aAppId, aChallenge,
1063 : aRegisteredKeys,
1064 : mAuthenticators, &aCallback,
1065 0 : mEventTarget);
1066 0 : pool->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
1067 : }
1068 :
1069 : } // namespace dom
1070 9 : } // namespace mozilla
|