Bug 1464015 - Web Authentication - Rework IPC layer for future Android/Windows support r=jcj

Reviewers: jcj

Reviewed By: jcj

Subscribers: mgoodwin

Bug #: 1464015

Differential Revision: https://phabricator.services.mozilla.com/D1378
This commit is contained in:
Tim Taubert 2018-05-30 16:06:09 +02:00
Родитель e09ad9e15c
Коммит 2a252e45a4
21 изменённых файлов: 631 добавлений и 566 удалений

Просмотреть файл

@ -7109,7 +7109,7 @@ var WebAuthnPromptHelper = {
label: gNavigatorBundle.getString("webauthn.proceed"), label: gNavigatorBundle.getString("webauthn.proceed"),
accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"), accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
callback(state) { callback(state) {
mgr.resumeRegister(tid, !state.checkboxChecked); mgr.resumeRegister(tid, state.checkboxChecked);
} }
}; };
}, },

Просмотреть файл

@ -11,13 +11,9 @@
#include "mozilla/dom/WebAuthnTransactionChild.h" #include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h" #include "mozilla/dom/WebAuthnUtil.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h" #include "nsIEffectiveTLDService.h"
#include "nsNetCID.h"
#include "nsNetUtil.h" #include "nsNetUtil.h"
#include "nsURLParsers.h" #include "nsURLParsers.h"
#include "U2FUtil.h"
#include "hasht.h"
using namespace mozilla::ipc; using namespace mozilla::ipc;
@ -129,59 +125,6 @@ RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
} }
} }
static nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash)
{
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_FAILED(srv)) {
return srv;
}
if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aRpId, aRpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
nsString base64;
Unused << NS_WARN_IF(NS_FAILED(aRpIdHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::RpID: %s", aRpId.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Rp ID Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
Unused << NS_WARN_IF(NS_FAILED(aClientDataHash.ToJwkBase64(base64)));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
MOZ_LOG(gU2FLog, LogLevel::Debug,
("dom::U2FManager::Client Data Hash (base64): %s",
NS_ConvertUTF16toUTF8(base64).get()));
}
return NS_OK;
}
/*********************************************************************** /***********************************************************************
* U2F JavaScript API Implementation * U2F JavaScript API Implementation
**********************************************************************/ **********************************************************************/
@ -280,17 +223,18 @@ U2F::Register(const nsAString& aAppId,
return; return;
} }
// Produce the AppParam from the current AppID
nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
nsAutoString clientDataJSON; nsAutoString clientDataJSON;
// Pick the first valid RegisterRequest; we can only work with one. // Pick the first valid RegisterRequest; we can only work with one.
CryptoBuffer challenge;
for (const RegisterRequest& req : aRegisterRequests) { for (const RegisterRequest& req : aRegisterRequests) {
if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() || if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() ||
req.mVersion.Value() != kRequiredU2FVersion) { req.mVersion.Value() != kRequiredU2FVersion) {
continue; continue;
} }
if (!challenge.Assign(NS_ConvertUTF16toUTF8(req.mChallenge.Value()))) {
continue;
}
nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment, nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
req.mChallenge.Value(), clientDataJSON); req.mChallenge.Value(), clientDataJSON);
@ -312,17 +256,6 @@ U2F::Register(const nsAString& aAppId,
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys, RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
excludeList); excludeList);
auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
rpIdHash, clientDataHash))) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, callback);
return;
}
if (!MaybeCreateBackgroundActor()) { if (!MaybeCreateBackgroundActor()) {
RegisterResponse response; RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
@ -332,27 +265,19 @@ U2F::Register(const nsAString& aAppId,
ListenForVisibilityEvents(); ListenForVisibilityEvents();
// Always blank for U2F NS_ConvertUTF16toUTF8 clientData(clientDataJSON);
nsTArray<WebAuthnExtension> extensions;
// Default values for U2F.
WebAuthnAuthenticatorSelection authSelection(false /* requireResidentKey */,
false /* requireUserVerification */,
false /* requirePlatformAttachment */);
uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds); uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
WebAuthnMakeCredentialInfo info(mOrigin, WebAuthnMakeCredentialInfo info(mOrigin,
rpIdHash, adjustedAppId,
clientDataHash, challenge,
clientData,
adjustedTimeoutMillis, adjustedTimeoutMillis,
excludeList, excludeList,
extensions, null_t() /* no extra info for U2F */);
authSelection,
false /* RequestDirectAttestation */);
MOZ_ASSERT(mTransaction.isNothing()); MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback)))); mTransaction = Some(U2FTransaction(Move(AsVariant(callback))));
mChild->SendRequestRegister(mTransaction.ref().mId, info); mChild->SendRequestRegister(mTransaction.ref().mId, info);
} }
@ -372,14 +297,20 @@ U2F::FinishMakeCredential(const uint64_t& aTransactionId,
return; return;
} }
// A CTAP2 response.
if (aResult.RegistrationData().Length() == 0) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer clientDataBuf; CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) { if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
RejectTransaction(NS_ERROR_ABORT); RejectTransaction(NS_ERROR_ABORT);
return; return;
} }
CryptoBuffer regBuf; CryptoBuffer regBuf;
if (NS_WARN_IF(!regBuf.Assign(aResult.RegBuffer()))) { if (NS_WARN_IF(!regBuf.Assign(aResult.RegistrationData()))) {
RejectTransaction(NS_ERROR_ABORT); RejectTransaction(NS_ERROR_ABORT);
return; return;
} }
@ -455,22 +386,19 @@ U2F::Sign(const nsAString& aAppId,
return; return;
} }
// Build the key list, if any CryptoBuffer challenge;
nsTArray<WebAuthnScopedCredential> permittedList; if (!challenge.Assign(NS_ConvertUTF16toUTF8(aChallenge))) {
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
permittedList);
auto clientData = NS_ConvertUTF16toUTF8(clientDataJSON);
CryptoBuffer rpIdHash, clientDataHash;
if (NS_FAILED(BuildTransactionHashes(cAppId, clientData,
rpIdHash, clientDataHash))) {
SignResponse response; SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
ExecuteCallback(response, callback); ExecuteCallback(response, callback);
return; return;
} }
// Build the key list, if any
nsTArray<WebAuthnScopedCredential> permittedList;
RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
permittedList);
if (!MaybeCreateBackgroundActor()) { if (!MaybeCreateBackgroundActor()) {
SignResponse response; SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR)); response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OTHER_ERROR));
@ -483,18 +411,19 @@ U2F::Sign(const nsAString& aAppId,
// Always blank for U2F // Always blank for U2F
nsTArray<WebAuthnExtension> extensions; nsTArray<WebAuthnExtension> extensions;
NS_ConvertUTF16toUTF8 clientData(clientDataJSON);
uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds); uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
WebAuthnGetAssertionInfo info(mOrigin, WebAuthnGetAssertionInfo info(mOrigin,
rpIdHash, adjustedAppId,
clientDataHash, challenge,
clientData,
adjustedTimeoutMillis, adjustedTimeoutMillis,
permittedList, permittedList,
false, /* requireUserVerification */ null_t() /* no extra info for U2F */);
extensions);
MOZ_ASSERT(mTransaction.isNothing()); MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(U2FTransaction(clientData, Move(AsVariant(callback)))); mTransaction = Some(U2FTransaction(Move(AsVariant(callback))));
mChild->SendRequestSign(mTransaction.ref().mId, info); mChild->SendRequestSign(mTransaction.ref().mId, info);
} }
@ -514,20 +443,26 @@ U2F::FinishGetAssertion(const uint64_t& aTransactionId,
return; return;
} }
// A CTAP2 response.
if (aResult.SignatureData().Length() == 0) {
RejectTransaction(NS_ERROR_ABORT);
return;
}
CryptoBuffer clientDataBuf; CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!clientDataBuf.Assign(mTransaction.ref().mClientData))) { if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
RejectTransaction(NS_ERROR_ABORT); RejectTransaction(NS_ERROR_ABORT);
return; return;
} }
CryptoBuffer credBuf; CryptoBuffer credBuf;
if (NS_WARN_IF(!credBuf.Assign(aResult.CredentialID()))) { if (NS_WARN_IF(!credBuf.Assign(aResult.KeyHandle()))) {
RejectTransaction(NS_ERROR_ABORT); RejectTransaction(NS_ERROR_ABORT);
return; return;
} }
CryptoBuffer sigBuf; CryptoBuffer sigBuf;
if (NS_WARN_IF(!sigBuf.Assign(aResult.SigBuffer()))) { if (NS_WARN_IF(!sigBuf.Assign(aResult.SignatureData()))) {
RejectTransaction(NS_ERROR_ABORT); RejectTransaction(NS_ERROR_ABORT);
return; return;
} }

