gecko-dev/dom/u2f/U2F.h
J.C. Jones e6dd50ba10 Bug 1309284 - Implement W3C Web Authentication JS API [part 1] r=keeler,qdot
This patch implements the W3C Web Authentication API from
https://www.w3.org/TR/webauthn/, currently the 28 September 2016
working draft.

It utilizes a tentative binding of the U2F NSS Soft Token to provide
authentication services while waiting on Bug 1245527 to support USB HID-based
U2F tokens. This binding is not in the specification yet, so it should be
considered an experiment to help the specification move fowrard.

There are also a handful of deviations from the specification's WebIDL, which
are annotated with comments in WebAuthentication.webidl.

There are no tests in this commit; they are in Part 4 of this commit series.
There is a small script online at https://webauthn.bin.coffee/ to exercise this
code, but it doesn't do any automated checks.

There are also a handful of TODOS:
1) The algorithm to relax the same-origin restriction is in Part 3.
2) The use of AlgorithmIdentifier and having a way to coerce an object to a
   string is still missing.
3) Timeouts and deadlines aren't there, and are pending reworking how
   the nsIU2FToken interface works.

UPDATED:
- Address qdot, keeler review comments (thanks!)
- Address more qdot, keeler review comments (thanks!)

MozReview-Commit-ID: JITapI38iOh

--HG--
extra : rebase_source : 9a09e852dd0c8dc47f42dabbcf8b845a6828b225
2017-01-09 13:22:49 -07:00

