--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -39,16 +39,17 @@
#include "mozilla/dom/FlyWebPublishedServer.h"
#include "mozilla/dom/FlyWebService.h"
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/Presentation.h"
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/TCPSocket.h"
#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/WebAuthentication.h"
#include "mozilla/dom/workers/RuntimeService.h"
#include "mozilla/Hal.h"
#include "nsISiteSpecificUserAgent.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/SSE.h"
#include "mozilla/StaticPtr.h"
#include "Connection.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
@@ -199,16 +200,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAuthentication)
#ifdef MOZ_AUDIO_CHANNEL_MANAGER
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelManager)
#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
@@ -2177,10 +2179,19 @@ Navigator::GetPresentation(ErrorResult&
return nullptr;
}
mPresentation = Presentation::Create(mWindow);
}
return mPresentation;
}
+WebAuthentication*
+Navigator::Authentication()
+{
+ if (!mAuthentication) {
+ mAuthentication = new WebAuthentication(GetWindow());
+ }
+ return mAuthentication;
+}
+
} // namespace dom
} // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -36,16 +36,17 @@ class systemMessageCallback;
class MediaDevices;
struct MediaStreamConstraints;
class WakeLock;
class ArrayBufferViewOrBlobOrStringOrFormData;
class ServiceWorkerContainer;
class DOMRequest;
struct FlyWebPublishOptions;
struct FlyWebFilter;
+class WebAuthentication;
} // namespace dom
} // namespace mozilla
//*****************************************************************************
// Navigator: Script "navigator" object
//*****************************************************************************
namespace mozilla {
@@ -235,16 +236,18 @@ public:
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv);
already_AddRefed<ServiceWorkerContainer> ServiceWorker();
+ mozilla::dom::WebAuthentication* Authentication();
+
void GetLanguages(nsTArray<nsString>& aLanguages);
bool MozE10sEnabled();
StorageManager* Storage();
static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
@@ -292,16 +295,17 @@ private:
RefPtr<nsPluginArray> mPlugins;
RefPtr<Permissions> mPermissions;
RefPtr<Geolocation> mGeolocation;
RefPtr<DesktopNotificationCenter> mNotification;
RefPtr<battery::BatteryManager> mBatteryManager;
RefPtr<Promise> mBatteryPromise;
RefPtr<PowerManager> mPowerManager;
RefPtr<network::Connection> mConnection;
+ RefPtr<WebAuthentication> mAuthentication;
#ifdef MOZ_AUDIO_CHANNEL_MANAGER
RefPtr<system::AudioChannelManager> mAudioChannelManager;
#endif
RefPtr<MediaDevices> mMediaDevices;
nsTArray<nsWeakPtr> mDeviceStorageStores;
RefPtr<time::TimeManager> mTimeManager;
RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1057,16 +1057,20 @@ DOMInterfaces = {
'VTTCue': {
'nativeType': 'mozilla::dom::TextTrackCue'
},
'VTTRegion': {
'nativeType': 'mozilla::dom::TextTrackRegion',
},
+'WebAuthentication': {
+ 'implicitJSContext': 'makeCredential',
+},
+
'WindowClient': {
'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
},
'WebGLActiveInfo': {
'nativeType': 'mozilla::WebGLActiveInfo',
'headerFile': 'WebGLActiveInfo.h'
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -119,17 +119,17 @@ CryptoBuffer::FromJwkBase64(const nsStri
nsresult rv = Base64URLDecode(temp, Base64URLDecodePaddingPolicy::Reject,
*this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
-CryptoBuffer::ToJwkBase64(nsString& aBase64)
+CryptoBuffer::ToJwkBase64(nsString& aBase64) const
{
// Shortcut for the empty octet string
if (Length() == 0) {
aBase64.Truncate();
return NS_OK;
}
nsAutoCString base64;
--- a/dom/crypto/CryptoBuffer.h
+++ b/dom/crypto/CryptoBuffer.h
@@ -39,17 +39,17 @@ public:
uint8_t* Assign(const TypedArray_base<T, UnwrapArray,
GetLengthAndDataAndSharedness>& aArray)
{
aArray.ComputeLengthAndData();
return Assign(aArray.Data(), aArray.Length());
}
nsresult FromJwkBase64(const nsString& aBase64);
- nsresult ToJwkBase64(nsString& aBase64);
+ nsresult ToJwkBase64(nsString& aBase64) const;
bool ToSECItem(PLArenaPool* aArena, SECItem* aItem) const;
JSObject* ToUint8Array(JSContext* aCx) const;
bool ToNewUnsignedBuffer(uint8_t** aBuf, uint32_t* aBufLen) const;
bool GetBigIntValue(unsigned long& aRetVal);
};
} // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredential.cpp
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/ScopedCredential.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredential, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredential)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredential)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredential)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ScopedCredential::ScopedCredential(WebAuthentication* aParent)
+ : mParent(aParent)
+ , mType(ScopedCredentialType::ScopedCred)
+{}
+
+ScopedCredential::~ScopedCredential()
+{}
+
+JSObject*
+ScopedCredential::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ScopedCredentialBinding::Wrap(aCx, this, aGivenProto);
+}
+
+ScopedCredentialType
+ScopedCredential::Type() const
+{
+ return mType;
+}
+
+void
+ScopedCredential::GetId(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mIdBuffer.ToUint8Array(aCx));
+}
+
+nsresult
+ScopedCredential::SetType(ScopedCredentialType aType)
+{
+ mType = aType;
+ return NS_OK;
+}
+
+nsresult
+ScopedCredential::SetId(CryptoBuffer& aBuffer)
+{
+ if (!mIdBuffer.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredential.h
@@ -0,0 +1,66 @@
+/* -*- 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_ScopedCredential_h
+#define mozilla_dom_ScopedCredential_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredential final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredential)
+
+public:
+ ScopedCredential(WebAuthentication* aParent);
+
+protected:
+ ~ScopedCredential();
+
+public:
+ WebAuthentication*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ ScopedCredentialType
+ Type() const;
+
+ void
+ GetId(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ nsresult
+ SetType(ScopedCredentialType aType);
+
+ nsresult
+ SetId(CryptoBuffer& aBuffer);
+
+private:
+ RefPtr<WebAuthentication> mParent;
+ CryptoBuffer mIdBuffer;
+ ScopedCredentialType mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScopedCredential_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredentialInfo.cpp
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/ScopedCredentialInfo.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredentialInfo, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredentialInfo)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredentialInfo)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredentialInfo)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ScopedCredentialInfo::ScopedCredentialInfo(WebAuthentication* aParent)
+ : mParent(aParent)
+{}
+
+ScopedCredentialInfo::~ScopedCredentialInfo()
+{}
+
+JSObject*
+ScopedCredentialInfo::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return ScopedCredentialInfoBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<ScopedCredential>
+ScopedCredentialInfo::Credential() const
+{
+ RefPtr<ScopedCredential> temp(mCredential);
+ return temp.forget();
+}
+
+already_AddRefed<WebAuthnAttestation>
+ScopedCredentialInfo::Attestation() const
+{
+ RefPtr<WebAuthnAttestation> temp(mAttestation);
+ return temp.forget();
+}
+
+void
+ScopedCredentialInfo::SetCredential(RefPtr<ScopedCredential> aCredential)
+{
+ mCredential = aCredential;
+}
+
+void
+ScopedCredentialInfo::SetAttestation(RefPtr<WebAuthnAttestation> aAttestation)
+{
+ mAttestation = aAttestation;
+}
+
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/ScopedCredentialInfo.h
@@ -0,0 +1,74 @@
+/* -*- 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_ScopedCredentialInfo_h
+#define mozilla_dom_ScopedCredentialInfo_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class CryptoKey;
+class ScopedCredential;
+class WebAuthnAttestation;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredentialInfo final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredentialInfo)
+
+public:
+ ScopedCredentialInfo(WebAuthentication* aParent);
+
+protected:
+ ~ScopedCredentialInfo();
+
+public:
+ WebAuthentication*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<ScopedCredential>
+ Credential() const;
+
+ already_AddRefed<WebAuthnAttestation>
+ Attestation() const;
+
+ void
+ SetCredential(RefPtr<ScopedCredential>);
+
+ void
+ SetAttestation(RefPtr<WebAuthnAttestation>);
+
+private:
+ RefPtr<WebAuthentication> mParent;
+ RefPtr<WebAuthnAttestation> mAttestation;
+ RefPtr<ScopedCredential> mCredential;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScopedCredentialInfo_h
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -44,17 +44,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2
static mozilla::LazyLogModule gWebauthLog("webauth_u2f");
static nsresult
AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
const nsAString& aChallenge, CryptoBuffer& aClientData)
{
MOZ_ASSERT(NS_IsMainThread());
- ClientData clientDataObject;
+ U2FClientData clientDataObject;
clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
clientDataObject.mChallenge.Construct(aChallenge);
clientDataObject.mOrigin.Construct(aOrigin);
nsAutoString json;
if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
return NS_ERROR_FAILURE;
}
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -18,16 +18,17 @@
#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;
@@ -48,29 +49,16 @@ struct LocalRegisteredKey
{
nsString mKeyHandle;
nsString mVersion;
Nullable<nsString> mAppId;
// TODO: Support transport preferences
// Nullable<nsTArray<Transport>> mTransports;
};
-// These enumerations are defined in the FIDO U2F Javascript API under the
-// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
-// Any changes to these must occur in both locations.
-enum class ErrorCode {
- OK = 0,
- OTHER_ERROR = 1,
- BAD_REQUEST = 2,
- CONFIGURATION_UNSUPPORTED = 3,
- DEVICE_INELIGIBLE = 4,
- TIMEOUT = 5
-};
-
-typedef nsCOMPtr<nsIU2FToken> Authenticator;
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
{
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FAuthenticator.h
@@ -0,0 +1,33 @@
+/* -*- 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_U2FAuthenticator_h
+#define mozilla_dom_U2FAuthenticator_h
+
+#include "nsIU2FToken.h"
+#include "USBToken.h"
+
+namespace mozilla {
+namespace dom {
+
+ // These enumerations are defined in the FIDO U2F Javascript API under the
+// interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
+// Any changes to these must occur in both locations.
+enum class ErrorCode {
+ OK = 0,
+ OTHER_ERROR = 1,
+ BAD_REQUEST = 2,
+ CONFIGURATION_UNSUPPORTED = 3,
+ DEVICE_INELIGIBLE = 4,
+ TIMEOUT = 5
+};
+
+typedef nsCOMPtr<nsIU2FToken> Authenticator;
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_U2FAuthenticator_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthentication.cpp
@@ -0,0 +1,1021 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/WebAuthentication.h"
+#include "mozilla/dom/WebAuthnAssertion.h"
+#include "mozilla/dom/WebAuthnAttestation.h"
+
+#include "mozilla/dom/Promise.h"
+#include "nsICryptoHash.h"
+#include "pkix/Input.h"
+#include "pkixutil.h"
+
+namespace mozilla {
+namespace dom {
+
+extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthentication, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthentication)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthentication)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthentication)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+template<class OOS>
+static nsresult
+GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm,
+ /* out */ nsString& aName)
+{
+ MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
+
+ if (aAlgorithm.IsString()) {
+ // If string, then treat as algorithm name
+ aName.Assign(aAlgorithm.GetAsString());
+ } else {
+ // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
+ }
+
+ if (!NormalizeToken(aName, aName)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
+ /* out */ CryptoBuffer& aOut)
+{
+ MOZ_ASSERT(aHashService);
+
+ nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aHashService->Update(
+ reinterpret_cast<const uint8_t*>(aIn.BeginReading()),aIn.Length());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString fullHash;
+ // Passing false below means we will get a binary result rather than a
+ // base64-encoded string.
+ rv = aHashService->Finish(false, fullHash);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aOut.Assign(fullHash);
+ return rv;
+}
+
+static nsresult
+AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
+ /* out */ nsACString& aJsonOut)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsString challengeBase64;
+ nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebAuthnClientData clientDataObject;
+ clientDataObject.mOrigin.Assign(aOrigin);
+ clientDataObject.mHashAlg.SetAsString().Assign(NS_LITERAL_STRING("S256"));
+ clientDataObject.mChallenge.Assign(challengeBase64);
+
+ nsAutoString temp;
+ if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
+ return NS_OK;
+}
+
+static nsresult
+ScopedCredentialGetData(const ScopedCredentialDescriptor& aSCD,
+ /* out */ uint8_t** aBuf, /* out */ uint32_t* aBufLen)
+{
+ MOZ_ASSERT(aBuf);
+ MOZ_ASSERT(aBufLen);
+
+ if (aSCD.mId.IsArrayBufferView()) {
+ *aBuf = aSCD.mId.GetAsArrayBufferView().Data();
+ *aBufLen = aSCD.mId.GetAsArrayBufferView().Length();
+ } else if (aSCD.mId.IsArrayBuffer()) {
+ *aBuf = aSCD.mId.GetAsArrayBuffer().Data();
+ *aBufLen = aSCD.mId.GetAsArrayBuffer().Length();
+ } else {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
+ uint32_t aLen)
+{
+ if (aSrc.EnsureLength(aLen) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ aDest.ClearAndRetainStorage();
+
+ for (uint32_t offset = 0; offset < aLen; ++offset) {
+ uint8_t b;
+ if (aSrc.Read(b) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ if (!aDest.AppendElement(b, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+U2FAssembleAuthenticatorData(/* out */ CryptoBuffer& aAuthenticatorData,
+ const CryptoBuffer& aRpIdHash,
+ const CryptoBuffer& aSignatureData)
+{
+ // The AuthenticatorData for U2F devices is the concatenation of the
+ // RP ID with the output of the U2F Sign operation.
+ if (aRpIdHash.Length() != 32) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aAuthenticatorData.AppendElements(aRpIdHash, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!aAuthenticatorData.AppendElements(aSignatureData, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
+ /* out */ CryptoBuffer& aPubKeyBuf,
+ /* out */ CryptoBuffer& aKeyHandleBuf,
+ /* out */ CryptoBuffer& aAttestationCertBuf,
+ /* out */ CryptoBuffer& aSignatureBuf)
+{
+ // U2F v1.1 Format via
+ // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html
+ //
+ // Bytes Value
+ // 1 0x05
+ // 65 public key
+ // 1 key handle length
+ // * key handle
+ // ASN.1 attestation certificate
+ // * attestation signature
+
+ pkix::Input u2fResponse;
+ u2fResponse.Init(aResponse.Elements(), aResponse.Length());
+
+ pkix::Reader input(u2fResponse);
+
+ uint8_t b;
+ if (input.Read(b) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ if (b != 0x05) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint8_t handleLen;
+ if (input.Read(handleLen) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
+ // length.
+ pkix::Input cert;
+ if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert)
+ != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ pkix::Reader certInput(cert);
+ rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The remainder of u2fResponse is the signature
+ pkix::Input u2fSig;
+ input.SkipToEnd(u2fSig);
+ pkix::Reader sigInput(u2fSig);
+ rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+WebAuthentication::WebAuthentication(nsPIDOMWindowInner* aParent)
+ : mInitialized(false)
+{
+ mParent = do_QueryInterface(aParent);
+ MOZ_ASSERT(mParent);
+}
+
+WebAuthentication::~WebAuthentication()
+{}
+
+nsresult
+WebAuthentication::InitLazily()
+{
+ if (mInitialized) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mParent);
+ if (!mParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
+ MOZ_ASSERT(doc);
+
+ nsIPrincipal* principal = doc->NodePrincipal();
+ nsresult rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(mOrigin.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This only functions in e10s mode
+ // TODO: Remove in Bug 1323339
+ if (XRE_IsParentProcess()) {
+ MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ ("Is non-e10s Process, WebAuthn not available"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
+ if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+JSObject*
+WebAuthentication::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WebAuthenticationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn MakeCredential method. The exact
+// mapping of U2F data fields to WebAuthn data fields is still a matter of
+// ongoing discussion, and this should not be taken as anything but a point-in-
+// time possibility.
+void
+WebAuthentication::U2FAuthMakeCredential(
+ const RefPtr<CredentialRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ const Account& aAccount,
+ const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+ const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+ const WebAuthnExtensions& aExtensions)
+{
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
+ aRequest->AddActiveToken(__func__);
+
+ // 5.1.1 When this operation is invoked, the authenticator must perform the
+ // following procedure:
+
+ // 5.1.1.a Check if all the supplied parameters are syntactically well-
+ // formed and of the correct length. If not, return an error code equivalent
+ // to UnknownError and terminate the operation.
+
+ if ((aRpIdHash.Length() != SHA256_LENGTH) ||
+ (aClientDataHash.Length() != SHA256_LENGTH)) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ // 5.1.1.b Check if at least one of the specified combinations of
+ // ScopedCredentialType and cryptographic parameters is supported. If not,
+ // return an error code equivalent to NotSupportedError and terminate the
+ // operation.
+
+ bool isValidCombination = false;
+
+ for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
+ if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
+ aNormalizedParams[a].mAlgorithm.IsString() &&
+ aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
+ WEBCRYPTO_NAMED_CURVE_P256)) {
+ isValidCombination = true;
+ break;
+ }
+ }
+ if (!isValidCombination) {
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // 5.1.1.c Check if a credential matching any of the supplied
+ // ScopedCredential identifiers is present on this authenticator. If so,
+ // return an error code equivalent to NotAllowedError and terminate the
+ // operation.
+
+ if (aExcludeList.WasPassed()) {
+ const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();
+
+ for (const ScopedCredentialDescriptor& scd : list) {
+ bool isRegistered = false;
+
+ uint8_t *data;
+ uint32_t len;
+
+ // data is owned by the Descriptor, do don't free it here.
+ if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ if (isRegistered) {
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ }
+ }
+
+ // 5.1.1.d Prompt the user for consent to create a new credential. The
+ // prompt for obtaining this consent is shown by the authenticator if it has
+ // its own output capability, or by the user agent otherwise. If the user
+ // denies consent, return an error code equivalent to NotAllowedError and
+ // terminate the operation.
+
+ // 5.1.1.d Once user consent has been obtained, generate a new credential
+ // object
+
+ // 5.1.1.e If any error occurred while creating the new credential object,
+ // return an error code equivalent to UnknownError and terminate the
+ // operation.
+
+ // 5.1.1.f Process all the supported extensions requested by the client, and
+ // generate an attestation statement. If no authority key is available to
+ // sign such an attestation statement, then the authenticator performs self
+ // attestation of the credential with its own private key. For more details
+ // on attestation, see §5.3 Credential Attestation Statements.
+
+ // No extensions are supported
+
+ // 4.1.1.11 While issuedRequests is not empty, perform the following actions
+ // depending upon the adjustedTimeout timer and responses from the
+ // authenticators:
+
+ // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
+ // issuedRequests invoke the authenticatorCancel operation on that
+ // authenticator and remove its entry from the list.
+
+ uint8_t* buffer;
+ uint32_t bufferlen;
+
+ nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(),
+ aClientDataHash.Length(), &buffer, &bufferlen);
+
+ // 4.1.1.11.b If any authenticator returns a status indicating that the user
+ // cancelled the operation, delete that authenticator’s entry from
+ // issuedRequests. For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.1.11.c If any authenticator returns an error status, delete the
+ // corresponding entry from issuedRequests.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer regData;
+ if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ // Decompose the U2F registration packet
+ CryptoBuffer pubKeyBuf;
+ CryptoBuffer keyHandleBuf;
+ CryptoBuffer attestationCertBuf;
+ CryptoBuffer signatureBuf;
+
+ rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
+ attestationCertBuf, signatureBuf);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ // Sign the aClientDataHash explicitly to get the format needed for
+ // the AuthenticatorData parameter of WebAuthnAttestation. This might
+ // be temporary while the spec settles down how to incorporate U2F.
+ rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(), aClientDataHash.Length(),
+ keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
+ &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer signatureData;
+ if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ CryptoBuffer clientDataBuf;
+ if (!clientDataBuf.Assign(aClientData)) {
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ CryptoBuffer authenticatorDataBuf;
+ rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+ signatureData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ // 4.1.1.11.d If any authenticator indicates success:
+
+ // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.
+
+ // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
+ // populate its fields with the values returned from the authenticator as well
+ // as the clientDataJSON computed earlier.
+
+ RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+ credential->SetType(ScopedCredentialType::ScopedCred);
+ credential->SetId(keyHandleBuf);
+
+ RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
+ attestation->SetFormat(NS_LITERAL_STRING("u2f"));
+ attestation->SetClientData(clientDataBuf);
+ attestation->SetAuthenticatorData(authenticatorDataBuf);
+ attestation->SetAttestation(regData);
+
+ CredentialPtr info = new ScopedCredentialInfo(this);
+ info->SetCredential(credential);
+ info->SetAttestation(attestation);
+
+ // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
+ aRequest->SetSuccess(info);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn GetAssertion method. The exact mapping
+// of U2F data fields to WebAuthn data fields is still a matter of ongoing
+// discussion, and this should not be taken as anything but a point-in- time
+// possibility.
+void
+WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ nsTArray<CryptoBuffer>& aAllowList,
+ const WebAuthnExtensions& aExtensions)
+{
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));
+
+ // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
+ aRequest->AddActiveToken(__func__);
+
+ // 4.1.2.8 While issuedRequests is not empty, perform the following actions
+ // depending upon the adjustedTimeout timer and responses from the
+ // authenticators:
+
+ // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
+ // in issuedRequests invoke the authenticatorCancel operation on that
+ // authenticator and remove its entry from the list.
+
+ for (CryptoBuffer& allowedCredential : aAllowList) {
+ bool isRegistered = false;
+ nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
+ allowedCredential.Length(),
+ &isRegistered);
+
+ // 4.1.2.8.b If any authenticator returns a status indicating that the user
+ // cancelled the operation, delete that authenticator’s entry from
+ // issuedRequests. For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator, and remove its entry
+ // from the list.
+
+ // 4.1.2.8.c If any authenticator returns an error status, delete the
+ // corresponding entry from issuedRequests.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ if (!isRegistered) {
+ continue;
+ }
+
+ // Sign
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(), aClientDataHash.Length(),
+ allowedCredential.Elements(), allowedCredential.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer signatureData;
+ if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ // 4.1.2.8.d If any authenticator returns success:
+
+ // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.
+
+ // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
+ // populate its fields with the values returned from the authenticator as
+ // well as the clientDataJSON computed earlier.
+
+ CryptoBuffer clientDataBuf;
+ if (!clientDataBuf.Assign(aClientData)) {
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ CryptoBuffer authenticatorDataBuf;
+ rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+ signatureData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+ credential->SetType(ScopedCredentialType::ScopedCred);
+ credential->SetId(allowedCredential);
+
+ AssertionPtr assertion = new WebAuthnAssertion(this);
+ assertion->SetCredential(credential);
+ assertion->SetClientData(clientDataBuf);
+ assertion->SetAuthenticatorData(authenticatorDataBuf);
+ assertion->SetSignature(signatureData);
+
+ // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
+ aRequest->SetSuccess(assertion);
+ return;
+ }
+
+ // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
+ // and terminate this algorithm.
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+}
+
+already_AddRefed<Promise>
+WebAuthentication::MakeCredential(JSContext* aCx, const Account& aAccount,
+ const Sequence<ScopedCredentialParameters>& aCryptoParameters,
+ const ArrayBufferViewOrArrayBuffer& aChallenge,
+ const ScopedCredentialOptions& aOptions)
+{
+ MOZ_ASSERT(mParent);
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ return nullptr;
+ }
+
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+
+ nsresult initRv = InitLazily();
+ if (NS_FAILED(initRv)) {
+ promise->MaybeReject(initRv);
+ return promise.forget();
+ }
+
+ // 4.1.1.1 If timeoutSeconds was specified, check if its value lies within a
+ // reasonable range as defined by the platform and if not, correct it to the
+ // closest value lying within that range.
+
+ double adjustedTimeout = 30.0;
+ if (aOptions.mTimeoutSeconds.WasPassed()) {
+ adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+ adjustedTimeout = std::max(15.0, adjustedTimeout);
+ adjustedTimeout = std::min(120.0, adjustedTimeout);
+ }
+
+ // 4.1.1.2 Let promise be a new Promise. Return promise and start a timer for
+ // adjustedTimeout seconds.
+
+ RefPtr<CredentialRequest> requestMonitor = new CredentialRequest();
+ requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+ if (mOrigin.EqualsLiteral("null")) {
+ // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
+ // DOMException whose name is "NotAllowedError", and terminate this
+ // algorithm
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ nsCString rpId;
+ if (!aOptions.mRpId.WasPassed()) {
+ // 4.1.1.3.a If rpId is not specified, then set rpId to callerOrigin, and
+ // rpIdHash to the SHA-256 hash of rpId.
+ rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+ } else {
+ // 4.1.1.3.b If rpId is specified, then invoke the procedure used for
+ // relaxing the same-origin restriction by setting the document.domain
+ // attribute, using rpId as the given value but without changing the current
+ // document’s domain. If no errors are thrown, set rpId to the value of host
+ // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ // TODO: relax the same-origin restriction
+
+ rpId.Assign(NS_ConvertUTF16toUTF8(aOptions.mRpId.Value()));
+ }
+
+ CryptoBuffer rpIdHash;
+ if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsresult srv;
+ nsCOMPtr<nsICryptoHash> hashService =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, rpId, rpIdHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.1.4 Process each element of cryptoParameters using the following steps,
+ // to produce a new sequence normalizedParameters.
+ nsTArray<ScopedCredentialParameters> normalizedParams;
+ for (size_t a = 0; a < aCryptoParameters.Length(); ++a) {
+ // 4.1.1.4.a Let current be the currently selected element of
+ // cryptoParameters.
+
+ // 4.1.1.4.b If current.type does not contain a ScopedCredentialType
+ // supported by this implementation, then stop processing current and move
+ // on to the next element in cryptoParameters.
+ if (aCryptoParameters[a].mType != ScopedCredentialType::ScopedCred) {
+ continue;
+ }
+
+ // 4.1.1.4.c Let normalizedAlgorithm be the result of normalizing an
+ // algorithm using the procedure defined in [WebCryptoAPI], with alg set to
+ // current.algorithm and op set to 'generateKey'. If an error occurs during
+ // this procedure, then stop processing current and move on to the next
+ // element in cryptoParameters.
+
+ nsString algName;
+ if (NS_FAILED(GetAlgorithmName(aCx, aCryptoParameters[a].mAlgorithm,
+ algName))) {
+ continue;
+ }
+
+ // 4.1.1.4.d Add a new object of type ScopedCredentialParameters to
+ // normalizedParameters, with type set to current.type and algorithm set to
+ // normalizedAlgorithm.
+ ScopedCredentialParameters normalizedObj;
+ normalizedObj.mType = aCryptoParameters[a].mType;
+ normalizedObj.mAlgorithm.SetAsString().Assign(algName);
+
+ normalizedParams.AppendElement(normalizedObj);
+ }
+
+ // 4.1.1.5 If normalizedAlgorithm is empty and cryptoParameters was not empty,
+ // cancel the timer started in step 2, reject promise with a DOMException
+ // whose name is "NotSupportedError", and terminate this algorithm.
+
+ // 4.1.1.6 If excludeList is undefined, set it to the empty list.
+
+ // 4.1.1.7 If extensions was specified, process any extensions supported by
+ // this client platform, to produce the extension data that needs to be sent
+ // to the authenticator. If an error is encountered while processing an
+ // extension, skip that extension and do not produce any extension data for
+ // it. Call the result of this processing clientExtensions.
+
+ // 4.1.1.8 Use attestationChallenge, callerOrigin and rpId, along with the
+ // token binding key associated with callerOrigin (if any), to create a
+ // ClientData structure representing this request. Choose a hash algorithm for
+ // hashAlg and compute the clientDataJSON and clientDataHash.
+
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ CryptoBuffer clientDataHash;
+ if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, clientDataJSON, clientDataHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.1.9 Initialize issuedRequests to an empty list.
+ RefPtr<CredentialPromise> monitorPromise = requestMonitor->Ensure();
+
+ // 4.1.1.10 For each authenticator currently available on this platform:
+ // asynchronously invoke the authenticatorMakeCredential operation on that
+ // authenticator with rpIdHash, clientDataHash, accountInformation,
+ // normalizedParameters, excludeList and clientExtensions as parameters. Add a
+ // corresponding entry to issuedRequests.
+ for (Authenticator u2ftoken : mAuthenticators) {
+ // 4.1.1.10.a For each credential C in excludeList that has a non-empty
+ // transports list, optionally use only the specified transports to test for
+ // the existence of C.
+ U2FAuthMakeCredential(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+ clientDataHash, aAccount, normalizedParams,
+ aOptions.mExcludeList, aOptions.mExtensions);
+ }
+
+ requestMonitor->CompleteTask();
+
+ monitorPromise->Then(AbstractThread::MainThread(), __func__,
+ [promise] (CredentialPtr aInfo) {
+ promise->MaybeResolve(aInfo);
+ },
+ [promise] (nsresult aErrorCode) {
+ promise->MaybeReject(aErrorCode);
+ });
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+WebAuthentication::GetAssertion(const ArrayBufferViewOrArrayBuffer& aChallenge,
+ const AssertionOptions& aOptions)
+{
+ MOZ_ASSERT(mParent);
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ return nullptr;
+ }
+
+ // 4.1.2.1 If timeoutSeconds was specified, check if its value lies within a
+ // reasonable range as defined by the platform and if not, correct it to the
+ // closest value lying within that range.
+
+ double adjustedTimeout = 30.0;
+ if (aOptions.mTimeoutSeconds.WasPassed()) {
+ adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+ adjustedTimeout = std::max(15.0, adjustedTimeout);
+ adjustedTimeout = std::min(120.0, adjustedTimeout);
+ }
+
+ // 4.1.2.2 Let promise be a new Promise. Return promise and start a timer for
+ // adjustedTimeout seconds.
+
+ RefPtr<AssertionRequest> requestMonitor = new AssertionRequest();
+ requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+
+ nsresult initRv = InitLazily();
+ if (NS_FAILED(initRv)) {
+ promise->MaybeReject(initRv);
+ return promise.forget();
+ }
+
+ if (mOrigin.EqualsLiteral("null")) {
+ // 4.1.2.3 If callerOrigin is an opaque origin, reject promise with a
+ // DOMException whose name is "NotAllowedError", and terminate this algorithm
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ nsCString rpId;
+ if (!aOptions.mRpId.WasPassed()) {
+ // 4.1.2.3.a If rpId is not specified, then set rpId to callerOrigin, and
+ // rpIdHash to the SHA-256 hash of rpId.
+ rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+ } else {
+ // 4.1.2.3.b If rpId is specified, then invoke the procedure used for
+ // relaxing the same-origin restriction by setting the document.domain
+ // attribute, using rpId as the given value but without changing the current
+ // document’s domain. If no errors are thrown, set rpId to the value of host
+ // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ // TODO: relax the same-origin restriction
+
+ rpId.Assign(NS_ConvertUTF16toUTF8(aOptions.mRpId.Value()));
+ }
+
+ CryptoBuffer rpIdHash;
+ if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsresult srv;
+ nsCOMPtr<nsICryptoHash> hashService =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, rpId, rpIdHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.2.4 If extensions was specified, process any extensions supported by
+ // this client platform, to produce the extension data that needs to be sent
+ // to the authenticator. If an error is encountered while processing an
+ // extension, skip that extension and do not produce any extension data for
+ // it. Call the result of this processing clientExtensions.
+
+ // TODO
+
+ // 4.1.2.5 Use assertionChallenge, callerOrigin and rpId, along with the token
+ // binding key associated with callerOrigin (if any), to create a ClientData
+ // structure representing this request. Choose a hash algorithm for hashAlg
+ // and compute the clientDataJSON and clientDataHash.
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ CryptoBuffer clientDataHash;
+ if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, clientDataJSON, clientDataHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.2.6 Initialize issuedRequests to an empty list.
+ RefPtr<AssertionPromise> monitorPromise = requestMonitor->Ensure();
+
+ // 4.1.2.7 For each authenticator currently available on this platform,
+ // perform the following steps:
+ for(Authenticator u2ftoken : mAuthenticators) {
+ // 4.1.2.7.a If allowList is undefined or empty, let credentialList be an
+ // empty list. Otherwise, execute a platform-specific procedure to determine
+ // which, if any, credentials listed in allowList might be present on this
+ // authenticator, and set credentialList to this filtered list. If no such
+ // filtering is possible, set credentialList to an empty list.
+
+ nsTArray<CryptoBuffer> credentialList;
+
+ // Note: we only support U2F-style authentication for now, so we effectively
+ // require an AllowList.
+ if (!aOptions.mAllowList.WasPassed()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ const Sequence<ScopedCredentialDescriptor>& allowList =
+ aOptions.mAllowList.Value();
+
+ for (const ScopedCredentialDescriptor& scd : allowList) {
+ CryptoBuffer buf;
+ if (!buf.Assign(scd.mId)) {
+ promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.2.7.b For each credential C within the credentialList that has a
+ // non- empty transports list, optionally use only the specified
+ // transports to get assertions using credential C.
+
+ // TODO: Filter using Transport
+ credentialList.AppendElement(buf);
+ }
+
+ // 4.1.2.7.c If the above filtering process concludes that none of the
+ // credentials on allowList can possibly be on this authenticator, do not
+ // perform any of the following steps for this authenticator, and proceed to
+ // the next authenticator (if any).
+ if (credentialList.IsEmpty()) {
+ continue;
+ }
+
+ // 4.1.2.7.d Asynchronously invoke the authenticatorGetAssertion operation
+ // on this authenticator with rpIdHash, clientDataHash, credentialList, and
+ // clientExtensions as parameters.
+ U2FAuthGetAssertion(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+ clientDataHash, credentialList, aOptions.mExtensions);
+ }
+
+ requestMonitor->CompleteTask();
+
+ monitorPromise->Then(AbstractThread::MainThread(), __func__,
+ [promise] (AssertionPtr aAssertion) {
+ promise->MaybeResolve(aAssertion);
+ },
+ [promise] (nsresult aErrorCode) {
+ promise->MaybeReject(aErrorCode);
+ });
+
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthentication.h
@@ -0,0 +1,110 @@
+/* -*- 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_WebAuthentication_h
+#define mozilla_dom_WebAuthentication_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+#include "U2FAuthenticator.h"
+#include "WebAuthnRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+struct Account;
+class ArrayBufferViewOrArrayBuffer;
+struct AssertionOptions;
+class OwningArrayBufferViewOrArrayBuffer;
+struct ScopedCredentialOptions;
+struct ScopedCredentialParameters;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+typedef RefPtr<ScopedCredentialInfo> CredentialPtr;
+typedef RefPtr<WebAuthnAssertion> AssertionPtr;
+typedef WebAuthnRequest<CredentialPtr> CredentialRequest;
+typedef WebAuthnRequest<AssertionPtr> AssertionRequest;
+typedef MozPromise<CredentialPtr, nsresult, false> CredentialPromise;
+typedef MozPromise<AssertionPtr, nsresult, false> AssertionPromise;
+
+class WebAuthentication final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthentication)
+
+public:
+ WebAuthentication(nsPIDOMWindowInner* aParent);
+
+protected:
+ ~WebAuthentication();
+
+public:
+ nsPIDOMWindowInner*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise>
+ MakeCredential(JSContext* aCx, const Account& accountInformation,
+ const Sequence<ScopedCredentialParameters>& cryptoParameters,
+ const ArrayBufferViewOrArrayBuffer& attestationChallenge,
+ const ScopedCredentialOptions& options);
+
+ already_AddRefed<Promise>
+ GetAssertion(const ArrayBufferViewOrArrayBuffer& assertionChallenge,
+ const AssertionOptions& options);
+
+private:
+ nsresult
+ InitLazily();
+
+ void
+ U2FAuthMakeCredential(const RefPtr<CredentialRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ const Account& aAccount,
+ const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+ const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+ const WebAuthnExtensions& aExtensions);
+ void
+ U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ nsTArray<CryptoBuffer>& aAllowList,
+ const WebAuthnExtensions& aExtensions);
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsString mOrigin;
+ Sequence<Authenticator> mAuthenticators;
+ bool mInitialized;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthentication_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAssertion.cpp
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnAssertion.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAssertion, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAssertion)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAssertion)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAssertion)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WebAuthnAssertion::WebAuthnAssertion(WebAuthentication* aParent)
+ : mParent(aParent)
+{}
+
+WebAuthnAssertion::~WebAuthnAssertion()
+{}
+
+JSObject*
+WebAuthnAssertion::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WebAuthnAssertionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<ScopedCredential>
+WebAuthnAssertion::Credential() const
+{
+ RefPtr<ScopedCredential> temp(mCredential);
+ return temp.forget();
+}
+
+void
+WebAuthnAssertion::GetClientData(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mClientData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAssertion::GetAuthenticatorData(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAssertion::GetSignature(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mSignature.ToUint8Array(aCx));
+}
+
+nsresult
+WebAuthnAssertion::SetCredential(RefPtr<ScopedCredential> aCredential)
+{
+ mCredential = aCredential;
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetClientData(CryptoBuffer& aBuffer)
+{
+ if (!mClientData.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetAuthenticatorData(CryptoBuffer& aBuffer)
+{
+ if (!mAuthenticatorData.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAssertion::SetSignature(CryptoBuffer& aBuffer)
+{
+ if (!mSignature.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAssertion.h
@@ -0,0 +1,86 @@
+/* -*- 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_WebAuthnAssertion_h
+#define mozilla_dom_WebAuthnAssertion_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScopedCredential;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+class WebAuthnAssertion final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAssertion)
+
+public:
+ WebAuthnAssertion(WebAuthentication* aParent);
+
+protected:
+ ~WebAuthnAssertion();
+
+public:
+ WebAuthentication*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<ScopedCredential>
+ Credential() const;
+
+ void
+ GetClientData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ void
+ GetAuthenticatorData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ void
+ GetSignature(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ nsresult
+ SetCredential(RefPtr<ScopedCredential> aCredential);
+
+ nsresult
+ SetClientData(CryptoBuffer& aBuffer);
+
+ nsresult
+ SetAuthenticatorData(CryptoBuffer& aBuffer);
+
+ nsresult
+ SetSignature(CryptoBuffer& aBuffer);
+
+private:
+ RefPtr<WebAuthentication> mParent;
+ RefPtr<ScopedCredential> mCredential;
+ CryptoBuffer mAuthenticatorData;
+ CryptoBuffer mClientData;
+ CryptoBuffer mSignature;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAssertion_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAttestation.cpp
@@ -0,0 +1,100 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnAttestation.h"
+
+namespace mozilla {
+namespace dom {
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAttestation, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAttestation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAttestation)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAttestation)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WebAuthnAttestation::WebAuthnAttestation(WebAuthentication* aParent)
+ : mParent(aParent)
+{}
+
+WebAuthnAttestation::~WebAuthnAttestation()
+{}
+
+JSObject*
+WebAuthnAttestation::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return WebAuthnAttestationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+WebAuthnAttestation::GetFormat(nsString& aRetVal) const
+{
+ aRetVal = mFormat;
+}
+
+void
+WebAuthnAttestation::GetClientData(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mClientData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAttestation::GetAuthenticatorData(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetVal) const
+{
+ aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+}
+
+void
+WebAuthnAttestation::GetAttestation(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRetVal) const
+{
+ // JS::RootedObject obj(aCx);
+ // obj.set();
+ aRetVal.setObject(*mAttestation.ToUint8Array(aCx));
+}
+
+nsresult
+WebAuthnAttestation::SetFormat(nsString aFormat)
+{
+ mFormat = aFormat;
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetClientData(CryptoBuffer& aBuffer)
+{
+ if (!mClientData.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetAuthenticatorData(CryptoBuffer& aBuffer)
+{
+ if (!mAuthenticatorData.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult
+WebAuthnAttestation::SetAttestation(CryptoBuffer& aBuffer)
+{
+ if (!mAttestation.Assign(aBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnAttestation.h
@@ -0,0 +1,79 @@
+/* -*- 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_WebAuthnAttestation_h
+#define mozilla_dom_WebAuthnAttestation_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebAuthnAttestation final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAttestation)
+
+public:
+ WebAuthnAttestation(WebAuthentication* aParent);
+
+protected:
+ ~WebAuthnAttestation();
+
+public:
+ WebAuthentication*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void
+ GetFormat(nsString& aRetVal) const;
+
+ void
+ GetClientData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ void
+ GetAuthenticatorData(JSContext* caCxx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ void
+ GetAttestation(JSContext* aCx, JS::MutableHandle<JS::Value> aRetVal) const;
+
+ nsresult
+ SetFormat(nsString aFormat);
+
+ nsresult
+ SetClientData(CryptoBuffer& aBuffer);
+
+ nsresult
+ SetAuthenticatorData(CryptoBuffer& aBuffer);
+
+ nsresult
+ SetAttestation(CryptoBuffer& aBuffer);
+
+private:
+ RefPtr<WebAuthentication> mParent;
+ nsString mFormat;
+ CryptoBuffer mClientData;
+ CryptoBuffer mAuthenticatorData;
+ CryptoBuffer mAttestation;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAttestation_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/WebAuthnRequest.h
@@ -0,0 +1,137 @@
+/* -*- 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_WebAuthnAsync_h
+#define mozilla_dom_WebAuthnAsync_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace dom {
+
+extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
+
+// WebAuthnRequest tracks the completion of a single WebAuthn request that
+// may run on multiple kinds of authenticators, and be subject to a deadline.
+template<class Success>
+class WebAuthnRequest {
+public:
+ WebAuthnRequest()
+ : mCancelled(false)
+ , mSuccess(false)
+ , mCountTokens(0)
+ , mTokensFailed(0)
+ , mReentrantMonitor("WebAuthnRequest")
+ {}
+
+ void AddActiveToken(const char* aCallSite)
+ {
+ MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ ("WebAuthnRequest is tracking a new token, called from [%s]",
+ aCallSite));
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ mCountTokens += 1;
+ }
+
+ bool IsComplete()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mCancelled || mSuccess ||
+ (mCountTokens > 0 && mTokensFailed == mCountTokens);
+ }
+
+ void CancelNow()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause CancelNow to get called after
+ // a success or a cancel. We only complete once.
+ if (IsComplete()) {
+ return;
+ }
+
+ mCancelled = true;
+ mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+
+ void SetFailure(nsresult aError)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause SetFailure to get called after
+ // a success or a cancel. We only complete once.
+ if (IsComplete()) {
+ return;
+ }
+
+ mTokensFailed += 1;
+ MOZ_ASSERT(mTokensFailed <= mCountTokens);
+
+ if (mTokensFailed == mCountTokens) {
+ // Provide the final error as being indicitive of the whole set.
+ mPromise.Reject(aError, __func__);
+ }
+ }
+
+ void SetSuccess(const Success& aResult)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause multiple calls to SetSuccess
+ // in succession. We will only select the earliest.
+ if (IsComplete()) {
+ return;
+ }
+
+ mSuccess = true;
+ mPromise.Resolve(aResult, __func__);
+ }
+
+ void SetDeadline(TimeDuration aDeadline)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ // TODO: Monitor the deadline and stop with a timeout error if it expires.
+ }
+
+ RefPtr<MozPromise<Success, nsresult, false>> Ensure()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ return mPromise.Ensure(__func__);
+ }
+
+ void CompleteTask()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mCountTokens == 0) {
+ // Special case for there being no tasks to complete
+ mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebAuthnRequest)
+
+private:
+ ~WebAuthnRequest() {};
+
+ bool mCancelled;
+ bool mSuccess;
+ int mCountTokens;
+ int mTokensFailed;
+ ReentrantMonitor mReentrantMonitor;
+ MozPromiseHolder<MozPromise<Success, nsresult, false>> mPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAsync_h
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,30 +1,43 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.dom += [
'NSSU2FTokenRemote.h',
+ 'ScopedCredential.h',
+ 'ScopedCredentialInfo.h',
'U2F.h',
+ 'U2FAuthenticator.h',
'USBToken.h',
+ 'WebAuthentication.h',
+ 'WebAuthnAssertion.h',
+ 'WebAuthnAttestation.h',
+ 'WebAuthnRequest.h',
]
UNIFIED_SOURCES += [
'NSSU2FTokenRemote.cpp',
+ 'ScopedCredential.cpp',
+ 'ScopedCredentialInfo.cpp',
'U2F.cpp',
'USBToken.cpp',
+ 'WebAuthentication.cpp',
+ 'WebAuthnAssertion.cpp',
+ 'WebAuthnAttestation.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
'/dom/crypto',
'/security/manager/ssl',
'/security/pkix/include',
+ '/security/pkix/lib',
]
-MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
\ No newline at end of file
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -375,8 +375,13 @@ partial interface Navigator {
readonly attribute boolean mozE10sEnabled;
};
#endif
[NoInterfaceObject, Exposed=(Window,Worker)]
interface NavigatorConcurrentHardware {
readonly attribute unsigned long long hardwareConcurrency;
};
+
+partial interface Navigator {
+ [Pref="security.webauth.w3c", SameObject]
+ readonly attribute WebAuthentication authentication;
+};
--- a/dom/webidl/U2F.webidl
+++ b/dom/webidl/U2F.webidl
@@ -21,17 +21,17 @@ typedef sequence<Transport> Transports;
enum Transport {
"bt",
"ble",
"nfc",
"usb"
};
-dictionary ClientData {
+dictionary U2FClientData {
DOMString typ; // Spelling is from the specification
DOMString challenge;
DOMString origin;
// cid_pubkey for Token Binding is not implemented
};
dictionary RegisterRequest {
DOMString version;
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WebAuthentication.webidl
@@ -0,0 +1,113 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://www.w3.org/TR/webauthn/
+ */
+
+/***** Interfaces to Data *****/
+
+[SecureContext]
+interface ScopedCredentialInfo {
+ readonly attribute ScopedCredential credential;
+ readonly attribute WebAuthnAttestation attestation;
+};
+
+dictionary Account {
+ required DOMString rpDisplayName;
+ required DOMString displayName;
+ required DOMString id;
+ DOMString name;
+ DOMString imageURL;
+};
+
+typedef (boolean or DOMString) WebAuthnAlgorithmID; // Fix when upstream there's a definition of how to serialize AlgorithmIdentifier
+
+dictionary ScopedCredentialParameters {
+ required ScopedCredentialType type;
+ required WebAuthnAlgorithmID algorithm; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+};
+
+dictionary ScopedCredentialOptions {
+ unsigned long timeoutSeconds;
+ USVString rpId;
+ sequence<ScopedCredentialDescriptor> excludeList;
+ WebAuthnExtensions extensions;
+};
+
+[SecureContext]
+interface WebAuthnAssertion {
+ readonly attribute ScopedCredential credential;
+ readonly attribute ArrayBuffer clientData;
+ readonly attribute ArrayBuffer authenticatorData;
+ readonly attribute ArrayBuffer signature;
+};
+
+dictionary AssertionOptions {
+ unsigned long timeoutSeconds;
+ USVString rpId;
+ sequence<ScopedCredentialDescriptor> allowList;
+ WebAuthnExtensions extensions;
+};
+
+dictionary WebAuthnExtensions {
+};
+
+[SecureContext]
+interface WebAuthnAttestation {
+ readonly attribute USVString format;
+ readonly attribute ArrayBuffer clientData;
+ readonly attribute ArrayBuffer authenticatorData;
+ readonly attribute any attestation;
+};
+
+// Renamed from "ClientData" to avoid a collision with U2F
+dictionary WebAuthnClientData {
+ required DOMString challenge;
+ required DOMString origin;
+ required WebAuthnAlgorithmID hashAlg; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+ DOMString tokenBinding;
+ WebAuthnExtensions extensions;
+};
+
+enum ScopedCredentialType {
+ "ScopedCred"
+};
+
+[SecureContext]
+interface ScopedCredential {
+ readonly attribute ScopedCredentialType type;
+ readonly attribute ArrayBuffer id;
+};
+
+dictionary ScopedCredentialDescriptor {
+ required ScopedCredentialType type;
+ required BufferSource id;
+ sequence <WebAuthnTransport> transports;
+};
+
+// Renamed from "Transport" to avoid a collision with U2F
+enum WebAuthnTransport {
+ "usb",
+ "nfc",
+ "ble"
+};
+
+/***** The Main API *****/
+
+[SecureContext]
+interface WebAuthentication {
+ Promise<ScopedCredentialInfo> makeCredential (
+ Account accountInformation,
+ sequence<ScopedCredentialParameters> cryptoParameters,
+ BufferSource attestationChallenge,
+ optional ScopedCredentialOptions options
+ );
+
+ Promise<WebAuthnAssertion> getAssertion (
+ BufferSource assertionChallenge,
+ optional AssertionOptions options
+ );
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -557,16 +557,17 @@ WEBIDL_FILES = [
'VideoPlaybackQuality.webidl',
'VideoStreamTrack.webidl',
'VideoTrack.webidl',
'VideoTrackList.webidl',
'VRDisplay.webidl',
'VTTCue.webidl',
'VTTRegion.webidl',
'WaveShaperNode.webidl',
+ 'WebAuthentication.webidl',
'WebComponents.webidl',
'WebGL2RenderingContext.webidl',
'WebGLRenderingContext.webidl',
'WebKitCSSMatrix.webidl',
'WebSocket.webidl',
'WheelEvent.webidl',
'WidevineCDMManifest.webidl',
'WindowOrWorkerGlobalScope.webidl',
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -88,16 +88,17 @@ pref("security.pki.netscape_step_up_poli
// Configures Certificate Transparency support mode:
// 0: Fully disabled.
// 1: Only collect telemetry. CT qualification checks are not performed.
pref("security.pki.certificate_transparency.mode", 1);
pref("security.webauth.u2f", false);
pref("security.webauth.u2f_enable_softtoken", false);
pref("security.webauth.u2f_enable_usbtoken", false);
+pref("security.webauth.w3c", false);
pref("security.ssl.errorReporting.enabled", true);
pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
pref("security.ssl.errorReporting.automatic", false);
// Impose a maximum age on HPKP headers, to avoid sites getting permanently
// blacking themselves out by setting a bad pin. (60 days by default)
// https://tools.ietf.org/html/rfc7469#section-4.1