Просмотреть файл

@ -38,10 +38,8 @@ class U2FTransaction
nsMainThreadPtrHandle<U2FSignCallback>> U2FCallback; nsMainThreadPtrHandle<U2FSignCallback>> U2FCallback;
public: public:
explicit U2FTransaction(const nsCString& aClientData, explicit U2FTransaction(const U2FCallback&& aCallback)
const U2FCallback&& aCallback) : mCallback(Move(aCallback))
: mClientData(aClientData)
, mCallback(Move(aCallback))
, mId(NextId()) , mId(NextId())
{ {
MOZ_ASSERT(mId > 0); MOZ_ASSERT(mId > 0);
@ -63,9 +61,6 @@ public:
return mCallback.as<nsMainThreadPtrHandle<U2FSignCallback>>(); return mCallback.as<nsMainThreadPtrHandle<U2FSignCallback>>();
} }
// Client data used to assemble reply objects.
nsCString mClientData;
// The callback passed to the API. // The callback passed to the API.
U2FCallback mCallback; U2FCallback mCallback;

Просмотреть файл

@ -1,46 +0,0 @@
/* -*- 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 mozilla_dom_U2FUtil_h
#define mozilla_dom_U2FUtil_h
namespace mozilla {
namespace dom {
static nsresult
HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
/* out */ CryptoBuffer& aOut)
{
MOZ_ASSERT(aHashService);
nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aHashService->Update(
reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoCString fullHash;
// Passing false below means we will get a binary result rather than a
// base64-encoded string.
rv = aHashService->Finish(false, fullHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aOut.Assign(fullHash);
return rv;
}
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_U2FUtil_h

Просмотреть файл

@ -10,7 +10,6 @@ with Files("**"):
EXPORTS.mozilla.dom += [ EXPORTS.mozilla.dom += [
'U2F.h', 'U2F.h',
'U2FAuthenticator.h', 'U2FAuthenticator.h',
'U2FUtil.h',
] ]
UNIFIED_SOURCES += [ UNIFIED_SOURCES += [

Просмотреть файл

@ -16,6 +16,8 @@
include protocol PBackground; include protocol PBackground;
using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -46,37 +48,63 @@ union WebAuthnExtensionResult {
WebAuthnExtensionResultAppId; WebAuthnExtensionResultAppId;
}; };
struct WebAuthnMakeCredentialInfo { struct WebAuthnMakeCredentialExtraInfo {
nsString Origin;
uint8_t[] RpIdHash;
uint8_t[] ClientDataHash;
uint32_t TimeoutMS;
WebAuthnScopedCredential[] ExcludeList;
WebAuthnExtension[] Extensions; WebAuthnExtension[] Extensions;
WebAuthnAuthenticatorSelection AuthenticatorSelection; WebAuthnAuthenticatorSelection AuthenticatorSelection;
bool RequestDirectAttestation; bool RequestDirectAttestation;
}; };
union WebAuthnMaybeMakeCredentialExtraInfo {
WebAuthnMakeCredentialExtraInfo;
null_t;
};
struct WebAuthnMakeCredentialInfo {
nsString Origin;
nsString RpId;
uint8_t[] Challenge;
nsCString ClientDataJSON;
uint32_t TimeoutMS;
WebAuthnScopedCredential[] ExcludeList;
WebAuthnMaybeMakeCredentialExtraInfo Extra;
};
struct WebAuthnMakeCredentialResult { struct WebAuthnMakeCredentialResult {
uint8_t[] RegBuffer; nsCString ClientDataJSON;
bool DirectAttestationPermitted; uint8_t[] AttestationObject;
uint8_t[] KeyHandle;
/* Might be empty if the token implementation doesn't support CTAP1. */
uint8_t[] RegistrationData;
};
struct WebAuthnGetAssertionExtraInfo {
WebAuthnExtension[] Extensions;
bool RequireUserVerification;
};
union WebAuthnMaybeGetAssertionExtraInfo {
WebAuthnGetAssertionExtraInfo;
null_t;
}; };
struct WebAuthnGetAssertionInfo { struct WebAuthnGetAssertionInfo {
nsString Origin; nsString Origin;
uint8_t[] RpIdHash; nsString RpId;
uint8_t[] ClientDataHash; uint8_t[] Challenge;
nsCString ClientDataJSON;
uint32_t TimeoutMS; uint32_t TimeoutMS;
WebAuthnScopedCredential[] AllowList; WebAuthnScopedCredential[] AllowList;
bool RequireUserVerification; WebAuthnMaybeGetAssertionExtraInfo Extra;
WebAuthnExtension[] Extensions;
}; };
struct WebAuthnGetAssertionResult { struct WebAuthnGetAssertionResult {
uint8_t[] RpIdHash; nsCString ClientDataJSON;
uint8_t[] CredentialID; uint8_t[] KeyHandle;
uint8_t[] SigBuffer; uint8_t[] Signature;
uint8_t[] AuthenticatorData;
WebAuthnExtensionResult[] Extensions; WebAuthnExtensionResult[] Extensions;
/* Might be empty if the token implementation doesn't support CTAP1. */
uint8_t[] SignatureData;
}; };
async protocol PWebAuthnTransaction { async protocol PWebAuthnTransaction {

Просмотреть файл

@ -5,6 +5,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/U2FHIDTokenManager.h" #include "mozilla/dom/U2FHIDTokenManager.h"
#include "mozilla/dom/WebAuthnUtil.h"
#include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/StaticMutex.h" #include "mozilla/StaticMutex.h"
@ -51,7 +52,7 @@ u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
NS_DISPATCH_NORMAL)); NS_DISPATCH_NORMAL));
} }
U2FHIDTokenManager::U2FHIDTokenManager() : mTransactionId(0) U2FHIDTokenManager::U2FHIDTokenManager()
{ {
StaticMutexAutoLock lock(gInstanceMutex); StaticMutexAutoLock lock(gInstanceMutex);
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
@ -84,7 +85,7 @@ U2FHIDTokenManager::Drop()
mU2FManager = nullptr; mU2FManager = nullptr;
// Reset transaction ID so that queued runnables exit early. // Reset transaction ID so that queued runnables exit early.
mTransactionId = 0; mTransaction.reset();
} }
// A U2F Register operation causes a new key pair to be generated by the token. // A U2F Register operation causes a new key pair to be generated by the token.
@ -107,13 +108,16 @@ U2FHIDTokenManager::Drop()
// * attestation signature // * attestation signature
// //
RefPtr<U2FRegisterPromise> RefPtr<U2FRegisterPromise>
U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo) U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
uint64_t registerFlags = 0; uint64_t registerFlags = 0;
const WebAuthnAuthenticatorSelection& sel = aInfo.AuthenticatorSelection(); if (aInfo.Extra().type() != WebAuthnMaybeMakeCredentialExtraInfo::Tnull_t) {
const auto& extra = aInfo.Extra().get_WebAuthnMakeCredentialExtraInfo();
const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
// Set flags for credential creation. // Set flags for credential creation.
if (sel.requireResidentKey()) { if (sel.requireResidentKey()) {
@ -125,23 +129,35 @@ U2FHIDTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
if (sel.requirePlatformAttachment()) { if (sel.requirePlatformAttachment()) {
registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT; registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT;
} }
}
CryptoBuffer rpIdHash, clientDataHash;
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
rpIdHash, clientDataHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}
ClearPromises(); ClearPromises();
mCurrentAppId = aInfo.RpIdHash(); mTransaction.reset();
mTransactionId = rust_u2f_mgr_register(mU2FManager, uint64_t tid = rust_u2f_mgr_register(mU2FManager,
registerFlags, registerFlags,
(uint64_t)aInfo.TimeoutMS(), (uint64_t)aInfo.TimeoutMS(),
u2f_register_callback, u2f_register_callback,
aInfo.ClientDataHash().Elements(), clientDataHash.Elements(),
aInfo.ClientDataHash().Length(), clientDataHash.Length(),
aInfo.RpIdHash().Elements(), rpIdHash.Elements(),
aInfo.RpIdHash().Length(), rpIdHash.Length(),
U2FKeyHandles(aInfo.ExcludeList()).Get()); U2FKeyHandles(aInfo.ExcludeList()).Get());
if (mTransactionId == 0) { if (tid == 0) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
} }
mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON(),
aForceNoneAttestation));
return mRegisterPromise.Ensure(__func__); return mRegisterPromise.Ensure(__func__);
} }
@ -166,37 +182,50 @@ U2FHIDTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
CryptoBuffer rpIdHash, clientDataHash;
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
rpIdHash, clientDataHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}
uint64_t signFlags = 0; uint64_t signFlags = 0;
nsTArray<nsTArray<uint8_t>> appIds;
appIds.AppendElement(rpIdHash);
if (aInfo.Extra().type() != WebAuthnMaybeGetAssertionExtraInfo::Tnull_t) {
const auto& extra = aInfo.Extra().get_WebAuthnGetAssertionExtraInfo();
// Set flags for credential requests. // Set flags for credential requests.
if (aInfo.RequireUserVerification()) { if (extra.RequireUserVerification()) {
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION; signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
} }
mCurrentAppId = aInfo.RpIdHash();
nsTArray<nsTArray<uint8_t>> appIds;
appIds.AppendElement(mCurrentAppId);
// Process extensions. // Process extensions.
for (const WebAuthnExtension& ext: aInfo.Extensions()) { for (const WebAuthnExtension& ext: extra.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) { if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId()); appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
} }
} }
}
ClearPromises(); ClearPromises();
mTransactionId = rust_u2f_mgr_sign(mU2FManager, mTransaction.reset();
uint64_t tid = rust_u2f_mgr_sign(mU2FManager,
signFlags, signFlags,
(uint64_t)aInfo.TimeoutMS(), (uint64_t)aInfo.TimeoutMS(),
u2f_sign_callback, u2f_sign_callback,
aInfo.ClientDataHash().Elements(), clientDataHash.Elements(),
aInfo.ClientDataHash().Length(), clientDataHash.Length(),
U2FAppIds(appIds).Get(), U2FAppIds(appIds).Get(),
U2FKeyHandles(aInfo.AllowList()).Get()); U2FKeyHandles(aInfo.AllowList()).Get());
if (mTransactionId == 0) { if (tid == 0) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
} }
mTransaction = Some(Transaction(tid, rpIdHash, aInfo.ClientDataJSON()));
return mSignPromise.Ensure(__func__); return mSignPromise.Ensure(__func__);
} }
@ -206,7 +235,8 @@ U2FHIDTokenManager::Cancel()
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
ClearPromises(); ClearPromises();
mTransactionId = rust_u2f_mgr_cancel(mU2FManager); rust_u2f_mgr_cancel(mU2FManager);
mTransaction.reset();
} }
void void
@ -214,7 +244,8 @@ U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
if (aResult->GetTransactionId() != mTransactionId) { if (mTransaction.isNothing() ||
aResult->GetTransactionId() != mTransaction.ref().mId) {
return; return;
} }
@ -231,9 +262,41 @@ U2FHIDTokenManager::HandleRegisterResult(UniquePtr<U2FResult>&& aResult)
return; return;
} }
// Will be set by the U2FTokenManager. // Decompose the U2F registration packet
bool directAttestationPermitted = false; CryptoBuffer pubKeyBuf;
WebAuthnMakeCredentialResult result(registration, directAttestationPermitted); CryptoBuffer keyHandle;
CryptoBuffer attestationCertBuf;
CryptoBuffer signatureBuf;
CryptoBuffer regData;
regData.Assign(registration);
// Only handles attestation cert chains of length=1.
nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandle,
attestationCertBuf, signatureBuf);
if (NS_WARN_IF(NS_FAILED(rv))) {
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
CryptoBuffer rpIdHashBuf;
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
CryptoBuffer attObj;
rv = AssembleAttestationObject(rpIdHashBuf, pubKeyBuf, keyHandle,
attestationCertBuf, signatureBuf,
mTransaction.ref().mForceNoneAttestation,
attObj);
if (NS_FAILED(rv)) {
mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON,
attObj, keyHandle, regData);
mRegisterPromise.Resolve(Move(result), __func__); mRegisterPromise.Resolve(Move(result), __func__);
} }
@ -242,7 +305,8 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
if (aResult->GetTransactionId() != mTransactionId) { if (mTransaction.isNothing() ||
aResult->GetTransactionId() != mTransaction.ref().mId) {
return; return;
} }
@ -271,14 +335,51 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
return; return;
} }
CryptoBuffer rawSignatureBuf;
if (!rawSignatureBuf.Assign(signature)) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
nsTArray<WebAuthnExtensionResult> extensions; nsTArray<WebAuthnExtensionResult> extensions;
if (appId != mCurrentAppId) { if (appId != mTransaction.ref().mRpIdHash) {
// Indicate to the RP that we used the FIDO appId. // Indicate to the RP that we used the FIDO appId.
extensions.AppendElement(WebAuthnExtensionResultAppId(true)); extensions.AppendElement(WebAuthnExtensionResultAppId(true));
} }
WebAuthnGetAssertionResult result(appId, keyHandle, signature, extensions); CryptoBuffer signatureBuf;
CryptoBuffer counterBuf;
uint8_t flags = 0;
nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf,
signatureBuf);
if (NS_WARN_IF(NS_FAILED(rv))) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
CryptoBuffer chosenAppIdBuf;
if (!chosenAppIdBuf.Assign(appId)) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
// Preserve the two LSBs of the flags byte, UP and RFU1.
// See <https://github.com/fido-alliance/fido-2-specs/pull/519>
flags &= 0b11;
CryptoBuffer emptyAttestationData;
CryptoBuffer authenticatorData;
rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf,
emptyAttestationData, authenticatorData);
if (NS_WARN_IF(NS_FAILED(rv))) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON,
keyHandle, signatureBuf, authenticatorData,
extensions, rawSignatureBuf);
mSignPromise.Resolve(Move(result), __func__); mSignPromise.Resolve(Move(result), __func__);
} }

Просмотреть файл

@ -134,7 +134,8 @@ public:
explicit U2FHIDTokenManager(); explicit U2FHIDTokenManager();
RefPtr<U2FRegisterPromise> RefPtr<U2FRegisterPromise>
Register(const WebAuthnMakeCredentialInfo& aInfo) override; Register(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation) override;
RefPtr<U2FSignPromise> RefPtr<U2FSignPromise>
Sign(const WebAuthnGetAssertionInfo& aInfo) override; Sign(const WebAuthnGetAssertionInfo& aInfo) override;
@ -153,9 +154,34 @@ private:
mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__); mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
} }
class Transaction
{
public:
Transaction(uint64_t aId,
const nsTArray<uint8_t>& aRpIdHash,
const nsCString& aClientDataJSON,
bool aForceNoneAttestation = false)
: mId(aId)
, mRpIdHash(aRpIdHash)
, mClientDataJSON(aClientDataJSON)
, mForceNoneAttestation(aForceNoneAttestation)
{ }
// The transaction ID.
uint64_t mId;
// The RP ID hash.
nsTArray<uint8_t> mRpIdHash;
// The clientData JSON.
nsCString mClientDataJSON;
// Whether we'll force "none" attestation.
bool mForceNoneAttestation;
};
rust_u2f_manager* mU2FManager; rust_u2f_manager* mU2FManager;
uint64_t mTransactionId; Maybe<Transaction> mTransaction;
nsTArray<uint8_t> mCurrentAppId;
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise; MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
MozPromiseHolder<U2FSignPromise> mSignPromise; MozPromiseHolder<U2FSignPromise> mSignPromise;
}; };

Просмотреть файл

@ -583,7 +583,8 @@ U2FSoftTokenManager::IsRegistered(const nsTArray<uint8_t>& aKeyHandle,
// * attestation signature // * attestation signature
// //
RefPtr<U2FRegisterPromise> RefPtr<U2FRegisterPromise>
U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo) U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation)
{ {
if (!mInitialized) { if (!mInitialized) {
nsresult rv = Init(); nsresult rv = Init();
@ -592,7 +593,9 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
} }
} }
const WebAuthnAuthenticatorSelection& sel = aInfo.AuthenticatorSelection(); if (aInfo.Extra().type() != WebAuthnMaybeMakeCredentialExtraInfo::Tnull_t) {
const auto& extra = aInfo.Extra().get_WebAuthnMakeCredentialExtraInfo();
const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection();
// The U2F softtoken neither supports resident keys or // The U2F softtoken neither supports resident keys or
// user verification, nor is it a platform authenticator. // user verification, nor is it a platform authenticator.
@ -601,11 +604,20 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
sel.requirePlatformAttachment()) { sel.requirePlatformAttachment()) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__); return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
} }
}
CryptoBuffer rpIdHash, clientDataHash;
NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
rpIdHash, clientDataHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
}
// Optional exclusion list. // Optional exclusion list.
for (const WebAuthnScopedCredential& cred: aInfo.ExcludeList()) { for (const WebAuthnScopedCredential& cred: aInfo.ExcludeList()) {
bool isRegistered = false; bool isRegistered = false;
nsresult rv = IsRegistered(cred.id(), aInfo.RpIdHash(), isRegistered); nsresult rv = IsRegistered(cred.id(), rpIdHash, isRegistered);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return U2FRegisterPromise::CreateAndReject(rv, __func__); return U2FRegisterPromise::CreateAndReject(rv, __func__);
} }
@ -623,7 +635,7 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
// Construct a one-time-use Attestation Certificate // Construct a one-time-use Attestation Certificate
UniqueSECKEYPrivateKey attestPrivKey; UniqueSECKEYPrivateKey attestPrivKey;
UniqueCERTCertificate attestCert; UniqueCERTCertificate attestCert;
nsresult rv = GetAttestationCertificate(slot, attestPrivKey, attestCert); rv = GetAttestationCertificate(slot, attestPrivKey, attestCert);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
} }
@ -641,16 +653,16 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
// The key handle will be the result of keywrap(privKey, key=mWrappingKey) // The key handle will be the result of keywrap(privKey, key=mWrappingKey)
UniqueSECItem keyHandleItem = UniqueSECItem keyHandleItem =
KeyHandleFromPrivateKey(slot, mWrappingKey, KeyHandleFromPrivateKey(slot, mWrappingKey,
const_cast<uint8_t*>(aInfo.RpIdHash().Elements()), const_cast<uint8_t*>(rpIdHash.Elements()),
aInfo.RpIdHash().Length(), privKey); rpIdHash.Length(), privKey);
if (NS_WARN_IF(!keyHandleItem.get())) { if (NS_WARN_IF(!keyHandleItem.get())) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
} }
// Sign the challenge using the Attestation privkey (from attestCert) // Sign the challenge using the Attestation privkey (from attestCert)
mozilla::dom::CryptoBuffer signedDataBuf; mozilla::dom::CryptoBuffer signedDataBuf;
if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + aInfo.RpIdHash().Length() + if (NS_WARN_IF(!signedDataBuf.SetCapacity(1 + rpIdHash.Length() +
aInfo.ClientDataHash().Length() + clientDataHash.Length() +
keyHandleItem->len + kPublicKeyLen, keyHandleItem->len + kPublicKeyLen,
mozilla::fallible))) { mozilla::fallible))) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); return U2FRegisterPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
@ -659,8 +671,8 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
// // It's OK to ignore the return values here because we're writing into // // It's OK to ignore the return values here because we're writing into
// // pre-allocated space // // pre-allocated space
signedDataBuf.AppendElement(0x00, mozilla::fallible); signedDataBuf.AppendElement(0x00, mozilla::fallible);
signedDataBuf.AppendElements(aInfo.RpIdHash(), mozilla::fallible); signedDataBuf.AppendElements(rpIdHash, mozilla::fallible);
signedDataBuf.AppendElements(aInfo.ClientDataHash(), mozilla::fallible); signedDataBuf.AppendElements(clientDataHash, mozilla::fallible);
signedDataBuf.AppendSECItem(keyHandleItem.get()); signedDataBuf.AppendSECItem(keyHandleItem.get());
signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue); signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue);
@ -688,10 +700,36 @@ U2FSoftTokenManager::Register(const WebAuthnMakeCredentialInfo& aInfo)
registrationBuf.AppendSECItem(attestCert.get()->derCert); registrationBuf.AppendSECItem(attestCert.get()->derCert);
registrationBuf.AppendSECItem(signatureItem); registrationBuf.AppendSECItem(signatureItem);
// Will be set by the U2FTokenManager. CryptoBuffer keyHandleBuf;
bool directAttestationPermitted = false; if (!keyHandleBuf.AppendSECItem(keyHandleItem.get())) {
WebAuthnMakeCredentialResult result((nsTArray<uint8_t>(registrationBuf)), return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
directAttestationPermitted); }
CryptoBuffer attestCertBuf;
if (!attestCertBuf.AppendSECItem(attestCert.get()->derCert)) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
CryptoBuffer signatureBuf;
if (!signatureBuf.AppendSECItem(signatureItem)) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
CryptoBuffer pubKeyBuf;
if (!pubKeyBuf.AppendSECItem(pubKey->u.ec.publicValue)) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
CryptoBuffer attObj;
rv = AssembleAttestationObject(rpIdHash, pubKeyBuf, keyHandleBuf,
attestCertBuf, signatureBuf,
aForceNoneAttestation, attObj);
if (NS_FAILED(rv)) {
return U2FRegisterPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
WebAuthnMakeCredentialResult result(aInfo.ClientDataJSON(), attObj,
keyHandleBuf, registrationBuf);
return U2FRegisterPromise::CreateAndResolve(Move(result), __func__); return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
} }
@ -742,20 +780,32 @@ U2FSoftTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
} }
} }
// The U2F softtoken doesn't support user verification. CryptoBuffer rpIdHash, clientDataHash;
if (aInfo.RequireUserVerification()) { NS_ConvertUTF16toUTF8 rpId(aInfo.RpId());
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__); nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(),
rpIdHash, clientDataHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
} }
nsTArray<nsTArray<uint8_t>> appIds; nsTArray<nsTArray<uint8_t>> appIds;
appIds.AppendElement(aInfo.RpIdHash()); appIds.AppendElement(rpIdHash);
if (aInfo.Extra().type() != WebAuthnMaybeGetAssertionExtraInfo::Tnull_t) {
const auto& extra = aInfo.Extra().get_WebAuthnGetAssertionExtraInfo();
// The U2F softtoken doesn't support user verification.
if (extra.RequireUserVerification()) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
}
// Process extensions. // Process extensions.
for (const WebAuthnExtension& ext: aInfo.Extensions()) { for (const WebAuthnExtension& ext: extra.Extensions()) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) { if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId()); appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
} }
} }
}
nsTArray<uint8_t> chosenAppId; nsTArray<uint8_t> chosenAppId;
nsTArray<uint8_t> keyHandle; nsTArray<uint8_t> keyHandle;
@ -770,11 +820,11 @@ U2FSoftTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
UniquePK11SlotInfo slot(PK11_GetInternalSlot()); UniquePK11SlotInfo slot(PK11_GetInternalSlot());
MOZ_ASSERT(slot.get()); MOZ_ASSERT(slot.get());
if (NS_WARN_IF((aInfo.ClientDataHash().Length() != kParamLen) || if (NS_WARN_IF((clientDataHash.Length() != kParamLen) ||
(chosenAppId.Length() != kParamLen))) { (chosenAppId.Length() != kParamLen))) {
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
("Parameter lengths are wrong! challenge=%d app=%d expected=%d", ("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
(uint32_t)aInfo.ClientDataHash().Length(), (uint32_t)clientDataHash.Length(),
(uint32_t)chosenAppId.Length(), kParamLen)); (uint32_t)chosenAppId.Length(), kParamLen));
return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__); return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
@ -820,8 +870,8 @@ U2FSoftTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
mozilla::fallible); mozilla::fallible);
signedDataBuf.AppendElement(0x01, mozilla::fallible); signedDataBuf.AppendElement(0x01, mozilla::fallible);
signedDataBuf.AppendSECItem(counterItem); signedDataBuf.AppendSECItem(counterItem);
signedDataBuf.AppendElements(aInfo.ClientDataHash().Elements(), signedDataBuf.AppendElements(clientDataHash.Elements(),
aInfo.ClientDataHash().Length(), clientDataHash.Length(),
mozilla::fallible); mozilla::fallible);
if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) { if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
@ -847,27 +897,51 @@ U2FSoftTokenManager::Sign(const WebAuthnGetAssertionInfo& aInfo)
} }
// Assemble the signature data into a buffer for return // Assemble the signature data into a buffer for return
mozilla::dom::CryptoBuffer signatureBuf; mozilla::dom::CryptoBuffer signatureDataBuf;
if (NS_WARN_IF(!signatureBuf.SetCapacity(1 + counterItem.len + signatureItem.len, if (NS_WARN_IF(!signatureDataBuf.SetCapacity(1 + counterItem.len + signatureItem.len,
mozilla::fallible))) { mozilla::fallible))) {
return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
} }
// It's OK to ignore the return values here because we're writing into // It's OK to ignore the return values here because we're writing into
// pre-allocated space // pre-allocated space
signatureBuf.AppendElement(0x01, mozilla::fallible); signatureDataBuf.AppendElement(0x01, mozilla::fallible);
signatureBuf.AppendSECItem(counterItem); signatureDataBuf.AppendSECItem(counterItem);
signatureBuf.AppendSECItem(signatureItem); signatureDataBuf.AppendSECItem(signatureItem);
nsTArray<uint8_t> signature(signatureBuf);
nsTArray<WebAuthnExtensionResult> extensions; nsTArray<WebAuthnExtensionResult> extensions;
if (chosenAppId != aInfo.RpIdHash()) { if (chosenAppId != rpIdHash) {
// Indicate to the RP that we used the FIDO appId. // Indicate to the RP that we used the FIDO appId.
extensions.AppendElement(WebAuthnExtensionResultAppId(true)); extensions.AppendElement(WebAuthnExtensionResultAppId(true));
} }
WebAuthnGetAssertionResult result(chosenAppId, keyHandle, signature, extensions); CryptoBuffer counterBuf;
if (!counterBuf.AppendSECItem(counterItem)) {
return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
}
CryptoBuffer signatureBuf;
if (!signatureBuf.AppendSECItem(signatureItem)) {
return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
}
CryptoBuffer chosenAppIdBuf;
if (!chosenAppIdBuf.Assign(chosenAppId)) {
return U2FSignPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
}
CryptoBuffer authenticatorData;
CryptoBuffer emptyAttestationData;
rv = AssembleAuthenticatorData(chosenAppIdBuf, 0x01, counterBuf,
emptyAttestationData, authenticatorData);
if (NS_WARN_IF(NS_FAILED(rv))) {
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
WebAuthnGetAssertionResult result(aInfo.ClientDataJSON(), keyHandle,
signatureBuf, authenticatorData,
extensions, signatureDataBuf);
return U2FSignPromise::CreateAndResolve(Move(result), __func__); return U2FSignPromise::CreateAndResolve(Move(result), __func__);
} }

Просмотреть файл

@ -24,7 +24,8 @@ public:
explicit U2FSoftTokenManager(uint32_t aCounter); explicit U2FSoftTokenManager(uint32_t aCounter);
RefPtr<U2FRegisterPromise> RefPtr<U2FRegisterPromise>
Register(const WebAuthnMakeCredentialInfo& aInfo) override; Register(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation) override;
RefPtr<U2FSignPromise> RefPtr<U2FSignPromise>
Sign(const WebAuthnGetAssertionInfo& aInfo) override; Sign(const WebAuthnGetAssertionInfo& aInfo) override;

Просмотреть файл

@ -14,11 +14,7 @@
#include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ClearOnShutdown.h" #include "mozilla/ClearOnShutdown.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
#include "hasht.h"
#include "nsICryptoHash.h"
#include "nsTextFormatter.h" #include "nsTextFormatter.h"
#include "pkix/Input.h"
#include "pkixutil.h"
// Not named "security.webauth.u2f_softtoken_counter" because setting that // Not named "security.webauth.u2f_softtoken_counter" because setting that
// name causes the window.u2f object to disappear until preferences get // name causes the window.u2f object to disappear until preferences get
@ -303,23 +299,26 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
return; return;
} }
// Check if all the supplied parameters are syntactically well-formed and mLastTransactionId = aTransactionId;
// of the correct length. If not, return an error code equivalent to
// UnknownError and terminate the operation.
if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) || // Determine whether direct attestation was requested.
(aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) { bool directAttestationRequested = false;
AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR); if (aTransactionInfo.Extra().type() == WebAuthnMaybeMakeCredentialExtraInfo::TWebAuthnMakeCredentialExtraInfo) {
const auto& extra = aTransactionInfo.Extra().get_WebAuthnMakeCredentialExtraInfo();
directAttestationRequested = extra.RequestDirectAttestation();
}
// Start a register request immediately if direct attestation
// wasn't requested or the test pref is set.
if (!directAttestationRequested ||
U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
// Force "none" attestation when "direct" attestation wasn't requested.
DoRegister(aTransactionInfo, !directAttestationRequested);
return; return;
} }
mLastTransactionId = aTransactionId;
// If the RP request direct attestation, ask the user for permission and // If the RP request direct attestation, ask the user for permission and
// store the transaction info until the user proceeds or cancels. // store the transaction info until the user proceeds or cancels.
// Might be overriden by a pref for testing purposes.
if (aTransactionInfo.RequestDirectAttestation() &&
!U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin()); NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
SendPromptNotification(kRegisterDirectPromptNotifcation, SendPromptNotification(kRegisterDirectPromptNotifcation,
aTransactionId, aTransactionId,
@ -327,13 +326,11 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
MOZ_ASSERT(mPendingRegisterInfo.isNothing()); MOZ_ASSERT(mPendingRegisterInfo.isNothing());
mPendingRegisterInfo = Some(aTransactionInfo); mPendingRegisterInfo = Some(aTransactionInfo);
} else {
DoRegister(aTransactionInfo);
}
} }
void void
U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo) U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
MOZ_ASSERT(mLastTransactionId > 0); MOZ_ASSERT(mLastTransactionId > 0);
@ -346,17 +343,12 @@ U2FTokenManager::DoRegister(const WebAuthnMakeCredentialInfo& aInfo)
uint64_t tid = mLastTransactionId; uint64_t tid = mLastTransactionId;
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now(); mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
bool requestDirectAttestation = aInfo.RequestDirectAttestation();
mTokenManagerImpl mTokenManagerImpl
->Register(aInfo) ->Register(aInfo, aForceNoneAttestation)
->Then(GetCurrentThreadSerialEventTarget(), __func__, ->Then(GetCurrentThreadSerialEventTarget(), __func__,
[tid, startTime, requestDirectAttestation](WebAuthnMakeCredentialResult&& aResult) { [tid, startTime](WebAuthnMakeCredentialResult&& aResult) {
U2FTokenManager* mgr = U2FTokenManager::Get(); U2FTokenManager* mgr = U2FTokenManager::Get();
// The token manager implementations set DirectAttestationPermitted
// to false by default. Override this here with information from
// the JS prompt.
aResult.DirectAttestationPermitted() = requestDirectAttestation;
mgr->MaybeConfirmRegister(tid, aResult); mgr->MaybeConfirmRegister(tid, aResult);
Telemetry::ScalarAdd( Telemetry::ScalarAdd(
Telemetry::ScalarID::SECURITY_WEBAUTHN_USED, Telemetry::ScalarID::SECURITY_WEBAUTHN_USED,
@ -412,12 +404,6 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
return; return;
} }
if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
(aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
// Show a prompt that lets the user cancel the ongoing transaction. // Show a prompt that lets the user cancel the ongoing transaction.
NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin()); NS_ConvertUTF16toUTF8 origin(aTransactionInfo.Origin());
SendPromptNotification(kSignPromptNotifcation, SendPromptNotification(kSignPromptNotifcation,
@ -487,7 +473,7 @@ U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent,
NS_IMETHODIMP NS_IMETHODIMP
U2FTokenManager::ResumeRegister(uint64_t aTransactionId, U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
bool aPermitDirectAttestation) bool aForceNoneAttestation)
{ {
MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -499,14 +485,14 @@ U2FTokenManager::ResumeRegister(uint64_t aTransactionId,
nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>( nsCOMPtr<nsIRunnable> r(NewRunnableMethod<uint64_t, bool>(
"U2FTokenManager::RunResumeRegister", this, "U2FTokenManager::RunResumeRegister", this,
&U2FTokenManager::RunResumeRegister, aTransactionId, &U2FTokenManager::RunResumeRegister, aTransactionId,
aPermitDirectAttestation)); aForceNoneAttestation));
return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); return gBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
} }
void void
U2FTokenManager::RunResumeRegister(uint64_t aTransactionId, U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
bool aPermitDirectAttestation) bool aForceNoneAttestation)
{ {
mozilla::ipc::AssertIsOnBackgroundThread(); mozilla::ipc::AssertIsOnBackgroundThread();
@ -518,12 +504,8 @@ U2FTokenManager::RunResumeRegister(uint64_t aTransactionId,
return; return;
} }
// Forward whether the user opted into direct attestation.
mPendingRegisterInfo.ref().RequestDirectAttestation() =
aPermitDirectAttestation;
// Resume registration and cleanup. // Resume registration and cleanup.
DoRegister(mPendingRegisterInfo.ref()); DoRegister(mPendingRegisterInfo.ref(), aForceNoneAttestation);
mPendingRegisterInfo.reset(); mPendingRegisterInfo.reset();
} }

Просмотреть файл

@ -51,7 +51,8 @@ private:
void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError); void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError);
void ClearTransaction(); void ClearTransaction();
// Step two of "Register", kicking off the actual transaction. // Step two of "Register", kicking off the actual transaction.
void DoRegister(const WebAuthnMakeCredentialInfo& aInfo); void DoRegister(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation);
void MaybeConfirmRegister(const uint64_t& aTransactionId, void MaybeConfirmRegister(const uint64_t& aTransactionId,
const WebAuthnMakeCredentialResult& aResult); const WebAuthnMakeCredentialResult& aResult);
void MaybeAbortRegister(const uint64_t& aTransactionId, const nsresult& aError); void MaybeAbortRegister(const uint64_t& aTransactionId, const nsresult& aError);
@ -59,7 +60,7 @@ private:
const WebAuthnGetAssertionResult& aResult); const WebAuthnGetAssertionResult& aResult);
void MaybeAbortSign(const uint64_t& aTransactionId, const nsresult& aError); void MaybeAbortSign(const uint64_t& aTransactionId, const nsresult& aError);
// The main thread runnable function for "nsIU2FTokenManager.ResumeRegister". // The main thread runnable function for "nsIU2FTokenManager.ResumeRegister".
void RunResumeRegister(uint64_t aTransactionId, bool aPermitDirectAttestation); void RunResumeRegister(uint64_t aTransactionId, bool aForceNoneAttestation);
// The main thread runnable function for "nsIU2FTokenManager.Cancel". // The main thread runnable function for "nsIU2FTokenManager.Cancel".
void RunCancel(uint64_t aTransactionId); void RunCancel(uint64_t aTransactionId);
// Sends a "webauthn-prompt" observer notification with the given data. // Sends a "webauthn-prompt" observer notification with the given data.

Просмотреть файл

@ -28,7 +28,8 @@ public:
U2FTokenTransport() {} U2FTokenTransport() {}
virtual RefPtr<U2FRegisterPromise> virtual RefPtr<U2FRegisterPromise>
Register(const WebAuthnMakeCredentialInfo& aInfo) = 0; Register(const WebAuthnMakeCredentialInfo& aInfo,
bool aForceNoneAttestation) = 0;
virtual RefPtr<U2FSignPromise> virtual RefPtr<U2FSignPromise>
Sign(const WebAuthnGetAssertionInfo& aInfo) = 0; Sign(const WebAuthnGetAssertionInfo& aInfo) = 0;

Просмотреть файл

@ -6,15 +6,11 @@
#include "hasht.h" #include "hasht.h"
#include "nsHTMLDocument.h" #include "nsHTMLDocument.h"
#include "nsICryptoHash.h"
#include "nsNetCID.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "WebAuthnCoseIdentifiers.h" #include "WebAuthnCoseIdentifiers.h"
#include "mozilla/dom/AuthenticatorAttestationResponse.h" #include "mozilla/dom/AuthenticatorAttestationResponse.h"
#include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise.h"
#include "mozilla/dom/PWebAuthnTransaction.h" #include "mozilla/dom/PWebAuthnTransaction.h"
#include "mozilla/dom/U2FUtil.h"
#include "mozilla/dom/WebAuthnCBORUtil.h"
#include "mozilla/dom/WebAuthnManager.h" #include "mozilla/dom/WebAuthnManager.h"
#include "mozilla/dom/WebAuthnTransactionChild.h" #include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h" #include "mozilla/dom/WebAuthnUtil.h"
@ -26,15 +22,6 @@ using namespace mozilla::ipc;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
/***********************************************************************
* Protocol Constants
**********************************************************************/
const uint8_t FLAG_TUP = 0x01; // Test of User Presence required
const uint8_t FLAG_AT = 0x40; // Authenticator Data is provided
const uint8_t FLAG_UV = 0x04; // User was Verified (biometrics, etc.); this
// flag is not possible with U2F devices
/*********************************************************************** /***********************************************************************
* Statics * Statics
**********************************************************************/ **********************************************************************/
@ -271,27 +258,6 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
return promise.forget(); return promise.forget();
} }
CryptoBuffer rpIdHash;
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
}
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
srv = HashCString(hashService, rpId, rpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
// TODO: Move this logic into U2FTokenManager in Bug 1409220. // TODO: Move this logic into U2FTokenManager in Bug 1409220.
// Process each element of mPubKeyCredParams using the following steps, to // Process each element of mPubKeyCredParams using the following steps, to
@ -351,7 +317,7 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
} }
nsAutoCString clientDataJSON; nsAutoCString clientDataJSON;
srv = AssembleClientData(origin, challenge, nsresult srv = AssembleClientData(origin, challenge,
NS_LITERAL_STRING("webauthn.create"), NS_LITERAL_STRING("webauthn.create"),
aOptions.mExtensions, clientDataJSON); aOptions.mExtensions, clientDataJSON);
if (NS_WARN_IF(NS_FAILED(srv))) { if (NS_WARN_IF(NS_FAILED(srv))) {
@ -359,18 +325,6 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
return promise.forget(); return promise.forget();
} }
CryptoBuffer clientDataHash;
if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
srv = HashCString(hashService, clientDataJSON, clientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
nsTArray<WebAuthnScopedCredential> excludeList; nsTArray<WebAuthnScopedCredential> excludeList;
for (const auto& s: aOptions.mExcludeCredentials) { for (const auto& s: aOptions.mExcludeCredentials) {
WebAuthnScopedCredential c; WebAuthnScopedCredential c;
@ -410,15 +364,18 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
requireUserVerification, requireUserVerification,
requirePlatformAttachment); requirePlatformAttachment);
WebAuthnMakeCredentialInfo info(origin, WebAuthnMakeCredentialExtraInfo extra(extensions,
rpIdHash,
clientDataHash,
adjustedTimeout,
excludeList,
extensions,
authSelection, authSelection,
requestDirectAttestation); requestDirectAttestation);
WebAuthnMakeCredentialInfo info(origin,
NS_ConvertUTF8toUTF16(rpId),
challenge,
clientDataJSON,
adjustedTimeout,
excludeList,
extra);
ListenForVisibilityEvents(); ListenForVisibilityEvents();
AbortSignal* signal = nullptr; AbortSignal* signal = nullptr;
@ -428,11 +385,7 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
} }
MOZ_ASSERT(mTransaction.isNothing()); MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(WebAuthnTransaction(promise, mTransaction = Some(WebAuthnTransaction(promise, signal));
rpIdHash,
clientDataJSON,
signal));
mChild->SendRequestRegister(mTransaction.ref().mId, info); mChild->SendRequestRegister(mTransaction.ref().mId, info);
return promise.forget(); return promise.forget();
@ -502,20 +455,6 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
return promise.forget(); return promise.forget();
} }
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
srv = HashCString(hashService, rpId, rpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
// Use assertionChallenge, callerOrigin and rpId, along with the token binding // Use assertionChallenge, callerOrigin and rpId, along with the token binding
// key associated with callerOrigin (if any), to create a ClientData structure // key associated with callerOrigin (if any), to create a ClientData structure
// representing this request. Choose a hash algorithm for hashAlg and compute // representing this request. Choose a hash algorithm for hashAlg and compute
@ -527,25 +466,14 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
} }
nsAutoCString clientDataJSON; nsAutoCString clientDataJSON;
srv = AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"), nsresult srv = AssembleClientData(origin, challenge,
NS_LITERAL_STRING("webauthn.get"),
aOptions.mExtensions, clientDataJSON); aOptions.mExtensions, clientDataJSON);
if (NS_WARN_IF(NS_FAILED(srv))) { if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget(); return promise.forget();
} }
CryptoBuffer clientDataHash;
if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
srv = HashCString(hashService, clientDataJSON, clientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
nsTArray<WebAuthnScopedCredential> allowList; nsTArray<WebAuthnScopedCredential> allowList;
for (const auto& s: aOptions.mAllowCredentials) { for (const auto& s: aOptions.mAllowCredentials) {
if (s.mType == PublicKeyCredentialType::Public_key) { if (s.mType == PublicKeyCredentialType::Public_key) {
@ -608,7 +536,7 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
} }
// We need the SHA-256 hash of the appId. // We need the SHA-256 hash of the appId.
nsresult srv = HashCString(hashService, NS_ConvertUTF16toUTF8(appId), appIdHash); srv = HashCString(NS_ConvertUTF16toUTF8(appId), appIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) { if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget(); return promise.forget();
@ -618,13 +546,15 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
extensions.AppendElement(WebAuthnExtensionAppId(appIdHash)); extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
} }
WebAuthnGetAssertionExtraInfo extra(extensions, requireUserVerification);
WebAuthnGetAssertionInfo info(origin, WebAuthnGetAssertionInfo info(origin,
rpIdHash, NS_ConvertUTF8toUTF16(rpId),
clientDataHash, challenge,
clientDataJSON,
adjustedTimeout, adjustedTimeout,
allowList, allowList,
requireUserVerification, extra);
extensions);
ListenForVisibilityEvents(); ListenForVisibilityEvents();
@ -635,12 +565,9 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
} }
MOZ_ASSERT(mTransaction.isNothing()); MOZ_ASSERT(mTransaction.isNothing());
mTransaction = Some(WebAuthnTransaction(promise, mTransaction = Some(WebAuthnTransaction(promise, signal));
rpIdHash,
clientDataJSON,
signal));
mChild->SendRequestSign(mTransaction.ref().mId, info); mChild->SendRequestSign(mTransaction.ref().mId, info);
return promise.forget(); return promise.forget();
} }
@ -676,120 +603,38 @@ WebAuthnManager::FinishMakeCredential(const uint64_t& aTransactionId,
return; return;
} }
CryptoBuffer regData; CryptoBuffer clientDataBuf;
if (NS_WARN_IF(!regData.Assign(aResult.RegBuffer().Elements(), if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
aResult.RegBuffer().Length()))) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY); RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return; return;
} }
mozilla::dom::CryptoBuffer aaguidBuf; CryptoBuffer attObjBuf;
if (NS_WARN_IF(!aaguidBuf.SetCapacity(16, mozilla::fallible))) { if (NS_WARN_IF(!attObjBuf.Assign(aResult.AttestationObject()))) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY); RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return; return;
} }
// FIDO U2F devices have no AAGUIDs, so they'll be all zeros until we add
// support for CTAP2 devices.
for (int i=0; i<16; i++) {
aaguidBuf.AppendElement(0x00, mozilla::fallible);
}
// Decompose the U2F registration packet
CryptoBuffer pubKeyBuf;
CryptoBuffer keyHandleBuf; CryptoBuffer keyHandleBuf;
CryptoBuffer attestationCertBuf; if (NS_WARN_IF(!keyHandleBuf.Assign(aResult.KeyHandle()))) {
CryptoBuffer signatureBuf; RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
// Only handles attestation cert chains of length=1.
nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
attestationCertBuf, signatureBuf);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectTransaction(rv);
return; return;
} }
MOZ_ASSERT(keyHandleBuf.Length() <= 0xFFFF);
nsAutoString keyHandleBase64Url; nsAutoString keyHandleBase64Url;
rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url); nsresult rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
RejectTransaction(rv); RejectTransaction(rv);
return; return;
} }
CryptoBuffer clientDataBuf;
if (!clientDataBuf.Assign(mTransaction.ref().mClientData)) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return;
}
CryptoBuffer rpIdHashBuf;
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return;
}
// Construct the public key object
CryptoBuffer pubKeyObj;
rv = CBOREncodePublicKeyObj(pubKeyBuf, pubKeyObj);
if (NS_FAILED(rv)) {
RejectTransaction(rv);
return;
}
// During create credential, counter is always 0 for U2F
// See https://github.com/w3c/webauthn/issues/507
mozilla::dom::CryptoBuffer counterBuf;
if (NS_WARN_IF(!counterBuf.SetCapacity(4, mozilla::fallible))) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return;
}
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
// Construct the Attestation Data, which slots into the end of the
// Authentication Data buffer.
CryptoBuffer attDataBuf;
rv = AssembleAttestationData(aaguidBuf, keyHandleBuf, pubKeyObj, attDataBuf);
if (NS_FAILED(rv)) {
RejectTransaction(rv);
return;
}
mozilla::dom::CryptoBuffer authDataBuf;
// attDataBuf always contains data, so per [1] we have to set the AT flag.
// [1] https://w3c.github.io/webauthn/#sec-authenticator-data
const uint8_t flags = FLAG_TUP | FLAG_AT;
rv = AssembleAuthenticatorData(rpIdHashBuf, flags, counterBuf, attDataBuf,
authDataBuf);
if (NS_FAILED(rv)) {
RejectTransaction(rv);
return;
}
// Direct attestation might have been requested by the RP. This will
// be true only if the user consented via the permission UI.
CryptoBuffer attObj;
if (aResult.DirectAttestationPermitted()) {
rv = CBOREncodeFidoU2FAttestationObj(authDataBuf, attestationCertBuf,
signatureBuf, attObj);
} else {
rv = CBOREncodeNoneAttestationObj(authDataBuf, attObj);
}
if (NS_FAILED(rv)) {
RejectTransaction(rv);
return;
}
// Create a new PublicKeyCredential object and populate its fields with the // Create a new PublicKeyCredential object and populate its fields with the
// values returned from the authenticator as well as the clientDataJSON // values returned from the authenticator as well as the clientDataJSON
// computed earlier. // computed earlier.
RefPtr<AuthenticatorAttestationResponse> attestation = RefPtr<AuthenticatorAttestationResponse> attestation =
new AuthenticatorAttestationResponse(mParent); new AuthenticatorAttestationResponse(mParent);
attestation->SetClientDataJSON(clientDataBuf); attestation->SetClientDataJSON(clientDataBuf);
attestation->SetAttestationObject(attObj); attestation->SetAttestationObject(attObjBuf);
RefPtr<PublicKeyCredential> credential = RefPtr<PublicKeyCredential> credential =
new PublicKeyCredential(mParent); new PublicKeyCredential(mParent);
@ -813,57 +658,32 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
return; return;
} }
CryptoBuffer tokenSignatureData;
if (NS_WARN_IF(!tokenSignatureData.Assign(aResult.SigBuffer().Elements(),
aResult.SigBuffer().Length()))) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return;
}
CryptoBuffer clientDataBuf; CryptoBuffer clientDataBuf;
if (!clientDataBuf.Assign(mTransaction.ref().mClientData)) { if (!clientDataBuf.Assign(aResult.ClientDataJSON())) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY); RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return; return;
} }
CryptoBuffer rpIdHashBuf; CryptoBuffer credentialBuf;
if (!rpIdHashBuf.Assign(aResult.RpIdHash())) { if (!credentialBuf.Assign(aResult.KeyHandle())) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY); RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return; return;
} }
CryptoBuffer signatureBuf; CryptoBuffer signatureBuf;
CryptoBuffer counterBuf; if (!signatureBuf.Assign(aResult.Signature())) {
uint8_t flags = 0; RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
nsresult rv = U2FDecomposeSignResponse(tokenSignatureData, flags, counterBuf,
signatureBuf);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectTransaction(rv);
return; return;
} }
// Preserve the two LSBs of the flags byte, UP and RFU1.
// See <https://github.com/fido-alliance/fido-2-specs/pull/519>
flags &= 0b11;
CryptoBuffer attestationDataBuf;
CryptoBuffer authenticatorDataBuf; CryptoBuffer authenticatorDataBuf;
rv = AssembleAuthenticatorData(rpIdHashBuf, flags, counterBuf, if (!authenticatorDataBuf.Assign(aResult.AuthenticatorData())) {
/* deliberately empty */ attestationDataBuf,
authenticatorDataBuf);
if (NS_WARN_IF(NS_FAILED(rv))) {
RejectTransaction(rv);
return;
}
CryptoBuffer credentialBuf;
if (!credentialBuf.Assign(aResult.CredentialID())) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY); RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return; return;
} }
nsAutoString credentialBase64Url; nsAutoString credentialBase64Url;
rv = credentialBuf.ToJwkBase64(credentialBase64Url); nsresult rv = credentialBuf.ToJwkBase64(credentialBase64Url);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
RejectTransaction(rv); RejectTransaction(rv);
return; return;

Просмотреть файл

@ -48,12 +48,8 @@ class WebAuthnTransaction
{ {
public: public:
WebAuthnTransaction(const RefPtr<Promise>& aPromise, WebAuthnTransaction(const RefPtr<Promise>& aPromise,
const nsTArray<uint8_t>& aRpIdHash,
const nsCString& aClientData,
AbortSignal* aSignal) AbortSignal* aSignal)
: mPromise(aPromise) : mPromise(aPromise)
, mRpIdHash(aRpIdHash)
, mClientData(aClientData)
, mSignal(aSignal) , mSignal(aSignal)
, mId(NextId()) , mId(NextId())
{ {
@ -63,12 +59,6 @@ public:
// JS Promise representing the transaction status. // JS Promise representing the transaction status.
RefPtr<Promise> mPromise; RefPtr<Promise> mPromise;
// The RP ID hash.
nsTArray<uint8_t> mRpIdHash;
// Client data used to assemble reply objects.
nsCString mClientData;
// An optional AbortSignal instance. // An optional AbortSignal instance.
RefPtr<AbortSignal> mSignal; RefPtr<AbortSignal> mSignal;

Просмотреть файл

@ -18,6 +18,9 @@ NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1,
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId2, NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId2,
"https://www.gstatic.com/securitykey/a/google.com/origins.json"); "https://www.gstatic.com/securitykey/a/google.com/origins.json");
const uint8_t FLAG_TUP = 0x01; // Test of User Presence required
const uint8_t FLAG_AT = 0x40; // Authenticator Data is provided
bool bool
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin, EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
const U2FOperation& aOp, /* in/out */ nsString& aAppId) const U2FOperation& aOp, /* in/out */ nsString& aAppId)
@ -193,6 +196,74 @@ AssembleAttestationData(const CryptoBuffer& aaguidBuf,
return NS_OK; return NS_OK;
} }
nsresult
AssembleAttestationObject(const CryptoBuffer& aRpIdHash,
const CryptoBuffer& aPubKeyBuf,
const CryptoBuffer& aKeyHandleBuf,
const CryptoBuffer& aAttestationCertBuf,
const CryptoBuffer& aSignatureBuf,
bool aForceNoneAttestation,
/* out */ CryptoBuffer& aAttestationObjBuf)
{
// Construct the public key object
CryptoBuffer pubKeyObj;
nsresult rv = CBOREncodePublicKeyObj(aPubKeyBuf, pubKeyObj);
if (NS_FAILED(rv)) {
return rv;
}
mozilla::dom::CryptoBuffer aaguidBuf;
if (NS_WARN_IF(!aaguidBuf.SetCapacity(16, mozilla::fallible))) {
return NS_ERROR_OUT_OF_MEMORY;
}
// FIDO U2F devices have no AAGUIDs, so they'll be all zeros until we add
// support for CTAP2 devices.
for (int i=0; i<16; i++) {
aaguidBuf.AppendElement(0x00, mozilla::fallible);
}
// During create credential, counter is always 0 for U2F
// See https://github.com/w3c/webauthn/issues/507
mozilla::dom::CryptoBuffer counterBuf;
if (NS_WARN_IF(!counterBuf.SetCapacity(4, mozilla::fallible))) {
return NS_ERROR_OUT_OF_MEMORY;
}
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
counterBuf.AppendElement(0x00, mozilla::fallible);
// Construct the Attestation Data, which slots into the end of the
// Authentication Data buffer.
CryptoBuffer attDataBuf;
rv = AssembleAttestationData(aaguidBuf, aKeyHandleBuf, pubKeyObj, attDataBuf);
if (NS_FAILED(rv)) {
return rv;
}
CryptoBuffer authDataBuf;
// attDataBuf always contains data, so per [1] we have to set the AT flag.
// [1] https://w3c.github.io/webauthn/#sec-authenticator-data
const uint8_t flags = FLAG_TUP | FLAG_AT;
rv = AssembleAuthenticatorData(aRpIdHash, flags, counterBuf, attDataBuf,
authDataBuf);
if (NS_FAILED(rv)) {
return rv;
}
// Direct attestation might have been requested by the RP.
// The user might override this and request anonymization anyway.
if (aForceNoneAttestation) {
rv = CBOREncodeNoneAttestationObj(authDataBuf, aAttestationObjBuf);
} else {
rv = CBOREncodeFidoU2FAttestationObj(authDataBuf, aAttestationCertBuf,
aSignatureBuf, aAttestationObjBuf);
}
return rv;
}
nsresult nsresult
U2FDecomposeSignResponse(const CryptoBuffer& aResponse, U2FDecomposeSignResponse(const CryptoBuffer& aResponse,
/* out */ uint8_t& aFlags, /* out */ uint8_t& aFlags,
@ -323,5 +394,89 @@ U2FDecomposeECKey(const CryptoBuffer& aPubKeyBuf,
return NS_OK; return NS_OK;
} }
static nsresult
HashCString(nsICryptoHash* aHashService,
const nsACString& aIn,
/* out */ CryptoBuffer& aOut)
{
MOZ_ASSERT(aHashService);
nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aHashService->Update(
reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoCString fullHash;
// Passing false below means we will get a binary result rather than a
// base64-encoded string.
rv = aHashService->Finish(false, fullHash);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!aOut.Assign(fullHash))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
nsresult
HashCString(const nsACString& aIn, /* out */ CryptoBuffer& aOut)
{
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_FAILED(srv)) {
return srv;
}
srv = HashCString(hashService, aIn, aOut);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash)
{
nsresult srv;
nsCOMPtr<nsICryptoHash> hashService =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
if (NS_FAILED(srv)) {
return srv;
}
if (!aRpIdHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aRpId, aRpIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
if (!aClientDataHash.SetLength(SHA256_LENGTH, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
srv = HashCString(hashService, aClientDataJSON, aClientDataHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
} }
} }

Просмотреть файл

@ -12,7 +12,6 @@
*/ */
#include "mozilla/dom/CryptoBuffer.h" #include "mozilla/dom/CryptoBuffer.h"
#include "pkix/Input.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -35,10 +34,13 @@ AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
/* out */ CryptoBuffer& authDataBuf); /* out */ CryptoBuffer& authDataBuf);
nsresult nsresult
AssembleAttestationData(const CryptoBuffer& aaguidBuf, AssembleAttestationObject(const CryptoBuffer& aRpIdHash,
const CryptoBuffer& keyHandleBuf, const CryptoBuffer& aPubKeyBuf,
const CryptoBuffer& pubKeyObj, const CryptoBuffer& aKeyHandleBuf,
/* out */ CryptoBuffer& attestationDataBuf); const CryptoBuffer& aAttestationCertBuf,
const CryptoBuffer& aSignatureBuf,
bool aForceNoneAttestation,
/* out */ CryptoBuffer& aAttestationObjBuf);
nsresult nsresult
U2FDecomposeSignResponse(const CryptoBuffer& aResponse, U2FDecomposeSignResponse(const CryptoBuffer& aResponse,
@ -53,15 +55,20 @@ U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
/* out */ CryptoBuffer& aAttestationCertBuf, /* out */ CryptoBuffer& aAttestationCertBuf,
/* out */ CryptoBuffer& aSignatureBuf); /* out */ CryptoBuffer& aSignatureBuf);
nsresult
ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
uint32_t aLen);
nsresult nsresult
U2FDecomposeECKey(const CryptoBuffer& aPubKeyBuf, U2FDecomposeECKey(const CryptoBuffer& aPubKeyBuf,
/* out */ CryptoBuffer& aXcoord, /* out */ CryptoBuffer& aXcoord,
/* out */ CryptoBuffer& aYcoord); /* out */ CryptoBuffer& aYcoord);
nsresult
HashCString(const nsACString& aIn, /* out */ CryptoBuffer& aOut);
nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
/* out */ CryptoBuffer& aRpIdHash,
/* out */ CryptoBuffer& aClientDataHash);
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla

