--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -6,59 +6,639 @@
#include "hasht.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CryptoBuffer.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/U2FBinding.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
-#include "nsIEffectiveTLDService.h"
#include "nsNetCID.h"
#include "nsNSSComponent.h"
#include "nsURLParsers.h"
#include "pk11pub.h"
using mozilla::dom::ContentChild;
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
-};
-
#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
#define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f_enable_usbtoken"
-const nsString U2F::FinishEnrollment =
- NS_LITERAL_STRING("navigator.id.finishEnrollment");
-
-const nsString U2F::GetAssertion =
- NS_LITERAL_STRING("navigator.id.getAssertion");
+NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
+NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
-static mozilla::LazyLogModule gU2FLog("fido_u2f");
+static mozilla::LazyLogModule gU2FLog("webauth_u2f");
+
+template <class CB, class Rsp>
+void
+SendError(CB* aCallback, ErrorCode aErrorCode)
+{
+ Rsp response;
+ response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+
+ ErrorResult rv;
+ aCallback->Call(response, rv);
+ NS_WARN_IF(rv.Failed());
+ // Useful exceptions already got reported.
+ rv.SuppressException();
+}
+
+static nsresult
+AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
+ const nsAString& aChallenge, CryptoBuffer& aClientData)
+{
+ ClientData 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;
+ }
+
+ if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+NSSTokenIsCompatible(nsINSSU2FToken* aNSSToken, const nsString& aVersionString,
+ bool* aIsCompatible)
+{
+ MOZ_ASSERT(aIsCompatible);
+
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(aNSSToken);
+ return aNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static nsresult
+NSSTokenIsRegistered(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+ bool* aIsRegistered)
+{
+ MOZ_ASSERT(aIsRegistered);
+
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(aNSSToken);
+ return aNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
+ aIsRegistered);
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static nsresult
+NSSTokenSign(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+ CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
+ CryptoBuffer& aSignatureData)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(aNSSToken);
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv = aNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
+ aChallenge.Elements(), aChallenge.Length(),
+ aKeyHandle.Elements(), aKeyHandle.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(buffer);
+ aSignatureData.Assign(buffer, bufferlen);
+ free(buffer);
+ return NS_OK;
+ }
+
+ nsTArray<uint8_t> signatureBuffer;
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
+ &signatureBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aSignatureData.Assign(signatureBuffer);
+ return NS_OK;
+}
+
+static nsresult
+NSSTokenRegister(nsINSSU2FToken* aNSSToken, CryptoBuffer& aApplication,
+ CryptoBuffer& aChallenge, CryptoBuffer& aRegistrationData)
+{
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(aNSSToken);
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ nsresult rv;
+ rv = aNSSToken->Register(aApplication.Elements(), aApplication.Length(),
+ aChallenge.Elements(), aChallenge.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(buffer);
+ aRegistrationData.Assign(buffer, bufferlen);
+ free(buffer);
+ return NS_OK;
+ }
+
+ nsTArray<uint8_t> registrationBuffer;
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+ if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
+ ®istrationBuffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aRegistrationData.Assign(registrationBuffer);
+ return NS_OK;
+}
+
+U2FTask::U2FTask(const nsAString& aOrigin, const nsAString& aAppId)
+ : mOrigin(aOrigin)
+ , mAppId(aAppId)
+{}
+
+U2FTask::~U2FTask()
+{}
+
+U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin,
+ const nsAString& aAppId,
+ const Sequence<RegisterRequest>& aRegisterRequests,
+ const Sequence<RegisteredKey>& aRegisteredKeys,
+ U2FRegisterCallback* aCallback,
+ const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+ : U2FTask(aOrigin, aAppId)
+ , mRegisterRequests(aRegisterRequests)
+ , mRegisteredKeys(aRegisteredKeys)
+ , mCallback(aCallback)
+ , mNSSToken(aNSSToken)
+{}
+
+U2FRegisterTask::~U2FRegisterTask()
+{
+ nsNSSShutDownPreventionLock locker;
+
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(calledFromObject);
+}
+
+void
+U2FRegisterTask::ReturnError(ErrorCode aCode)
+{
+ SendError<U2FRegisterCallback, RegisterResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FRegisterTask::Run()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // TODO: Implement USB Tokens in Bug 1245527
+ const bool softTokenEnabled =
+ Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+ for (size_t i = 0; i < mRegisteredKeys.Length(); ++i) {
+ RegisteredKey request(mRegisteredKeys[i]);
+
+ // Check for required attributes
+ if (!(request.mKeyHandle.WasPassed() &&
+ request.mVersion.WasPassed())) {
+ continue;
+ }
+
+ // Do not permit an individual RegisteredKey to assert a different AppID
+ if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+ continue;
+ }
+
+ // Decode the key handle
+ CryptoBuffer keyHandle;
+ nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReturnError(ErrorCode::BAD_REQUEST);
+ return NS_ERROR_FAILURE;
+ }
+
+ // We ignore mTransports, as it is intended to be used for sorting the
+ // available devices by preference, but is not an exclusion factor.
+
+ bool isCompatible = false;
+ bool isRegistered = false;
+
+ // Determine if the provided keyHandle is registered at any device. If so,
+ // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
+ if (softTokenEnabled) {
+ rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+ &isCompatible);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (isCompatible && isRegistered) {
+ ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+ return NS_OK;
+ }
+ }
+ }
+
+ // Search the requests in order for the first some token can fulfill
+ for (size_t i = 0; i < mRegisterRequests.Length(); ++i) {
+ RegisterRequest request(mRegisterRequests[i]);
+
+ // Check for equired attributes
+ if (!(request.mVersion.WasPassed() &&
+ request.mChallenge.WasPassed())) {
+ continue;
+ }
+
+ CryptoBuffer clientData;
+ nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
+ request.mChallenge.Value(),
+ clientData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+ SECStatus srv;
+ nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+ CryptoBuffer appParam;
+ CryptoBuffer challengeParam;
+ if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+ !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+ reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+ cAppId.Length());
+ if (srv != SECSuccess) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+ clientData.Elements(), clientData.Length());
+ if (srv != SECSuccess) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the registration data from the token
+ CryptoBuffer regData;
+ bool registerSuccess = false;
+ bool isCompatible = false;
+
+ if (!registerSuccess && softTokenEnabled) {
+ rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+ &isCompatible);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (isCompatible) {
+ rv = NSSTokenRegister(mNSSToken, appParam, challengeParam, regData);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+ registerSuccess = true;
+ }
+ }
+
+ if (!registerSuccess) {
+ // Try another request
+ continue;
+ }
+
+ // Assemble a response object to return
+ nsString clientDataBase64, registrationDataBase64;
+ nsresult rvClientData =
+ clientData.ToJwkBase64(clientDataBase64);
+ nsresult rvRegistrationData =
+ regData.ToJwkBase64(registrationDataBase64);
+ if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+ NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ RegisterResponse response;
+ response.mClientData.Construct(clientDataBase64);
+ response.mRegistrationData.Construct(registrationDataBase64);
+ response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+ ErrorResult result;
+ mCallback->Call(response, result);
+ NS_WARN_IF(result.Failed());
+ // Useful exceptions already got reported.
+ result.SuppressException();
+ return NS_OK;
+ }
+
+ // Nothing could satisfy
+ ReturnError(ErrorCode::BAD_REQUEST);
+ return NS_ERROR_FAILURE;
+}
+
+U2FSignTask::U2FSignTask(const nsAString& aOrigin,
+ const nsAString& aAppId,
+ const nsAString& aChallenge,
+ const Sequence<RegisteredKey>& aRegisteredKeys,
+ U2FSignCallback* aCallback,
+ const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+ : U2FTask(aOrigin, aAppId)
+ , mChallenge(aChallenge)
+ , mRegisteredKeys(aRegisteredKeys)
+ , mCallback(aCallback)
+ , mNSSToken(aNSSToken)
+{}
+
+U2FSignTask::~U2FSignTask()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ shutdown(calledFromObject);
+}
+
+void
+U2FSignTask::ReturnError(ErrorCode aCode)
+{
+ SendError<U2FSignCallback, SignResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FSignTask::Run()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // TODO: Implement USB Tokens in Bug 1245527
+ const bool softTokenEnabled =
+ Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+ // Search the requests for one a token can fulfill
+ for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) {
+ RegisteredKey request(mRegisteredKeys[i]);
+
+ // Check for required attributes
+ if (!(request.mVersion.WasPassed() &&
+ request.mKeyHandle.WasPassed())) {
+ continue;
+ }
+
+ // Do not permit an individual RegisteredKey to assert a different AppID
+ if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+ continue;
+ }
+
+ // Assemble a clientData object
+ CryptoBuffer clientData;
+ nsresult rv = AssembleClientData(mOrigin, kGetAssertion, mChallenge,
+ clientData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+ SECStatus srv;
+ nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+ CryptoBuffer appParam;
+ CryptoBuffer challengeParam;
+ if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+ !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+ reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+ cAppId.Length());
+ if (srv != SECSuccess) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+ clientData.Elements(), clientData.Length());
+ if (srv != SECSuccess) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Decode the key handle
+ CryptoBuffer keyHandle;
+ rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the signature from the token
+ CryptoBuffer signatureData;
+ bool signSuccess = false;
+
+ // We ignore mTransports, as it is intended to be used for sorting the
+ // available devices by preference, but is not an exclusion factor.
+
+ if (!signSuccess && softTokenEnabled) {
+ bool isCompatible = false;
+ bool isRegistered = false;
+
+ rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+ &isCompatible);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (isCompatible && isRegistered) {
+ rv = NSSTokenSign(mNSSToken, keyHandle, appParam, challengeParam,
+ signatureData);
+ if (NS_FAILED(rv)) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+ signSuccess = true;
+ }
+ }
+
+ if (!signSuccess) {
+ // Try another request
+ continue;
+ }
+
+ // Assemble a response object to return
+ nsString clientDataBase64, signatureDataBase64;
+ nsresult rvClientData =
+ clientData.ToJwkBase64(clientDataBase64);
+ nsresult rvSignatureData =
+ signatureData.ToJwkBase64(signatureDataBase64);
+ if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+ NS_WARN_IF(NS_FAILED(rvSignatureData))) {
+ ReturnError(ErrorCode::OTHER_ERROR);
+ return NS_ERROR_FAILURE;
+ }
+ SignResponse response;
+ response.mKeyHandle.Construct(request.mKeyHandle.Value());
+ response.mClientData.Construct(clientDataBase64);
+ response.mSignatureData.Construct(signatureDataBase64);
+ response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+ ErrorResult result;
+ mCallback->Call(response, result);
+ NS_WARN_IF(result.Failed());
+ // Useful exceptions already got reported.
+ result.SuppressException();
+ return NS_OK;
+ }
+
+ // Nothing could satisfy
+ ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+ return NS_ERROR_FAILURE;
+}
+
+// EvaluateAppIDAndRunTask determines whether the supplied FIDO AppID is valid for
+// the current FacetID, e.g., the current origin.
+// See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
+// for a description of the algorithm.
+static void
+EvaluateAppIDAndRunTask(U2FTask* aTask)
+{
+ MOZ_ASSERT(aTask);
+
+ nsCOMPtr<nsIURLParser> urlParser =
+ do_GetService(NS_STDURLPARSER_CONTRACTID);
+
+ MOZ_ASSERT(urlParser);
+
+ uint32_t facetSchemePos;
+ int32_t facetSchemeLen;
+ uint32_t facetAuthPos;
+ int32_t facetAuthLen;
+ // Facet is the specification's way of referring to the web origin.
+ nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(aTask->mOrigin);
+ nsresult rv = urlParser->ParseURL(facetUrl.get(), aTask->mOrigin.Length(),
+ &facetSchemePos, &facetSchemeLen,
+ &facetAuthPos, &facetAuthLen,
+ nullptr, nullptr); // ignore path
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aTask->ReturnError(ErrorCode::BAD_REQUEST);
+ return;
+ }
+
+ nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
+ nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
+
+ uint32_t appIdSchemePos;
+ int32_t appIdSchemeLen;
+ uint32_t appIdAuthPos;
+ int32_t appIdAuthLen;
+ // AppID is user-supplied. It's quite possible for this parse to fail.
+ nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aTask->mAppId);
+ rv = urlParser->ParseURL(appIdUrl.get(), aTask->mAppId.Length(),
+ &appIdSchemePos, &appIdSchemeLen,
+ &appIdAuthPos, &appIdAuthLen,
+ nullptr, nullptr); // ignore path
+ if (NS_FAILED(rv)) {
+ aTask->ReturnError(ErrorCode::BAD_REQUEST);
+ return;
+ }
+
+ nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
+ nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
+
+ // If the facetId (origin) is not HTTPS, reject
+ if (!facetScheme.LowerCaseEqualsLiteral("https")) {
+ aTask->ReturnError(ErrorCode::BAD_REQUEST);
+ return;
+ }
+
+ // If the appId is empty or null, overwrite it with the facetId and accept
+ if (aTask->mAppId.IsEmpty() || aTask->mAppId.EqualsLiteral("null")) {
+ aTask->mAppId.Assign(aTask->mOrigin);
+ aTask->Run();
+ return;
+ }
+
+ // if the appId URL is not HTTPS, reject.
+ if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
+ aTask->ReturnError(ErrorCode::BAD_REQUEST);
+ return;
+ }
+
+ // If the facetId and the appId auths match, accept
+ if (facetAuth == appIdAuth) {
+ aTask->Run();
+ return;
+ }
+
+ // TODO(Bug 1244959) Implement the remaining algorithm.
+ aTask->ReturnError(ErrorCode::BAD_REQUEST);
+ return;
+}
U2F::U2F()
{}
U2F::~U2F()
{
nsNSSShutDownPreventionLock locker;
@@ -86,16 +666,17 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
nsIPrincipal* principal = doc->NodePrincipal();
aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (NS_WARN_IF(mOrigin.IsEmpty())) {
+ aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!EnsureNSSInitializedChromeOrContent()) {
MOZ_LOG(gU2FLog, LogLevel::Debug, ("Failed to get NSS context for U2F"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
@@ -109,607 +690,42 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
}
aRv = mUSBToken.Init();
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
-nsresult
-U2F::NSSTokenIsCompatible(const nsString& aVersionString, bool* aIsCompatible)
-{
- MOZ_ASSERT(aIsCompatible);
-
- if (XRE_IsParentProcess()) {
- MOZ_ASSERT(mNSSToken);
- return mNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
- }
-
- ContentChild* cc = ContentChild::GetSingleton();
- MOZ_ASSERT(cc);
- if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
- return NS_ERROR_FAILURE;
- }
- return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenIsRegistered(CryptoBuffer& aKeyHandle, bool* aIsRegistered)
-{
- MOZ_ASSERT(aIsRegistered);
-
- if (XRE_IsParentProcess()) {
- MOZ_ASSERT(mNSSToken);
- return mNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
- aIsRegistered);
- }
-
- ContentChild* cc = ContentChild::GetSingleton();
- MOZ_ASSERT(cc);
- if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
- return NS_ERROR_FAILURE;
- }
- return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenRegister(CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
- CryptoBuffer& aRegistrationData)
-{
- if (XRE_IsParentProcess()) {
- MOZ_ASSERT(mNSSToken);
- uint8_t* buffer;
- uint32_t bufferlen;
- nsresult rv;
- rv = mNSSToken->Register(aApplication.Elements(), aApplication.Length(),
- aChallenge.Elements(), aChallenge.Length(),
- &buffer, &bufferlen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
-
- MOZ_ASSERT(buffer);
- aRegistrationData.Assign(buffer, bufferlen);
- free(buffer);
- return NS_OK;
- }
-
- nsTArray<uint8_t> registrationBuffer;
- ContentChild* cc = ContentChild::GetSingleton();
- MOZ_ASSERT(cc);
- if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
- ®istrationBuffer)) {
- return NS_ERROR_FAILURE;
- }
-
- aRegistrationData.Assign(registrationBuffer);
- return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenSign(CryptoBuffer& aKeyHandle, CryptoBuffer& aApplication,
- CryptoBuffer& aChallenge, CryptoBuffer& aSignatureData)
-{
- if (XRE_IsParentProcess()) {
- MOZ_ASSERT(mNSSToken);
- uint8_t* buffer;
- uint32_t bufferlen;
- nsresult rv = mNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
- aChallenge.Elements(), aChallenge.Length(),
- aKeyHandle.Elements(), aKeyHandle.Length(),
- &buffer, &bufferlen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
-
- MOZ_ASSERT(buffer);
- aSignatureData.Assign(buffer, bufferlen);
- free(buffer);
- return NS_OK;
- }
-
- nsTArray<uint8_t> signatureBuffer;
- ContentChild* cc = ContentChild::GetSingleton();
- MOZ_ASSERT(cc);
- if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
- &signatureBuffer)) {
- return NS_ERROR_FAILURE;
- }
-
- aSignatureData.Assign(signatureBuffer);
- return NS_OK;
-}
-
-nsresult
-U2F::AssembleClientData(const nsAString& aTyp,
- const nsAString& aChallenge,
- CryptoBuffer& aClientData) const
-{
- ClientData clientDataObject;
- clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
- clientDataObject.mChallenge.Construct(aChallenge);
- clientDataObject.mOrigin.Construct(mOrigin);
-
- nsAutoString json;
- if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
- return NS_ERROR_FAILURE;
- }
-
- if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
- return NS_ERROR_FAILURE;
- }
-
- return NS_OK;
-}
-
-bool
-U2F::ValidAppID(/* in/out */ nsString& aAppId) const
-{
- nsCOMPtr<nsIURLParser> urlParser =
- do_GetService(NS_STDURLPARSER_CONTRACTID);
- nsCOMPtr<nsIEffectiveTLDService> tldService =
- do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-
- MOZ_ASSERT(urlParser);
- MOZ_ASSERT(tldService);
-
- uint32_t facetSchemePos;
- int32_t facetSchemeLen;
- uint32_t facetAuthPos;
- int32_t facetAuthLen;
- // Facet is the specification's way of referring to the web origin.
- nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
- nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
- &facetSchemePos, &facetSchemeLen,
- &facetAuthPos, &facetAuthLen,
- nullptr, nullptr); // ignore path
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return false;
- }
-
- nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
- nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
-
- uint32_t appIdSchemePos;
- int32_t appIdSchemeLen;
- uint32_t appIdAuthPos;
- int32_t appIdAuthLen;
- nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aAppId);
- rv = urlParser->ParseURL(appIdUrl.get(), aAppId.Length(),
- &appIdSchemePos, &appIdSchemeLen,
- &appIdAuthPos, &appIdAuthLen,
- nullptr, nullptr); // ignore path
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return false;
- }
-
- nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
- nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
-
- // If the facetId (origin) is not HTTPS, reject
- if (!facetScheme.LowerCaseEqualsLiteral("https")) {
- return false;
- }
-
- // If the appId is empty or null, overwrite it with the facetId and accept
- if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
- aAppId.Assign(mOrigin);
- return true;
- }
-
- // if the appId URL is not HTTPS, reject.
- if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
- return false;
- }
-
- // If the facetId and the appId auths match, accept
- if (facetAuth == appIdAuth) {
- return true;
- }
-
- // TODO(Bug 1244959) Implement the remaining algorithm.
- return false;
-}
-
-template <class CB, class Rsp>
-void
-SendError(CB& aCallback, ErrorCode aErrorCode)
-{
- Rsp response;
- response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
-
- ErrorResult rv;
- aCallback.Call(response, rv);
- NS_WARN_IF(rv.Failed());
- // Useful exceptions already got reported.
- rv.SuppressException();
-}
-
void
U2F::Register(const nsAString& aAppId,
const Sequence<RegisterRequest>& aRegisterRequests,
const Sequence<RegisteredKey>& aRegisteredKeys,
U2FRegisterCallback& aCallback,
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
ErrorResult& aRv)
{
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- const bool softTokenEnabled =
- Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
- const bool usbTokenEnabled =
- Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
- nsAutoString appId(aAppId);
-
- // Verify the global appId first.
- if (!ValidAppID(appId)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::BAD_REQUEST);
- return;
- }
-
- for (size_t i = 0; i < aRegisteredKeys.Length(); ++i) {
- RegisteredKey request(aRegisteredKeys[i]);
-
- // Check for required attributes
- if (!(request.mKeyHandle.WasPassed() &&
- request.mVersion.WasPassed())) {
- continue;
- }
-
- // Verify the appId for this Registered Key, if set
- if (request.mAppId.WasPassed() &&
- !ValidAppID(request.mAppId.Value())) {
- continue;
- }
-
- // Decode the key handle
- CryptoBuffer keyHandle;
- nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
- if (NS_WARN_IF(NS_FAILED(rv))) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::BAD_REQUEST);
- return;
- }
-
- // We ignore mTransports, as it is intended to be used for sorting the
- // available devices by preference, but is not an exclusion factor.
-
- bool isCompatible = false;
- bool isRegistered = false;
-
- // Determine if the provided keyHandle is registered at any device. If so,
- // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
- if (usbTokenEnabled &&
- mUSBToken.IsCompatibleVersion(request.mVersion.Value()) &&
- mUSBToken.IsRegistered(keyHandle)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::DEVICE_INELIGIBLE);
- return;
- }
- if (softTokenEnabled) {
- rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
- if (NS_FAILED(rv)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
- if (NS_FAILED(rv)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- if (isCompatible && isRegistered) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::DEVICE_INELIGIBLE);
- return;
- }
- }
- }
-
- // Search the requests in order for the first some token can fulfill
- for (size_t i = 0; i < aRegisterRequests.Length(); ++i) {
- RegisterRequest request(aRegisterRequests[i]);
-
- // Check for equired attributes
- if (!(request.mVersion.WasPassed() &&
- request.mChallenge.WasPassed())) {
- continue;
- }
+ RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, aAppId,
+ aRegisterRequests,
+ aRegisteredKeys,
+ &aCallback,
+ mNSSToken);
- CryptoBuffer clientData;
- nsresult rv = AssembleClientData(FinishEnrollment,
- request.mChallenge.Value(),
- clientData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- // Hash the AppID and the ClientData into the AppParam and ChallengeParam
- SECStatus srv;
- nsCString cAppId = NS_ConvertUTF16toUTF8(appId);
- CryptoBuffer appParam;
- CryptoBuffer challengeParam;
- if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
- !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
- reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
- cAppId.Length());
- if (srv != SECSuccess) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
- clientData.Elements(), clientData.Length());
- if (srv != SECSuccess) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- // Get the registration data from the token
- CryptoBuffer registrationData;
- bool registerSuccess = false;
- bool isCompatible = false;
- if (usbTokenEnabled) {
- // TODO: Implement in Bug 1245527
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- if (!registerSuccess && softTokenEnabled) {
- rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
- if (NS_FAILED(rv)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- if (isCompatible) {
- rv = NSSTokenRegister(appParam, challengeParam, registrationData);
- if (NS_FAILED(rv)) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
- registerSuccess = true;
- }
- }
-
- if (!registerSuccess) {
- // Try another request
- continue;
- }
-
- // Assemble a response object to return
- nsString clientDataBase64, registrationDataBase64;
- nsresult rvClientData =
- clientData.ToJwkBase64(clientDataBase64);
- nsresult rvRegistrationData =
- registrationData.ToJwkBase64(registrationDataBase64);
- if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
- NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- RegisterResponse response;
- response.mClientData.Construct(clientDataBase64);
- response.mRegistrationData.Construct(registrationDataBase64);
- response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
- ErrorResult result;
- aCallback.Call(response, result);
- NS_WARN_IF(result.Failed());
- // Useful exceptions already got reported.
- result.SuppressException();
- return;
- }
-
- // Nothing could satisfy
- SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
- ErrorCode::BAD_REQUEST);
- return;
+ EvaluateAppIDAndRunTask(registerTask);
}
void
U2F::Sign(const nsAString& aAppId,
const nsAString& aChallenge,
const Sequence<RegisteredKey>& aRegisteredKeys,
U2FSignCallback& aCallback,
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
ErrorResult& aRv)
{
- nsNSSShutDownPreventionLock locker;
- if (isAlreadyShutDown()) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- const bool softTokenEnabled =
- Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
- const bool usbTokenEnabled =
- Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
- nsAutoString appId(aAppId);
-
- // Verify the global appId first.
- if (!ValidAppID(appId)) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::BAD_REQUEST);
- return;
- }
-
- // Search the requests for one a token can fulfill
- for (size_t i = 0; i < aRegisteredKeys.Length(); i += 1) {
- RegisteredKey request(aRegisteredKeys[i]);
-
- // Check for required attributes
- if (!(request.mVersion.WasPassed() &&
- request.mKeyHandle.WasPassed())) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- continue;
- }
-
- // Allow an individual RegisteredKey to assert a different AppID
- nsAutoString regKeyAppId(appId);
- if (request.mAppId.WasPassed()) {
- regKeyAppId.Assign(request.mAppId.Value());
- if (!ValidAppID(regKeyAppId)) {
- continue;
- }
- }
-
- // Assemble a clientData object
- CryptoBuffer clientData;
- nsresult rv = AssembleClientData(GetAssertion, aChallenge, clientData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- // Hash the AppID and the ClientData into the AppParam and ChallengeParam
- SECStatus srv;
- nsCString cAppId = NS_ConvertUTF16toUTF8(regKeyAppId);
- CryptoBuffer appParam;
- CryptoBuffer challengeParam;
- if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
- !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
- reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
- cAppId.Length());
- if (srv != SECSuccess) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
- clientData.Elements(), clientData.Length());
- if (srv != SECSuccess) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
+ RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, aAppId, aChallenge,
+ aRegisteredKeys, &aCallback,
+ mNSSToken);
- // Decode the key handle
- CryptoBuffer keyHandle;
- rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
- if (NS_WARN_IF(NS_FAILED(rv))) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- // Get the signature from the token
- CryptoBuffer signatureData;
- bool signSuccess = false;
-
- // We ignore mTransports, as it is intended to be used for sorting the
- // available devices by preference, but is not an exclusion factor.
-
- if (usbTokenEnabled &&
- mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
- // TODO: Implement in Bug 1245527
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- if (!signSuccess && softTokenEnabled) {
- bool isCompatible = false;
- bool isRegistered = false;
-
- rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
- if (NS_FAILED(rv)) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
- if (NS_FAILED(rv)) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
-
- if (isCompatible && isRegistered) {
- rv = NSSTokenSign(keyHandle, appParam, challengeParam, signatureData);
- if (NS_FAILED(rv)) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
- signSuccess = true;
- }
- }
-
- if (!signSuccess) {
- // Try another request
- continue;
- }
-
- // Assemble a response object to return
- nsString clientDataBase64, signatureDataBase64;
- nsresult rvClientData =
- clientData.ToJwkBase64(clientDataBase64);
- nsresult rvSignatureData =
- signatureData.ToJwkBase64(signatureDataBase64);
- if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
- NS_WARN_IF(NS_FAILED(rvSignatureData))) {
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::OTHER_ERROR);
- return;
- }
- SignResponse response;
- response.mKeyHandle.Construct(request.mKeyHandle.Value());
- response.mClientData.Construct(clientDataBase64);
- response.mSignatureData.Construct(signatureDataBase64);
- response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
- ErrorResult result;
- aCallback.Call(response, result);
- NS_WARN_IF(result.Failed());
- // Useful exceptions already got reported.
- result.SuppressException();
- return;
- }
-
- // Nothing could satisfy
- SendError<U2FSignCallback, SignResponse>(aCallback,
- ErrorCode::DEVICE_INELIGIBLE);
- return;
+ EvaluateAppIDAndRunTask(signTask);
}
} // namespace dom
} // namespace mozilla