316 lines
8.4 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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_U2F_h
#define mozilla_dom_U2F_h
#include "js/TypeDecls.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/U2FBinding.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/SharedThreadPool.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIU2FToken.h"
#include "nsNSSShutDown.h"
#include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsWrapperCache.h"
#include "U2FAuthenticator.h"
#include "USBToken.h"
namespace mozilla {
namespace dom {
class U2FRegisterCallback;
class U2FSignCallback;
// Defined in U2FBinding.h by the U2F.webidl; their use requires a JSContext.
struct RegisterRequest;
struct RegisteredKey;
// These structs are analogs to the WebIDL versions, but can be used on worker
// threads which lack a JSContext.
struct LocalRegisterRequest
{
nsString mChallenge;
nsString mVersion;
CryptoBuffer mClientData;
};
struct LocalRegisteredKey
{
nsString mKeyHandle;
nsString mVersion;
Nullable<nsString> mAppId;
// TODO: Support transport preferences
// Nullable<nsTArray<Transport>> mTransports;
};
typedef MozPromise<nsString, ErrorCode, false> U2FPromise;
typedef MozPromise<Authenticator, ErrorCode, false> U2FPrepPromise;
// U2FPrepTasks return lists of Authenticators that are OK to
// proceed; they're useful for culling incompatible Authenticators.
// Currently, only IsRegistered is supported.
class U2FPrepTask : public Runnable
{
public:
explicit U2FPrepTask(const Authenticator& aAuthenticator);
RefPtr<U2FPrepPromise> Execute();
protected:
virtual ~U2FPrepTask();
Authenticator mAuthenticator;
MozPromiseHolder<U2FPrepPromise> mPromise;
};
// Determine whether the provided Authenticator already knows
// of the provided Registered Key.
class U2FIsRegisteredTask final : public U2FPrepTask
{
public:
U2FIsRegisteredTask(const Authenticator& aAuthenticator,
const LocalRegisteredKey& aRegisteredKey);
NS_DECL_NSIRUNNABLE
private:
~U2FIsRegisteredTask();
LocalRegisteredKey mRegisteredKey;
};
class U2FTask : public Runnable
{
public:
U2FTask(const nsAString& aOrigin,
const nsAString& aAppId,
const Authenticator& aAuthenticator);
RefPtr<U2FPromise> Execute();
nsString mOrigin;
nsString mAppId;
Authenticator mAuthenticator;
protected:
virtual ~U2FTask();
MozPromiseHolder<U2FPromise> mPromise;
};
// Use the provided Authenticator to Register a new scoped credential
// for the provided application.
class U2FRegisterTask final : public U2FTask
{
public:
U2FRegisterTask(const nsAString& aOrigin,
const nsAString& aAppId,
const Authenticator& aAuthenticator,
const CryptoBuffer& aAppParam,
const CryptoBuffer& aChallengeParam,
const LocalRegisterRequest& aRegisterEntry);
NS_DECL_NSIRUNNABLE
private:
~U2FRegisterTask();
CryptoBuffer mAppParam;
CryptoBuffer mChallengeParam;
LocalRegisterRequest mRegisterEntry;
};
// Generate an assertion using the provided Authenticator for the given origin
// and provided application to attest to ownership of a valid scoped credential.
class U2FSignTask final : public U2FTask
{
public:
U2FSignTask(const nsAString& aOrigin,
const nsAString& aAppId,
const nsAString& aVersion,
const Authenticator& aAuthenticator,
const CryptoBuffer& aAppParam,
const CryptoBuffer& aChallengeParam,
const CryptoBuffer& aClientData,
const CryptoBuffer& aKeyHandle);
NS_DECL_NSIRUNNABLE
private:
~U2FSignTask();
nsString mVersion;
CryptoBuffer mAppParam;
CryptoBuffer mChallengeParam;
CryptoBuffer mClientData;
CryptoBuffer mKeyHandle;
};
// Mediate inter-thread communication for multiple authenticators being queried
// in concert. Operates as a cyclic buffer with a stop-work method.
class U2FStatus
{
public:
U2FStatus();
void WaitGroupAdd();
void WaitGroupDone();
void WaitGroupWait();
void Stop(const ErrorCode aErrorCode);
void Stop(const ErrorCode aErrorCode, const nsAString& aResponse);
bool IsStopped();
ErrorCode GetErrorCode();
nsString GetResponse();
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FStatus)
private:
~U2FStatus();
uint16_t mCount;
bool mIsStopped;
nsString mResponse;
MOZ_INIT_OUTSIDE_CTOR ErrorCode mErrorCode;
ReentrantMonitor mReentrantMonitor;
};
// U2FRunnables run to completion, performing a single U2F operation such as
// registering, or signing.
class U2FRunnable : public Runnable
, public nsNSSShutDownObject
{
public:
U2FRunnable(const nsAString& aOrigin, const nsAString& aAppId);
// No NSS resources to release.
virtual
void virtualDestroyNSSReference() override {};
protected:
virtual ~U2FRunnable();
ErrorCode EvaluateAppID();
nsString mOrigin;
nsString mAppId;
};
// This U2FRunnable completes a single application-requested U2F Register
// operation.
class U2FRegisterRunnable : public U2FRunnable
{
public:
U2FRegisterRunnable(const nsAString& aOrigin,
const nsAString& aAppId,
const Sequence<RegisterRequest>& aRegisterRequests,
const Sequence<RegisteredKey>& aRegisteredKeys,
const Sequence<Authenticator>& aAuthenticators,
U2FRegisterCallback* aCallback);
void SendResponse(const RegisterResponse& aResponse);
void SetTimeout(const int32_t aTimeoutMillis);
NS_DECL_NSIRUNNABLE
private:
~U2FRegisterRunnable();
nsTArray<LocalRegisterRequest> mRegisterRequests;
nsTArray<LocalRegisteredKey> mRegisteredKeys;
nsTArray<Authenticator> mAuthenticators;
nsMainThreadPtrHandle<U2FRegisterCallback> mCallback;
Nullable<int32_t> opt_mTimeoutSeconds;
};
// This U2FRunnable completes a single application-requested U2F Sign operation.
class U2FSignRunnable : public U2FRunnable
{
public:
U2FSignRunnable(const nsAString& aOrigin,
const nsAString& aAppId,
const nsAString& aChallenge,
const Sequence<RegisteredKey>& aRegisteredKeys,
const Sequence<Authenticator>& aAuthenticators,
U2FSignCallback* aCallback);
void SendResponse(const SignResponse& aResponse);
void SetTimeout(const int32_t aTimeoutMillis);
NS_DECL_NSIRUNNABLE
private:
~U2FSignRunnable();
nsString mChallenge;
CryptoBuffer mClientData;
nsTArray<LocalRegisteredKey> mRegisteredKeys;
nsTArray<Authenticator> mAuthenticators;
nsMainThreadPtrHandle<U2FSignCallback> mCallback;
Nullable<int32_t> opt_mTimeoutSeconds;
};
// The U2F Class is used by the JS engine to initiate U2F operations.
class U2F final : public nsISupports
, public nsWrapperCache
, public nsNSSShutDownObject
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
U2F();
nsPIDOMWindowInner*
GetParentObject() const
{
return mParent;
}
void
Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv);
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
void
Register(const nsAString& aAppId,
const Sequence<RegisterRequest>& aRegisterRequests,
const Sequence<RegisteredKey>& aRegisteredKeys,
U2FRegisterCallback& aCallback,
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
ErrorResult& aRv);
void
Sign(const nsAString& aAppId,
const nsAString& aChallenge,
const Sequence<RegisteredKey>& aRegisteredKeys,
U2FSignCallback& aCallback,
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
ErrorResult& aRv);
// No NSS resources to release.
virtual
void virtualDestroyNSSReference() override {};
private:
nsCOMPtr<nsPIDOMWindowInner> mParent;
nsString mOrigin;
Sequence<Authenticator> mAuthenticators;
bool mInitialized;
~U2F();
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_U2F_h