Просмотреть файл

@ -22,11 +22,10 @@ interface nsIU2FTokenManager : nsISupports
* and we have to wait for user input to proceed. * and we have to wait for user input to proceed.
* *
* @param aTransactionID : The ID of the transaction to resume. * @param aTransactionID : The ID of the transaction to resume.
* @param aPermitDirectAttestation : Whether direct attestation was * @param aForceNoneAttestation : The user might enforce none attestation.
* permitted by the user.
*/ */
void resumeRegister(in uint64_t aTransactionID, void resumeRegister(in uint64_t aTransactionID,
in bool aPermitDirectAttestation); in bool aForceNoneAttestation);
/** /**
* Cancels the current WebAuthn/U2F transaction if that matches the given * Cancels the current WebAuthn/U2F transaction if that matches the given

Просмотреть файл

@ -98,8 +98,7 @@
.then(verifyAnonymizedCertificate) .then(verifyAnonymizedCertificate)
.catch(arrivingHereIsBad); .catch(arrivingHereIsBad);
// Request direct attestation, which should prompt for user intervention, // Request direct attestation, which will prompt for user intervention.
// once 1430150 lands.
await requestMakeCredential("direct") await requestMakeCredential("direct")
.then(verifyDirectCertificate) .then(verifyDirectCertificate)
.catch(arrivingHereIsBad); .catch(arrivingHereIsBad);

Просмотреть файл

@ -262,11 +262,9 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) -> u64 { pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut U2FManager) {
if !mgr.is_null() { if !mgr.is_null() {
// Ignore return value. // Ignore return value.
let _ = (*mgr).cancel(); let _ = (*mgr).cancel();
} }
new_tid()
} }

Просмотреть файл

@ -78,7 +78,7 @@ uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
const rust_u2f_app_ids* app_ids, const rust_u2f_app_ids* app_ids,
const rust_u2f_key_handles* khs); const rust_u2f_key_handles* khs);
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr); void rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
/// U2FAppIds functions. /// U2FAppIds functions.