mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-22 23:27:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
150
dom/u2f/NSSU2FTokenRemote.cpp
Normal file
150
dom/u2f/NSSU2FTokenRemote.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
|
||||
#include "NSSU2FTokenRemote.h"
|
||||
|
||||
using mozilla::dom::ContentChild;
|
||||
|
||||
NS_IMPL_ISUPPORTS(NSSU2FTokenRemote, nsIU2FToken)
|
||||
|
||||
static mozilla::LazyLogModule gWebauthLog("webauth_u2f");
|
||||
|
||||
NSSU2FTokenRemote::NSSU2FTokenRemote()
|
||||
{}
|
||||
|
||||
NSSU2FTokenRemote::~NSSU2FTokenRemote()
|
||||
{}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NSSU2FTokenRemote::IsCompatibleVersion(const nsAString& aVersionString,
|
||||
bool* aIsCompatible)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aIsCompatible);
|
||||
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
MOZ_ASSERT(cc);
|
||||
if (!cc->SendNSSU2FTokenIsCompatibleVersion(
|
||||
nsString(aVersionString), aIsCompatible)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NSSU2FTokenRemote::IsRegistered(uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
|
||||
bool* aIsRegistered)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aKeyHandle);
|
||||
NS_ENSURE_ARG_POINTER(aIsRegistered);
|
||||
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle,
|
||||
aKeyHandleLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
MOZ_ASSERT(cc);
|
||||
if (!cc->SendNSSU2FTokenIsRegistered(keyHandle, aIsRegistered)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NSSU2FTokenRemote::Register(uint8_t* aApplication,
|
||||
uint32_t aApplicationLen,
|
||||
uint8_t* aChallenge,
|
||||
uint32_t aChallengeLen,
|
||||
uint8_t** aRegistration,
|
||||
uint32_t* aRegistrationLen)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aApplication);
|
||||
NS_ENSURE_ARG_POINTER(aChallenge);
|
||||
NS_ENSURE_ARG_POINTER(aRegistration);
|
||||
NS_ENSURE_ARG_POINTER(aRegistrationLen);
|
||||
|
||||
nsTArray<uint8_t> application;
|
||||
if (!application.ReplaceElementsAt(0, application.Length(), aApplication,
|
||||
aApplicationLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
nsTArray<uint8_t> challenge;
|
||||
if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge,
|
||||
aChallengeLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> registrationBuffer;
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
MOZ_ASSERT(cc);
|
||||
if (!cc->SendNSSU2FTokenRegister(application, challenge,
|
||||
®istrationBuffer)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
size_t dataLen = registrationBuffer.Length();
|
||||
uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
|
||||
if (NS_WARN_IF(!tmp)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(tmp, registrationBuffer.Elements(), dataLen);
|
||||
*aRegistration = tmp;
|
||||
*aRegistrationLen = dataLen;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NSSU2FTokenRemote::Sign(uint8_t* aApplication, uint32_t aApplicationLen,
|
||||
uint8_t* aChallenge, uint32_t aChallengeLen,
|
||||
uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
|
||||
uint8_t** aSignature, uint32_t* aSignatureLen)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aApplication);
|
||||
NS_ENSURE_ARG_POINTER(aChallenge);
|
||||
NS_ENSURE_ARG_POINTER(aKeyHandle);
|
||||
NS_ENSURE_ARG_POINTER(aSignature);
|
||||
NS_ENSURE_ARG_POINTER(aSignatureLen);
|
||||
|
||||
nsTArray<uint8_t> application;
|
||||
if (!application.ReplaceElementsAt(0, application.Length(), aApplication,
|
||||
aApplicationLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> challenge;
|
||||
if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge,
|
||||
aChallengeLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle,
|
||||
aKeyHandleLen)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> signatureBuffer;
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
MOZ_ASSERT(cc);
|
||||
if (!cc->SendNSSU2FTokenSign(application, challenge, keyHandle,
|
||||
&signatureBuffer)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
size_t dataLen = signatureBuffer.Length();
|
||||
uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
|
||||
if (NS_WARN_IF(!tmp)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(tmp, signatureBuffer.Elements(), dataLen);
|
||||
*aSignature = tmp;
|
||||
*aSignatureLen = dataLen;
|
||||
return NS_OK;
|
||||
}
|
||||
24
dom/u2f/NSSU2FTokenRemote.h
Normal file
24
dom/u2f/NSSU2FTokenRemote.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef NSSU2FTokenRemote_h
|
||||
#define NSSU2FTokenRemote_h
|
||||
|
||||
#include "nsIU2FToken.h"
|
||||
|
||||
class NSSU2FTokenRemote : public nsIU2FToken
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIU2FTOKEN
|
||||
|
||||
NSSU2FTokenRemote();
|
||||
|
||||
private:
|
||||
virtual ~NSSU2FTokenRemote();
|
||||
};
|
||||
|
||||
#endif // NSSU2FTokenRemote_h
|
||||
1057
dom/u2f/U2F.cpp
Normal file
1057
dom/u2f/U2F.cpp
Normal file
File diff suppressed because it is too large
Load diff
327
dom/u2f/U2F.h
Normal file
327
dom/u2f/U2F.h
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_U2F_h
|
||||
#define mozilla_dom_U2F_h
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/Nullable.h"
|
||||
#include "mozilla/dom/U2FBinding.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIU2FToken.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
#include "USBToken.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class U2FRegisterCallback;
|
||||
class U2FSignCallback;
|
||||
|
||||
// Defined in U2FBinding.h by the U2F.webidl; their use requires a JSContext.
|
||||
struct RegisterRequest;
|
||||
struct RegisteredKey;
|
||||
|
||||
// These structs are analogs to the WebIDL versions, but can be used on worker
|
||||
// threads which lack a JSContext.
|
||||
struct LocalRegisterRequest
|
||||
{
|
||||
nsString mChallenge;
|
||||
nsString mVersion;
|
||||
CryptoBuffer mClientData;
|
||||
};
|
||||
|
||||
struct LocalRegisteredKey
|
||||
{
|
||||
nsString mKeyHandle;
|
||||
nsString mVersion;
|
||||
Nullable<nsString> mAppId;
|
||||
// TODO: Support transport preferences
|
||||
// Nullable<nsTArray<Transport>> mTransports;
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
public:
|
||||
explicit U2FPrepTask(const Authenticator& aAuthenticator);
|
||||
|
||||
RefPtr<U2FPrepPromise> Execute();
|
||||
|
||||
protected:
|
||||
virtual ~U2FPrepTask();
|
||||
|
||||
Authenticator mAuthenticator;
|
||||
MozPromiseHolder<U2FPrepPromise> mPromise;
|
||||
};
|
||||
|
||||
// Determine whether the provided Authenticator already knows
|
||||
// of the provided Registered Key.
|
||||
class U2FIsRegisteredTask final : public U2FPrepTask
|
||||
{
|
||||
public:
|
||||
U2FIsRegisteredTask(const Authenticator& aAuthenticator,
|
||||
const LocalRegisteredKey& aRegisteredKey);
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
private:
|
||||
~U2FIsRegisteredTask();
|
||||
|
||||
LocalRegisteredKey mRegisteredKey;
|
||||
};
|
||||
|
||||
class U2FTask : public Runnable
|
||||
{
|
||||
public:
|
||||
U2FTask(const nsAString& aOrigin,
|
||||
const nsAString& aAppId,
|
||||
const Authenticator& aAuthenticator);
|
||||
|
||||
RefPtr<U2FPromise> Execute();
|
||||
|
||||
nsString mOrigin;
|
||||
nsString mAppId;
|
||||
Authenticator mAuthenticator;
|
||||
|
||||
protected:
|
||||
virtual ~U2FTask();
|
||||
|
||||
MozPromiseHolder<U2FPromise> mPromise;
|
||||
};
|
||||
|
||||
// Use the provided Authenticator to Register a new scoped credential
|
||||
// for the provided application.
|
||||
class U2FRegisterTask final : public U2FTask
|
||||
{
|
||||
public:
|
||||
U2FRegisterTask(const nsAString& aOrigin,
|
||||
const nsAString& aAppId,
|
||||
const Authenticator& aAuthenticator,
|
||||
const CryptoBuffer& aAppParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const LocalRegisterRequest& aRegisterEntry);
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
private:
|
||||
~U2FRegisterTask();
|
||||
|
||||
CryptoBuffer mAppParam;
|
||||
CryptoBuffer mChallengeParam;
|
||||
LocalRegisterRequest mRegisterEntry;
|
||||
};
|
||||
|
||||
// Generate an assertion using the provided Authenticator for the given origin
|
||||
// and provided application to attest to ownership of a valid scoped credential.
|
||||
class U2FSignTask final : public U2FTask
|
||||
{
|
||||
public:
|
||||
U2FSignTask(const nsAString& aOrigin,
|
||||
const nsAString& aAppId,
|
||||
const nsAString& aVersion,
|
||||
const Authenticator& aAuthenticator,
|
||||
const CryptoBuffer& aAppParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aClientData,
|
||||
const CryptoBuffer& aKeyHandle);
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
private:
|
||||
~U2FSignTask();
|
||||
|
||||
nsString mVersion;
|
||||
CryptoBuffer mAppParam;
|
||||
CryptoBuffer mChallengeParam;
|
||||
CryptoBuffer mClientData;
|
||||
CryptoBuffer mKeyHandle;
|
||||
};
|
||||
|
||||
// Mediate inter-thread communication for multiple authenticators being queried
|
||||
// in concert. Operates as a cyclic buffer with a stop-work method.
|
||||
class U2FStatus
|
||||
{
|
||||
public:
|
||||
U2FStatus();
|
||||
|
||||
void WaitGroupAdd();
|
||||
void WaitGroupDone();
|
||||
void WaitGroupWait();
|
||||
|
||||
void Stop(const ErrorCode aErrorCode);
|
||||
void Stop(const ErrorCode aErrorCode, const nsAString& aResponse);
|
||||
bool IsStopped();
|
||||
ErrorCode GetErrorCode();
|
||||
nsString GetResponse();
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FStatus)
|
||||
|
||||
private:
|
||||
~U2FStatus();
|
||||
|
||||
uint16_t mCount;
|
||||
bool mIsStopped;
|
||||
nsString mResponse;
|
||||
MOZ_INIT_OUTSIDE_CTOR ErrorCode mErrorCode;
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
};
|
||||
|
||||
// U2FRunnables run to completion, performing a single U2F operation such as
|
||||
// registering, or signing.
|
||||
class U2FRunnable : public Runnable
|
||||
, public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
U2FRunnable(const nsAString& aOrigin, const nsAString& aAppId);
|
||||
|
||||
// No NSS resources to release.
|
||||
virtual
|
||||
void virtualDestroyNSSReference() override {};
|
||||
|
||||
protected:
|
||||
virtual ~U2FRunnable();
|
||||
ErrorCode EvaluateAppID();
|
||||
|
||||
nsString mOrigin;
|
||||
nsString mAppId;
|
||||
};
|
||||
|
||||
// This U2FRunnable completes a single application-requested U2F Register
|
||||
// operation.
|
||||
class U2FRegisterRunnable : public U2FRunnable
|
||||
{
|
||||
public:
|
||||
U2FRegisterRunnable(const nsAString& aOrigin,
|
||||
const nsAString& aAppId,
|
||||
const Sequence<RegisterRequest>& aRegisterRequests,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
const Sequence<Authenticator>& aAuthenticators,
|
||||
U2FRegisterCallback* aCallback);
|
||||
|
||||
void SendResponse(const RegisterResponse& aResponse);
|
||||
void SetTimeout(const int32_t aTimeoutMillis);
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
private:
|
||||
~U2FRegisterRunnable();
|
||||
|
||||
nsTArray<LocalRegisterRequest> mRegisterRequests;
|
||||
nsTArray<LocalRegisteredKey> mRegisteredKeys;
|
||||
nsTArray<Authenticator> mAuthenticators;
|
||||
nsMainThreadPtrHandle<U2FRegisterCallback> mCallback;
|
||||
Nullable<int32_t> opt_mTimeoutSeconds;
|
||||
};
|
||||
|
||||
// This U2FRunnable completes a single application-requested U2F Sign operation.
|
||||
class U2FSignRunnable : public U2FRunnable
|
||||
{
|
||||
public:
|
||||
U2FSignRunnable(const nsAString& aOrigin,
|
||||
const nsAString& aAppId,
|
||||
const nsAString& aChallenge,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
const Sequence<Authenticator>& aAuthenticators,
|
||||
U2FSignCallback* aCallback);
|
||||
|
||||
void SendResponse(const SignResponse& aResponse);
|
||||
void SetTimeout(const int32_t aTimeoutMillis);
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
private:
|
||||
~U2FSignRunnable();
|
||||
|
||||
nsString mChallenge;
|
||||
CryptoBuffer mClientData;
|
||||
nsTArray<LocalRegisteredKey> mRegisteredKeys;
|
||||
nsTArray<Authenticator> mAuthenticators;
|
||||
nsMainThreadPtrHandle<U2FSignCallback> mCallback;
|
||||
Nullable<int32_t> opt_mTimeoutSeconds;
|
||||
};
|
||||
|
||||
// The U2F Class is used by the JS engine to initiate U2F operations.
|
||||
class U2F final : public nsISupports
|
||||
, public nsWrapperCache
|
||||
, public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
|
||||
|
||||
U2F();
|
||||
|
||||
nsPIDOMWindowInner*
|
||||
GetParentObject() const
|
||||
{
|
||||
return mParent;
|
||||
}
|
||||
|
||||
void
|
||||
Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv);
|
||||
|
||||
virtual JSObject*
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void
|
||||
Register(const nsAString& aAppId,
|
||||
const Sequence<RegisterRequest>& aRegisterRequests,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FRegisterCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Sign(const nsAString& aAppId,
|
||||
const nsAString& aChallenge,
|
||||
const Sequence<RegisteredKey>& aRegisteredKeys,
|
||||
U2FSignCallback& aCallback,
|
||||
const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// No NSS resources to release.
|
||||
virtual
|
||||
void virtualDestroyNSSReference() override {};
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsString mOrigin;
|
||||
Sequence<Authenticator> mAuthenticators;
|
||||
bool mInitialized;
|
||||
|
||||
~U2F();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_U2F_h
|
||||
72
dom/u2f/USBToken.cpp
Normal file
72
dom/u2f/USBToken.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- 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 "USBToken.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
USBToken::USBToken()
|
||||
: mInitialized(false)
|
||||
{}
|
||||
|
||||
USBToken::~USBToken()
|
||||
{}
|
||||
|
||||
nsresult
|
||||
USBToken::Init()
|
||||
{
|
||||
// This routine does nothing at present, but Bug 1245527 will
|
||||
// integrate the upcoming USB HID service here, which will likely
|
||||
// require an initialization upon load.
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
if (mInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsString USBToken::mVersion = NS_LITERAL_STRING("U2F_V2");
|
||||
|
||||
bool
|
||||
USBToken::IsCompatibleVersion(const nsString& aVersionParam) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return mVersion == aVersionParam;
|
||||
}
|
||||
|
||||
bool
|
||||
USBToken::IsRegistered(const CryptoBuffer& aKeyHandle) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
USBToken::Register(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& /* aChallengeParam */,
|
||||
const CryptoBuffer& /* aApplicationParam */,
|
||||
CryptoBuffer& aRegistrationData) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
USBToken::Sign(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData) const
|
||||
{
|
||||
MOZ_ASSERT(mInitialized);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
49
dom/u2f/USBToken.h
Normal file
49
dom/u2f/USBToken.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* -*- 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_USBToken_h
|
||||
#define mozilla_dom_USBToken_h
|
||||
|
||||
#include "mozilla/dom/CryptoBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// USBToken implements FIDO operations using a USB device.
|
||||
class USBToken final
|
||||
{
|
||||
public:
|
||||
USBToken();
|
||||
|
||||
~USBToken();
|
||||
|
||||
nsresult Init();
|
||||
|
||||
bool IsCompatibleVersion(const nsString& aVersionParam) const;
|
||||
|
||||
bool IsRegistered(const CryptoBuffer& aKeyHandle) const;
|
||||
|
||||
nsresult Register(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
CryptoBuffer& aRegistrationData) const;
|
||||
|
||||
nsresult Sign(const Optional<Nullable<int32_t>>& opt_timeoutSeconds,
|
||||
const CryptoBuffer& aApplicationParam,
|
||||
const CryptoBuffer& aChallengeParam,
|
||||
const CryptoBuffer& aKeyHandle,
|
||||
CryptoBuffer& aSignatureData) const;
|
||||
|
||||
private:
|
||||
bool mInitialized;
|
||||
|
||||
static const nsString mVersion;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_USBToken_h
|
||||
30
dom/u2f/moz.build
Normal file
30
dom/u2f/moz.build
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# -*- 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',
|
||||
'U2F.h',
|
||||
'USBToken.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'NSSU2FTokenRemote.cpp',
|
||||
'U2F.cpp',
|
||||
'USBToken.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/dom/crypto',
|
||||
'/security/manager/ssl',
|
||||
'/security/pkix/include',
|
||||
]
|
||||
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
|
||||
63
dom/u2f/tests/frame_appid_facet.html
Normal file
63
dom/u2f/tests/frame_appid_facet.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
local_is(window.location.origin, "https://example.com", "Is loaded correctly");
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_expectThisManyTests(5);
|
||||
|
||||
u2f.register(null, [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Null AppID should work.");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Empty AppID should work.");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// Test: Correct TLD, but incorrect scheme
|
||||
u2f.register("http://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "HTTP scheme is disallowed");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// Test: Correct TLD, and also HTTPS
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "HTTPS origin for example.com should work");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// Test: Dynamic origin
|
||||
u2f.register(window.location.origin + "/otherAppId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "Direct window origin should work");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
60
dom/u2f/tests/frame_appid_facet_insecure.html
Normal file
60
dom/u2f/tests/frame_appid_facet_insecure.html
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
local_is(window.location.origin, "http://mochi.test:8888", "Is loaded correctly");
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_expectThisManyTests(5);
|
||||
|
||||
u2f.register(null, [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for null AppID");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for empty AppID");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("http://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP AppID");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTPS AppID from HTTP origin");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register(window.location.origin + "/otherAppId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP origin");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
56
dom/u2f/tests/frame_appid_facet_subdomain.html
Normal file
56
dom/u2f/tests/frame_appid_facet_subdomain.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
|
||||
|
||||
local_expectThisManyTests(4);
|
||||
|
||||
// same domain check
|
||||
u2f.register("https://test1.example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "AppID should work from a different path of this domain");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// same domain check, but wrong scheme
|
||||
u2f.register("http://test1.example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "AppID should not work when using a different scheme");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// eTLD+1 subdomain check
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "AppID should not work from another subdomain in this registered domain");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
// other domain check
|
||||
u2f.register("https://mochi.test:8888/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_isnot(res.errorCode, 0, "AppID should not work from other domains");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
30
dom/u2f/tests/frame_no_token.html
Normal file
30
dom/u2f/tests/frame_no_token.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for FIDO Universal Second Factor No Token</title>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var challenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(challenge);
|
||||
|
||||
var regRequest = {
|
||||
version: "U2F_V2",
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
};
|
||||
|
||||
local_expectThisManyTests(1);
|
||||
|
||||
u2f.register(window.location.origin, [regRequest], [], function (res) {
|
||||
local_isnot(res.errorCode, 0, "The registration should be rejected.");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
81
dom/u2f/tests/frame_register.html
Normal file
81
dom/u2f/tests/frame_register.html
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script src="u2futil.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for Register behavior for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var version = "U2F_V2";
|
||||
var challenge = new Uint8Array(16);
|
||||
|
||||
local_is(window.location.origin, "https://example.com", "Is loaded correctly");
|
||||
|
||||
local_expectThisManyTests(7);
|
||||
|
||||
// basic check
|
||||
u2f.register("https://example.com/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 0, "AppID should work from the domain");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "AppID should not work from other domains");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("", [], [], function(res){
|
||||
local_is(res.errorCode, 2, "Empty register requests");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", null, [], null);
|
||||
}, "Non-array register requests");
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", [], null, null);
|
||||
}, "Non-array sign requests");
|
||||
|
||||
local_doesThrow(function(){
|
||||
u2f.register("", null, null, null);
|
||||
}, "Non-array for both arguments");
|
||||
|
||||
u2f.register("", [{}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Empty request");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: version,
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Missing challenge");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Missing version");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
u2f.register("https://example.net/appId", [{
|
||||
version: "a_version_00",
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
}], [], function(res){
|
||||
local_is(res.errorCode, 2, "Invalid version");
|
||||
local_completeTest();
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
201
dom/u2f/tests/frame_register_sign.html
Normal file
201
dom/u2f/tests/frame_register_sign.html
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<script type="text/javascript" src="pkijs/common.js"></script>
|
||||
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Register and Sign Test for FIDO Universal Second Factor</p>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var state = {
|
||||
// Raw messages
|
||||
regRequest: null,
|
||||
regResponse: null,
|
||||
|
||||
regKey: null,
|
||||
signChallenge: null,
|
||||
signResponse: null,
|
||||
|
||||
// Parsed values
|
||||
publicKey: null,
|
||||
keyHandle: null,
|
||||
|
||||
// Constants
|
||||
version: "U2F_V2",
|
||||
appId: window.location.origin,
|
||||
};
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true]]},
|
||||
function() {
|
||||
local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
|
||||
local_isnot(window.u2f.register, undefined, "U2F Register API endpoint must exist");
|
||||
local_isnot(window.u2f.sign, undefined, "U2F Sign API endpoint must exist");
|
||||
|
||||
testRegistering();
|
||||
|
||||
function testRegistering() {
|
||||
var challenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(challenge);
|
||||
|
||||
state.regRequest = {
|
||||
version: state.version,
|
||||
challenge: bytesToBase64UrlSafe(challenge),
|
||||
};
|
||||
|
||||
u2f.register(state.appId, [state.regRequest], [], function(regResponse) {
|
||||
state.regResponse = regResponse;
|
||||
|
||||
local_is(regResponse.errorCode, 0, "The registration did not error");
|
||||
local_isnot(regResponse.registrationData, undefined, "The registration did not provide registration data");
|
||||
if (regResponse.errorCode > 0) {
|
||||
local_finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the response data from the U2F token
|
||||
var registrationData = base64ToBytesUrlSafe(regResponse.registrationData);
|
||||
local_is(registrationData[0], 0x05, "Reserved byte is correct")
|
||||
|
||||
state.publicKeyBytes = registrationData.slice(1, 66);
|
||||
var keyHandleLength = registrationData[66];
|
||||
state.keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
|
||||
state.keyHandle = bytesToBase64UrlSafe(state.keyHandleBytes);
|
||||
state.attestation = registrationData.slice(67 + keyHandleLength);
|
||||
|
||||
local_is(state.attestation[0], 0x30, "Attestation Certificate has correct starting byte");
|
||||
var asn1 = org.pkijs.fromBER(state.attestation.buffer);
|
||||
console.log(asn1);
|
||||
state.attestationCert = new org.pkijs.simpl.CERT({ schema: asn1.result });
|
||||
console.log(state.attestationCert);
|
||||
state.attestationSig = state.attestation.slice(asn1.offset);
|
||||
local_is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
|
||||
local_is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
|
||||
local_is(state.attestationCert.notAfter.value - state.attestationCert.notBefore.value, 1000*60*60*48, "Valid 48 hours (in millis)");
|
||||
|
||||
// Verify that the clientData from the U2F token makes sense
|
||||
var clientDataJSON = "";
|
||||
base64ToBytesUrlSafe(regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
|
||||
var clientData = JSON.parse(clientDataJSON);
|
||||
local_is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
|
||||
local_is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
|
||||
local_is(clientData.origin, window.location.origin, "Register - Origins are the same");
|
||||
|
||||
// Verify the signature from the attestation certificate
|
||||
deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
|
||||
.then(function(params){
|
||||
state.appParam = params.appParam;
|
||||
state.challengeParam = params.challengeParam;
|
||||
return state.attestationCert.getPublicKey();
|
||||
}).then(function(attestationPublicKey) {
|
||||
var signedData = assembleRegistrationSignedData(state.appParam, state.challengeParam, state.keyHandleBytes, state.publicKeyBytes);
|
||||
return verifySignature(attestationPublicKey, signedData, state.attestationSig);
|
||||
}).then(function(verified) {
|
||||
console.log("No error verifying signature");
|
||||
local_ok(verified, "Attestation Certificate signature verified")
|
||||
// Import the public key of the U2F token into WebCrypto
|
||||
return importPublicKey(state.publicKeyBytes)
|
||||
}).then(function(key) {
|
||||
state.publicKey = key;
|
||||
local_ok(true, "Imported public key")
|
||||
|
||||
// Ensure the attestation certificate is properly self-signed
|
||||
return state.attestationCert.verify()
|
||||
}).then(function(){
|
||||
local_ok(true, "Attestation Certificate verification successful");
|
||||
|
||||
// Continue test
|
||||
testReRegister()
|
||||
}).catch(function(err){
|
||||
console.log(err);
|
||||
local_ok(false, "Attestation Certificate verification failed");
|
||||
local_finished();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testReRegister() {
|
||||
state.regKey = {
|
||||
version: state.version,
|
||||
keyHandle: state.keyHandle,
|
||||
};
|
||||
|
||||
// Test that we don't re-register if we provide regKey as an
|
||||
// "already known" key handle. The U2F module should recognize regKey
|
||||
// as being usable and, thus, give back errorCode 4.
|
||||
u2f.register(state.appId, [state.regRequest], [state.regKey], function(regResponse) {
|
||||
// Since we attempted to register with state.regKey as a known key, expect
|
||||
// ineligible (=4).
|
||||
local_is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
|
||||
local_is(regResponse.registrationData, undefined, "The re-registration did not provide registration data");
|
||||
|
||||
// Continue test
|
||||
testSigning();
|
||||
});
|
||||
}
|
||||
|
||||
function testSigning() {
|
||||
var challenge = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(challenge);
|
||||
state.signChallenge = bytesToBase64UrlSafe(challenge);
|
||||
|
||||
// Now try to sign the signature challenge
|
||||
u2f.sign(state.appId, state.signChallenge, [state.regKey], function(signResponse) {
|
||||
state.signResponse = signResponse;
|
||||
|
||||
// Make sure this signature op worked, bailing early if it failed.
|
||||
local_is(signResponse.errorCode, 0, "The signing did not error");
|
||||
local_isnot(signResponse.clientData, undefined, "The signing did not provide client data");
|
||||
|
||||
if (signResponse.errorCode > 0) {
|
||||
local_finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the clientData that was returned from the module
|
||||
var clientDataJSON = "";
|
||||
base64ToBytesUrlSafe(signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
|
||||
var clientData = JSON.parse(clientDataJSON);
|
||||
local_is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
|
||||
local_is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
|
||||
local_is(clientData.origin, window.location.origin, "Sign - Origins are the same");
|
||||
|
||||
// Parse the signature data
|
||||
var signatureData = base64ToBytesUrlSafe(signResponse.signatureData);
|
||||
if (signatureData[0] != 0x01) {
|
||||
throw "User presence byte not set";
|
||||
}
|
||||
var presenceAndCounter = signatureData.slice(0,5);
|
||||
var signatureValue = signatureData.slice(5);
|
||||
|
||||
// Assemble the signed data and verify the signature
|
||||
deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
|
||||
.then(function(params){
|
||||
return assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam);
|
||||
})
|
||||
.then(function(signedData) {
|
||||
return verifySignature(state.publicKey, signedData, signatureValue);
|
||||
})
|
||||
.then(function(verified) {
|
||||
console.log("No error verifying signing signature");
|
||||
local_ok(verified, "Signing signature verified")
|
||||
|
||||
local_finished();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log(err);
|
||||
local_ok(false, "Signing signature invalid");
|
||||
local_finished();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
29
dom/u2f/tests/mochitest.ini
Normal file
29
dom/u2f/tests/mochitest.ini
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
frame_appid_facet.html
|
||||
frame_appid_facet_insecure.html
|
||||
frame_appid_facet_subdomain.html
|
||||
frame_no_token.html
|
||||
frame_register.html
|
||||
frame_register_sign.html
|
||||
pkijs/asn1.js
|
||||
pkijs/common.js
|
||||
pkijs/x509_schema.js
|
||||
pkijs/x509_simpl.js
|
||||
u2futil.js
|
||||
|
||||
# Feature does not function on e10s (Disabled in Bug 1297552)
|
||||
[test_util_methods.html]
|
||||
skip-if = !e10s
|
||||
[test_no_token.html]
|
||||
skip-if = !e10s
|
||||
[test_register.html]
|
||||
skip-if = !e10s
|
||||
[test_register_sign.html]
|
||||
skip-if = !e10s
|
||||
[test_appid_facet.html]
|
||||
skip-if = !e10s
|
||||
[test_appid_facet_insecure.html]
|
||||
skip-if = !e10s
|
||||
[test_appid_facet_subdomain.html]
|
||||
skip-if = !e10s
|
||||
30
dom/u2f/tests/pkijs/LICENSE
Normal file
30
dom/u2f/tests/pkijs/LICENSE
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
Copyright (c) 2014, GMO GlobalSign
|
||||
Copyright (c) 2015, Peculiar Ventures
|
||||
All rights reserved.
|
||||
|
||||
Author 2014-2015, Yury Strozhevsky
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1
dom/u2f/tests/pkijs/README
Normal file
1
dom/u2f/tests/pkijs/README
Normal file
|
|
@ -0,0 +1 @@
|
|||
PKIjs and ASN1js are from https://pkijs.org/ and https://asn1js.org/.
|
||||
5466
dom/u2f/tests/pkijs/asn1.js
Normal file
5466
dom/u2f/tests/pkijs/asn1.js
Normal file
File diff suppressed because it is too large
Load diff
1542
dom/u2f/tests/pkijs/common.js
Normal file
1542
dom/u2f/tests/pkijs/common.js
Normal file
File diff suppressed because it is too large
Load diff
1889
dom/u2f/tests/pkijs/x509_schema.js
Normal file
1889
dom/u2f/tests/pkijs/x509_schema.js
Normal file
File diff suppressed because it is too large
Load diff
7239
dom/u2f/tests/pkijs/x509_simpl.js
Normal file
7239
dom/u2f/tests/pkijs/x509_simpl.js
Normal file
File diff suppressed because it is too large
Load diff
34
dom/u2f/tests/test_appid_facet.html
Normal file
34
dom/u2f/tests/test_appid_facet.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
34
dom/u2f/tests/test_appid_facet_insecure.html
Normal file
34
dom/u2f/tests/test_appid_facet_insecure.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = "http://mochi.test:8888/tests/dom/u2f/tests/frame_appid_facet_insecure.html";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
34
dom/u2f/tests/test_appid_facet_subdomain.html
Normal file
34
dom/u2f/tests/test_appid_facet_subdomain.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_appid_facet_subdomain.html";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
32
dom/u2f/tests/test_no_token.html
Normal file
32
dom/u2f/tests/test_no_token.html
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for FIDO Universal Second Factor No Token</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", false],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = 'frame_no_token.html';
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
34
dom/u2f/tests/test_register.html
Normal file
34
dom/u2f/tests/test_register.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for Register behavior for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_register.html";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
34
dom/u2f/tests/test_register_sign.html
Normal file
34
dom/u2f/tests/test_register_sign.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Register and Sign Test for FIDO Universal Second Factor</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
|
||||
<div id="framediv">
|
||||
<iframe id="testing_frame"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// listen for messages from the test harness
|
||||
window.addEventListener("message", handleEventMessage, false);
|
||||
document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
65
dom/u2f/tests/test_util_methods.html
Normal file
65
dom/u2f/tests/test_util_methods.html
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Test for Utility Methods for other FIDO Universal Second Factor tests</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/dom/u2f/tests/u2futil.js"></script>
|
||||
<script type="text/javascript" src="pkijs/common.js"></script>
|
||||
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
|
||||
["security.webauth.u2f_enable_softtoken", true],
|
||||
["security.webauth.u2f_enable_usbtoken", false]]},
|
||||
function() {
|
||||
// Example from:
|
||||
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html
|
||||
//
|
||||
// Run this example from the console to check that the u2futil methods work
|
||||
var pubKey = hexDecode("04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d");
|
||||
var appId = "https://gstatic.com/securitykey/a/example.com";
|
||||
var clientData = string2buffer('{"typ":"navigator.id.getAssertion","challenge":"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}');
|
||||
var presenceAndCounter = hexDecode("0100000001");
|
||||
var signature = hexDecode("304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f");
|
||||
|
||||
// Import the key
|
||||
// Assemble the client data
|
||||
// Verify
|
||||
Promise.all([
|
||||
importPublicKey(pubKey),
|
||||
deriveAppAndChallengeParam(appId, clientData)
|
||||
])
|
||||
.then(function(results) {
|
||||
var importedKey = results[0];
|
||||
var params = results[1];
|
||||
var signedData = new Uint8Array(assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam));
|
||||
return verifySignature(importedKey, signedData, signature);
|
||||
})
|
||||
.then(function(verified) {
|
||||
console.log("verified:", verified);
|
||||
ok(true, "Utility methods work")
|
||||
SimpleTest.finish();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log("error:", err);
|
||||
ok(false, "Utility methods failed")
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
204
dom/u2f/tests/u2futil.js
Normal file
204
dom/u2f/tests/u2futil.js
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// Used by local_addTest() / local_completeTest()
|
||||
var _countCompletions = 0;
|
||||
var _expectedCompletions = 0;
|
||||
|
||||
function handleEventMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
let summary = event.data.test + ": " + event.data.msg;
|
||||
log(event.data.status + ": " + summary);
|
||||
ok(event.data.status, summary);
|
||||
} else if ("done" in event.data) {
|
||||
SimpleTest.finish();
|
||||
} else {
|
||||
ok(false, "Unexpected message in the test harness: " + event.data)
|
||||
}
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg)
|
||||
let logBox = document.getElementById("log");
|
||||
if (logBox) {
|
||||
logBox.textContent += "\n" + msg;
|
||||
}
|
||||
}
|
||||
|
||||
function local_is(value, expected, message) {
|
||||
if (value === expected) {
|
||||
local_ok(true, message);
|
||||
} else {
|
||||
local_ok(false, message + " unexpectedly: " + value + " !== " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
function local_isnot(value, expected, message) {
|
||||
if (value !== expected) {
|
||||
local_ok(true, message);
|
||||
} else {
|
||||
local_ok(false, message + " unexpectedly: " + value + " === " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
function local_ok(expression, message) {
|
||||
let body = {"test": this.location.pathname, "status":expression, "msg": message}
|
||||
parent.postMessage(body, "http://mochi.test:8888");
|
||||
}
|
||||
|
||||
function local_doesThrow(fn, name) {
|
||||
let gotException = false;
|
||||
try {
|
||||
fn();
|
||||
} catch (ex) { gotException = true; }
|
||||
local_ok(gotException, name);
|
||||
};
|
||||
|
||||
function local_expectThisManyTests(count) {
|
||||
if (_expectedCompletions > 0) {
|
||||
local_ok(false, "Error: local_expectThisManyTests should only be called once.");
|
||||
}
|
||||
_expectedCompletions = count;
|
||||
}
|
||||
|
||||
function local_completeTest() {
|
||||
_countCompletions += 1
|
||||
if (_countCompletions == _expectedCompletions) {
|
||||
log("All tests completed.")
|
||||
local_finished();
|
||||
}
|
||||
if (_countCompletions > _expectedCompletions) {
|
||||
local_ok(false, "Error: local_completeTest called more than local_addTest.");
|
||||
}
|
||||
}
|
||||
|
||||
function local_finished() {
|
||||
parent.postMessage({"done":true}, "http://mochi.test:8888");
|
||||
}
|
||||
|
||||
function string2buffer(str) {
|
||||
return (new Uint8Array(str.length)).map((x, i) => str.charCodeAt(i));
|
||||
}
|
||||
|
||||
function buffer2string(buf) {
|
||||
let str = "";
|
||||
buf.map(x => str += String.fromCharCode(x));
|
||||
return str;
|
||||
}
|
||||
|
||||
function bytesToBase64(u8a){
|
||||
let CHUNK_SZ = 0x8000;
|
||||
let c = [];
|
||||
for (let i = 0; i < u8a.length; i += CHUNK_SZ) {
|
||||
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
|
||||
}
|
||||
return window.btoa(c.join(""));
|
||||
}
|
||||
|
||||
function base64ToBytes(b64encoded) {
|
||||
return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
|
||||
return c.charCodeAt(0);
|
||||
}));
|
||||
}
|
||||
|
||||
function bytesToBase64UrlSafe(buf) {
|
||||
return bytesToBase64(buf)
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
function base64ToBytesUrlSafe(str) {
|
||||
if (str.length % 4 == 1) {
|
||||
throw "Improper b64 string";
|
||||
}
|
||||
|
||||
var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
|
||||
while (b64.length % 4 != 0) {
|
||||
b64 += "=";
|
||||
}
|
||||
return base64ToBytes(b64);
|
||||
}
|
||||
|
||||
function hexEncode(buf) {
|
||||
return Array.from(buf)
|
||||
.map(x => ("0"+x.toString(16)).substr(-2))
|
||||
.join("");
|
||||
}
|
||||
|
||||
function hexDecode(str) {
|
||||
return new Uint8Array(str.match(/../g).map(x => parseInt(x, 16)));
|
||||
}
|
||||
|
||||
function importPublicKey(keyBytes) {
|
||||
if (keyBytes[0] != 0x04 || keyBytes.byteLength != 65) {
|
||||
throw "Bad public key octet string";
|
||||
}
|
||||
var jwk = {
|
||||
kty: "EC",
|
||||
crv: "P-256",
|
||||
x: bytesToBase64UrlSafe(keyBytes.slice(1, 33)),
|
||||
y: bytesToBase64UrlSafe(keyBytes.slice(33))
|
||||
};
|
||||
return crypto.subtle.importKey("jwk", jwk, {name: "ECDSA", namedCurve: "P-256"}, true, ["verify"])
|
||||
}
|
||||
|
||||
function deriveAppAndChallengeParam(appId, clientData) {
|
||||
var appIdBuf = string2buffer(appId);
|
||||
return Promise.all([
|
||||
crypto.subtle.digest("SHA-256", appIdBuf),
|
||||
crypto.subtle.digest("SHA-256", clientData)
|
||||
])
|
||||
.then(function(digests) {
|
||||
return {
|
||||
appParam: new Uint8Array(digests[0]),
|
||||
challengeParam: new Uint8Array(digests[1]),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function assembleSignedData(appParam, presenceAndCounter, challengeParam) {
|
||||
let signedData = new Uint8Array(32 + 1 + 4 + 32);
|
||||
appParam.map((x, i) => signedData[0 + i] = x);
|
||||
presenceAndCounter.map((x, i) => signedData[32 + i] = x);
|
||||
challengeParam.map((x, i) => signedData[37 + i] = x);
|
||||
return signedData;
|
||||
}
|
||||
|
||||
function assembleRegistrationSignedData(appParam, challengeParam, keyHandle, pubKey) {
|
||||
let signedData = new Uint8Array(1 + 32 + 32 + keyHandle.length + 65);
|
||||
signedData[0] = 0x00;
|
||||
appParam.map((x, i) => signedData[1 + i] = x);
|
||||
challengeParam.map((x, i) => signedData[33 + i] = x);
|
||||
keyHandle.map((x, i) => signedData[65 + i] = x);
|
||||
pubKey.map((x, i) => signedData[65 + keyHandle.length + i] = x);
|
||||
return signedData;
|
||||
}
|
||||
|
||||
function sanitizeSigArray(arr) {
|
||||
// ECDSA signature fields into WebCrypto must be exactly 32 bytes long, so
|
||||
// this method strips leading padding bytes, if added, and also appends
|
||||
// padding zeros, if needed.
|
||||
if (arr.length > 32) {
|
||||
arr = arr.slice(arr.length - 32)
|
||||
}
|
||||
let ret = new Uint8Array(32);
|
||||
ret.set(arr, ret.length - arr.length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function verifySignature(key, data, derSig) {
|
||||
let sigAsn1 = org.pkijs.fromBER(derSig.buffer);
|
||||
let sigR = new Uint8Array(sigAsn1.result.value_block.value[0].value_block.value_hex);
|
||||
let sigS = new Uint8Array(sigAsn1.result.value_block.value[1].value_block.value_hex);
|
||||
|
||||
// The resulting R and S values from the ASN.1 Sequence must be fit into 32
|
||||
// bytes. Sometimes they have leading zeros, sometimes they're too short, it
|
||||
// all depends on what lib generated the signature.
|
||||
let R = sanitizeSigArray(sigR);
|
||||
let S = sanitizeSigArray(sigS);
|
||||
|
||||
let sigData = new Uint8Array(R.length + S.length);
|
||||
sigData.set(R);
|
||||
sigData.set(S, R.length);
|
||||
|
||||
let alg = {name: "ECDSA", hash: "SHA-256"};
|
||||
return crypto.subtle.verify(alg, key, sigData, data);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue