Bug 1406471 - Web Authentication - Implement FIDO AppID Extension r=jcj,smaug

Reviewers: jcj, smaug

Reviewed By: jcj

Bug #: 1406471

Differential Revision: https://phabricator.services.mozilla.com/D595
This commit is contained in:
Tim Taubert 2018-02-22 10:53:49 +01:00
Родитель d167e9637d
Коммит 0af61da4ec
24 изменённых файлов: 691 добавлений и 185 удалений

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

@ -9,6 +9,7 @@
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h"
#include "nsContentUtils.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
@ -128,110 +129,6 @@ RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
}
}
enum class U2FOperation
{
Register,
Sign
};
static ErrorCode
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
const U2FOperation& aOp, /* in/out */ nsString& aAppId)
{
// Facet is the specification's way of referring to the web origin.
nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
nsCOMPtr<nsIURI> facetUri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
return ErrorCode::BAD_REQUEST;
}
// If the facetId (origin) is not HTTPS, reject
bool facetIsHttps = false;
if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
return ErrorCode::BAD_REQUEST;
}
// If the appId is empty or null, overwrite it with the facetId and accept
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
aAppId.Assign(aOrigin);
return ErrorCode::OK;
}
// AppID is user-supplied. It's quite possible for this parse to fail.
nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
nsCOMPtr<nsIURI> appIdUri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
return ErrorCode::BAD_REQUEST;
}
// if the appId URL is not HTTPS, reject.
bool appIdIsHttps = false;
if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
return ErrorCode::BAD_REQUEST;
}
nsAutoCString appIdHost;
if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
return ErrorCode::BAD_REQUEST;
}
// Allow localhost.
if (appIdHost.EqualsLiteral("localhost")) {
nsAutoCString facetHost;
if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
return ErrorCode::BAD_REQUEST;
}
if (facetHost.EqualsLiteral("localhost")) {
return ErrorCode::OK;
}
}
// Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
// Web Authentication. See Bug 1244959 comment #8 for context on why we are
// doing this instead of implementing the external-fetch FacetID logic.
nsCOMPtr<nsIDocument> document = aParent->GetDoc();
if (!document || !document->IsHTMLDocument()) {
return ErrorCode::BAD_REQUEST;
}
nsHTMLDocument* html = document->AsHTMLDocument();
if (NS_WARN_IF(!html)) {
return ErrorCode::BAD_REQUEST;
}
// Use the base domain as the facet for evaluation. This lets this algorithm
// relax the whole eTLD+1.
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (!tldService) {
return ErrorCode::BAD_REQUEST;
}
nsAutoCString lowestFacetHost;
if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
return ErrorCode::BAD_REQUEST;
}
MOZ_LOG(gU2FLog, LogLevel::Debug,
("AppId %s Facet %s", appIdHost.get(), lowestFacetHost.get()));
if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
appIdHost)) {
return ErrorCode::OK;
}
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
if (aOp == U2FOperation::Sign && lowestFacetHost.EqualsLiteral("google.com") &&
(aAppId.Equals(kGoogleAccountsAppId1) ||
aAppId.Equals(kGoogleAccountsAppId2))) {
MOZ_LOG(gU2FLog, LogLevel::Debug,
("U2F permitted for Google Accounts via Bug #1436085"));
return ErrorCode::OK;
}
return ErrorCode::BAD_REQUEST;
}
static nsresult
BuildTransactionHashes(const nsCString& aRpId,
const nsCString& aClientDataJSON,
@ -375,13 +272,10 @@ U2F::Register(const nsAString& aAppId,
}
// Evaluate the AppID
nsString adjustedAppId;
adjustedAppId.Assign(aAppId);
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Register,
adjustedAppId);
if (appIdResult != ErrorCode::OK) {
nsString adjustedAppId(aAppId);
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Register, adjustedAppId)) {
RegisterResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
ExecuteCallback(response, callback);
return;
}
@ -538,13 +432,10 @@ U2F::Sign(const nsAString& aAppId,
}
// Evaluate the AppID
nsString adjustedAppId;
adjustedAppId.Assign(aAppId);
ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, U2FOperation::Sign,
adjustedAppId);
if (appIdResult != ErrorCode::OK) {
nsString adjustedAppId(aAppId);
if (!EvaluateAppID(mParent, mOrigin, U2FOperation::Sign, adjustedAppId)) {
SignResponse response;
response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
ExecuteCallback(response, callback);
return;
}

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

@ -30,8 +30,20 @@ struct WebAuthnScopedCredential {
uint8_t transports;
};
struct WebAuthnExtension {
/* TODO Fill in with predefined extensions */
struct WebAuthnExtensionAppId {
uint8_t[] AppId;
};
union WebAuthnExtension {
WebAuthnExtensionAppId;
};
struct WebAuthnExtensionResultAppId {
bool AppId;
};
union WebAuthnExtensionResult {
WebAuthnExtensionResultAppId;
};
struct WebAuthnMakeCredentialInfo {
@ -57,8 +69,10 @@ struct WebAuthnGetAssertionInfo {
};
struct WebAuthnGetAssertionResult {
uint8_t[] RpIdHash;
uint8_t[] CredentialID;
uint8_t[] SigBuffer;
WebAuthnExtensionResult[] Extensions;
};
async protocol PWebAuthnTransaction {

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

@ -115,6 +115,19 @@ PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject&
return promise.forget();
}
void
PublicKeyCredential::GetClientExtensionResults(AuthenticationExtensionsClientOutputs& aResult)
{
aResult = mClientExtensionOutputs;
}
void
PublicKeyCredential::SetClientExtensionResultAppId(bool aResult)
{
mClientExtensionOutputs.mAppid.Construct();
mClientExtensionOutputs.mAppid.Value() = aResult;
}
} // namespace dom
} // namespace mozilla

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

@ -49,12 +49,17 @@ public:
static already_AddRefed<Promise>
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
void
GetClientExtensionResults(AuthenticationExtensionsClientOutputs& aResult);
void
SetClientExtensionResultAppId(bool aResult);
private:
CryptoBuffer mRawId;
JS::Heap<JSObject*> mRawIdCachedObj;
RefPtr<AuthenticatorResponse> mResponse;
// Extensions are not supported yet.
// <some type> mClientExtensionResults;
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
};
} // namespace dom

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

@ -127,6 +127,7 @@ U2FHIDTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredenti
}
ClearPromises();
mCurrentAppId = aApplication;
mTransactionId = rust_u2f_mgr_register(mU2FManager,
registerFlags,
(uint64_t)aTimeoutMS,
@ -164,6 +165,7 @@ RefPtr<U2FSignPromise>
U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
const nsTArray<WebAuthnExtension>& aExtensions,
bool aRequireUserVerification,
uint32_t aTimeoutMS)
{
@ -176,15 +178,25 @@ U2FHIDTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION;
}
nsTArray<nsTArray<uint8_t>> appIds;
appIds.AppendElement(aApplication);
// Process extensions.
for (const WebAuthnExtension& ext: aExtensions) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
}
}
ClearPromises();
mCurrentAppId = aApplication;
mTransactionId = rust_u2f_mgr_sign(mU2FManager,
signFlags,
(uint64_t)aTimeoutMS,
u2f_sign_callback,
aChallenge.Elements(),
aChallenge.Length(),
aApplication.Elements(),
aApplication.Length(),
U2FAppIds(appIds).Get(),
U2FKeyHandles(aCredentials).Get());
if (mTransactionId == 0) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
@ -234,6 +246,12 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
MOZ_ASSERT(!mSignPromise.IsEmpty());
nsTArray<uint8_t> appId;
if (!aResult->CopyAppId(appId)) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
return;
}
nsTArray<uint8_t> keyHandle;
if (!aResult->CopyKeyHandle(keyHandle)) {
mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
@ -246,7 +264,14 @@ U2FHIDTokenManager::HandleSignResult(UniquePtr<U2FResult>&& aResult)
return;
}
WebAuthnGetAssertionResult result(keyHandle, signature);
nsTArray<WebAuthnExtensionResult> extensions;
if (appId != mCurrentAppId) {
// Indicate to the RP that we used the FIDO appId.
extensions.AppendElement(WebAuthnExtensionResultAppId(true));
}
WebAuthnGetAssertionResult result(appId, keyHandle, signature, extensions);
mSignPromise.Resolve(Move(result), __func__);
}

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

@ -18,13 +18,32 @@
namespace mozilla {
namespace dom {
class U2FAppIds {
public:
explicit U2FAppIds(const nsTArray<nsTArray<uint8_t>>& aApplications)
{
mAppIds = rust_u2f_app_ids_new();
for (auto& app_id: aApplications) {
rust_u2f_app_ids_add(mAppIds, app_id.Elements(), app_id.Length());
}
}
rust_u2f_app_ids* Get() { return mAppIds; }
~U2FAppIds() { rust_u2f_app_ids_free(mAppIds); }
private:
rust_u2f_app_ids* mAppIds;
};
class U2FKeyHandles {
public:
explicit U2FKeyHandles(const nsTArray<WebAuthnScopedCredential>& aCredentials)
{
mKeyHandles = rust_u2f_khs_new();
for (auto cred: aCredentials) {
for (auto& cred: aCredentials) {
rust_u2f_khs_add(mKeyHandles,
cred.id().Elements(),
cred.id().Length(),
@ -66,6 +85,11 @@ public:
return CopyBuffer(U2F_RESBUF_ID_SIGNATURE, aBuffer);
}
bool CopyAppId(nsTArray<uint8_t>& aBuffer)
{
return CopyBuffer(U2F_RESBUF_ID_APPID, aBuffer);
}
private:
bool CopyBuffer(uint8_t aResBufID, nsTArray<uint8_t>& aBuffer) {
if (!mResult) {
@ -104,6 +128,7 @@ public:
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
const nsTArray<WebAuthnExtension>& aExtensions,
bool aRequireUserVerification,
uint32_t aTimeoutMS) override;
@ -123,6 +148,7 @@ private:
rust_u2f_manager* mU2FManager;
uint64_t mTransactionId;
nsTArray<uint8_t> mCurrentAppId;
MozPromiseHolder<U2FRegisterPromise> mRegisterPromise;
MozPromiseHolder<U2FSignPromise> mSignPromise;
};

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

@ -693,6 +693,27 @@ U2FSoftTokenManager::Register(const nsTArray<WebAuthnScopedCredential>& aCredent
return U2FRegisterPromise::CreateAndResolve(Move(result), __func__);
}
bool
U2FSoftTokenManager::FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>>& aAppIds,
const nsTArray<WebAuthnScopedCredential>& aCredentials,
/*out*/ nsTArray<uint8_t>& aKeyHandle,
/*out*/ nsTArray<uint8_t>& aAppId)
{
for (const nsTArray<uint8_t>& app_id: aAppIds) {
for (const WebAuthnScopedCredential& cred: aCredentials) {
bool isRegistered = false;
nsresult rv = IsRegistered(cred.id(), app_id, isRegistered);
if (NS_SUCCEEDED(rv) && isRegistered) {
aKeyHandle.Assign(cred.id());
aAppId.Assign(app_id);
return true;
}
}
}
return false;
}
// A U2F Sign operation creates a signature over the "param" arguments (plus
// some other stuff) using the private key indicated in the key handle argument.
//
@ -713,6 +734,7 @@ RefPtr<U2FSignPromise>
U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
const nsTArray<WebAuthnExtension>& aExtensions,
bool aRequireUserVerification,
uint32_t aTimeoutMS)
{
@ -728,18 +750,21 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
}
nsTArray<uint8_t> keyHandle;
for (auto cred: aCredentials) {
bool isRegistered = false;
nsresult rv = IsRegistered(cred.id(), aApplication, isRegistered);
if (NS_SUCCEEDED(rv) && isRegistered) {
keyHandle.Assign(cred.id());
break;
nsTArray<nsTArray<uint8_t>> appIds;
appIds.AppendElement(aApplication);
// Process extensions.
for (const WebAuthnExtension& ext: aExtensions) {
if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
appIds.AppendElement(ext.get_WebAuthnExtensionAppId().AppId());
}
}
// Fail if we didn't recognize a key id.
if (keyHandle.IsEmpty()) {
nsTArray<uint8_t> chosenAppId(aApplication);
nsTArray<uint8_t> keyHandle;
// Fail if we can't find a valid key handle.
if (!FindRegisteredKeyHandle(appIds, aCredentials, keyHandle, chosenAppId)) {
return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
}
@ -748,10 +773,10 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
MOZ_ASSERT(slot.get());
if (NS_WARN_IF((aChallenge.Length() != kParamLen) || (aApplication.Length() != kParamLen))) {
if (NS_WARN_IF((aChallenge.Length() != kParamLen) || (chosenAppId.Length() != kParamLen))) {
MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
(uint32_t)aChallenge.Length(), (uint32_t)aApplication.Length(), kParamLen));
(uint32_t)aChallenge.Length(), (uint32_t)chosenAppId.Length(), kParamLen));
return U2FSignPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__);
}
@ -760,8 +785,8 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
const_cast<uint8_t*>(keyHandle.Elements()),
keyHandle.Length(),
const_cast<uint8_t*>(aApplication.Elements()),
aApplication.Length());
const_cast<uint8_t*>(chosenAppId.Elements()),
chosenAppId.Length());
if (NS_WARN_IF(!privKey.get())) {
MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
return U2FSignPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
@ -790,7 +815,7 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
// It's OK to ignore the return values here because we're writing into
// pre-allocated space
signedDataBuf.AppendElements(aApplication.Elements(), aApplication.Length(),
signedDataBuf.AppendElements(chosenAppId.Elements(), chosenAppId.Length(),
mozilla::fallible);
signedDataBuf.AppendElement(0x01, mozilla::fallible);
signedDataBuf.AppendSECItem(counterItem);
@ -832,7 +857,15 @@ U2FSoftTokenManager::Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials
signatureBuf.AppendSECItem(counterItem);
signatureBuf.AppendSECItem(signatureItem);
WebAuthnGetAssertionResult result(keyHandle, nsTArray<uint8_t>(signatureBuf));
nsTArray<uint8_t> signature(signatureBuf);
nsTArray<WebAuthnExtensionResult> extensions;
if (chosenAppId != aApplication) {
// Indicate to the RP that we used the FIDO appId.
extensions.AppendElement(WebAuthnExtensionResultAppId(true));
}
WebAuthnGetAssertionResult result(chosenAppId, keyHandle, signature, extensions);
return U2FSignPromise::CreateAndResolve(Move(result), __func__);
}

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

@ -34,6 +34,7 @@ public:
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
const nsTArray<WebAuthnExtension>& aExtensions,
bool aRequireUserVerification,
uint32_t aTimeoutMS) override;
@ -47,6 +48,12 @@ private:
const nsTArray<uint8_t>& aAppParam,
bool& aResult);
bool
FindRegisteredKeyHandle(const nsTArray<nsTArray<uint8_t>>& aAppIds,
const nsTArray<WebAuthnScopedCredential>& aCredentials,
/*out*/ nsTArray<uint8_t>& aKeyHandle,
/*out*/ nsTArray<uint8_t>& aAppId);
bool mInitialized;
mozilla::UniquePK11SymKey mWrappingKey;

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

@ -322,6 +322,7 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
mTokenManagerImpl->Sign(aTransactionInfo.AllowList(),
aTransactionInfo.RpIdHash(),
aTransactionInfo.ClientDataHash(),
aTransactionInfo.Extensions(),
aTransactionInfo.RequireUserVerification(),
aTransactionInfo.TimeoutMS())
->Then(GetCurrentThreadSerialEventTarget(), __func__,

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

@ -38,6 +38,7 @@ public:
Sign(const nsTArray<WebAuthnScopedCredential>& aCredentials,
const nsTArray<uint8_t>& aApplication,
const nsTArray<uint8_t>& aChallenge,
const nsTArray<WebAuthnExtension>& aExtensions,
bool aRequireUserVerification,
uint32_t aTimeoutMS) = 0;

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

@ -49,8 +49,11 @@ NS_IMPL_ISUPPORTS(WebAuthnManager, nsIDOMEventListener);
**********************************************************************/
static nsresult
AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
const nsAString& aType, /* out */ nsACString& aJsonOut)
AssembleClientData(const nsAString& aOrigin,
const CryptoBuffer& aChallenge,
const nsAString& aType,
const AuthenticationExtensionsClientInputs& aExtensions,
/* out */ nsACString& aJsonOut)
{
MOZ_ASSERT(NS_IsMainThread());
@ -65,6 +68,7 @@ AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
clientDataObject.mChallenge.Assign(challengeBase64);
clientDataObject.mOrigin.Assign(aOrigin);
clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
clientDataObject.mClientExtensions = aExtensions;
nsAutoString temp;
if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
@ -264,6 +268,12 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
}
}
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
if (aOptions.mExtensions.mAppid.WasPassed()) {
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget();
}
CryptoBuffer rpIdHash;
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
@ -345,7 +355,8 @@ WebAuthnManager::MakeCredential(const PublicKeyCredentialCreationOptions& aOptio
nsAutoCString clientDataJSON;
srv = AssembleClientData(origin, challenge,
NS_LITERAL_STRING("webauthn.create"), clientDataJSON);
NS_LITERAL_STRING("webauthn.create"),
aOptions.mExtensions, clientDataJSON);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
@ -537,7 +548,7 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
nsAutoCString clientDataJSON;
srv = AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"),
clientDataJSON);
aOptions.mExtensions, clientDataJSON);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
@ -593,14 +604,40 @@ WebAuthnManager::GetAssertion(const PublicKeyCredentialRequestOptions& aOptions,
bool requireUserVerification =
aOptions.mUserVerification == UserVerificationRequirement::Required;
// TODO: Add extension list building
// If extensions was specified, process any extensions supported by this
// If extensions were specified, process any extensions supported by this
// client platform, to produce the extension data that needs to be sent to the
// authenticator. If an error is encountered while processing an extension,
// skip that extension and do not produce any extension data for it. Call the
// result of this processing clientExtensions.
nsTArray<WebAuthnExtension> extensions;
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
if (aOptions.mExtensions.mAppid.WasPassed()) {
nsString appId(aOptions.mExtensions.mAppid.Value());
// Check that the appId value is allowed.
if (!EvaluateAppID(mParent, origin, U2FOperation::Sign, appId)) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
CryptoBuffer appIdHash;
if (!appIdHash.SetLength(SHA256_LENGTH, fallible)) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
}
// We need the SHA-256 hash of the appId.
nsresult srv = HashCString(hashService, NS_ConvertUTF16toUTF8(appId), appIdHash);
if (NS_WARN_IF(NS_FAILED(srv))) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
// Append the hash and send it to the backend.
extensions.AppendElement(WebAuthnExtensionAppId(appIdHash));
}
WebAuthnGetAssertionInfo info(rpIdHash,
clientDataHash,
adjustedTimeout,
@ -807,7 +844,7 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
}
CryptoBuffer rpIdHashBuf;
if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) {
if (!rpIdHashBuf.Assign(aResult.RpIdHash())) {
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
return;
}
@ -862,6 +899,14 @@ WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
credential->SetRawId(credentialBuf);
credential->SetResponse(assertion);
// Forward client extension results.
for (auto& ext: aResult.Extensions()) {
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId) {
bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
credential->SetClientExtensionResultAppId(appid);
}
}
mTransaction.ref().mPromise->MaybeResolve(credential);
ClearTransaction();
}

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

@ -5,11 +5,113 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/WebAuthnUtil.h"
#include "nsIEffectiveTLDService.h"
#include "nsNetUtil.h"
#include "pkixutil.h"
namespace mozilla {
namespace dom {
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId1,
"https://www.gstatic.com/securitykey/origins.json");
NS_NAMED_LITERAL_STRING(kGoogleAccountsAppId2,
"https://www.gstatic.com/securitykey/a/google.com/origins.json");
bool
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
const U2FOperation& aOp, /* in/out */ nsString& aAppId)
{
// Facet is the specification's way of referring to the web origin.
nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
nsCOMPtr<nsIURI> facetUri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
return false;
}
// If the facetId (origin) is not HTTPS, reject
bool facetIsHttps = false;
if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
return false;
}
// If the appId is empty or null, overwrite it with the facetId and accept
if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
aAppId.Assign(aOrigin);
return true;
}
// AppID is user-supplied. It's quite possible for this parse to fail.
nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
nsCOMPtr<nsIURI> appIdUri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
return false;
}
// if the appId URL is not HTTPS, reject.
bool appIdIsHttps = false;
if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
return false;
}
nsAutoCString appIdHost;
if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
return false;
}
// Allow localhost.
if (appIdHost.EqualsLiteral("localhost")) {
nsAutoCString facetHost;
if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
return false;
}
if (facetHost.EqualsLiteral("localhost")) {
return true;
}
}
// Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
// Web Authentication. See Bug 1244959 comment #8 for context on why we are
// doing this instead of implementing the external-fetch FacetID logic.
nsCOMPtr<nsIDocument> document = aParent->GetDoc();
if (!document || !document->IsHTMLDocument()) {
return false;
}
nsHTMLDocument* html = document->AsHTMLDocument();
if (NS_WARN_IF(!html)) {
return false;
}
// Use the base domain as the facet for evaluation. This lets this algorithm
// relax the whole eTLD+1.
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (!tldService) {
return false;
}
nsAutoCString lowestFacetHost;
if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
return false;
}
if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
appIdHost)) {
return true;
}
// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
if (aOp == U2FOperation::Sign && lowestFacetHost.EqualsLiteral("google.com") &&
(aAppId.Equals(kGoogleAccountsAppId1) ||
aAppId.Equals(kGoogleAccountsAppId2))) {
return true;
}
return false;
}
nsresult
ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
uint32_t aLen)

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

@ -16,6 +16,17 @@
namespace mozilla {
namespace dom {
enum class U2FOperation
{
Register,
Sign
};
bool
EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
const U2FOperation& aOp, /* in/out */ nsString& aAppId);
nsresult
AssembleAuthenticatorData(const CryptoBuffer& rpIdHashBuf,
const uint8_t flags,

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

@ -1,5 +1,6 @@
[DEFAULT]
support-files =
head.js
tab_webauthn_result.html
tab_webauthn_success.html
../cbor/*
@ -8,4 +9,5 @@ support-files =
skip-if = !e10s
[browser_abort_visibility.js]
[browser_fido_appid_extension.js]
[browser_webauthn_telemetry.js]

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

@ -0,0 +1,153 @@
/* 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/. */
"use strict";
const TEST_URL = "https://example.com/";
function arrivingHereIsBad(aResult) {
ok(false, "Bad result! Received a: " + aResult);
}
function expectError(aType) {
let expected = `${aType}Error`;
return function (aResult) {
is(aResult.slice(0, expected.length), expected, `Expecting a ${aType}Error`);
};
}
let expectNotSupportedError = expectError("NotSupported");
let expectNotAllowedError = expectError("NotAllowed");
let expectSecurityError = expectError("Security");
function promiseU2FRegister(tab, app_id) {
let challenge = crypto.getRandomValues(new Uint8Array(16));
challenge = bytesToBase64UrlSafe(challenge);
return ContentTask.spawn(tab.linkedBrowser, [app_id, challenge], function ([app_id, challenge]) {
return new Promise(resolve => {
content.u2f.register(app_id, [{version: "U2F_V2", challenge}], [], resolve);
});
}).then(res => {
is(res.errorCode, 0, "u2f.register() succeeded");
let data = base64ToBytesUrlSafe(res.registrationData);
is(data[0], 0x05, "Reserved byte is correct");
return data.slice(67, 67 + data[66]);
});
}
function promiseWebAuthnRegister(tab, appid) {
return ContentTask.spawn(tab.linkedBrowser, [appid], ([appid]) => {
const cose_alg_ECDSA_w_SHA256 = -7;
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
let pubKeyCredParams = [{
type: "public-key",
alg: cose_alg_ECDSA_w_SHA256
}];
let publicKey = {
rp: {id: content.document.domain, name: "none", icon: "none"},
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
pubKeyCredParams,
extensions: {appid},
challenge
};
return content.navigator.credentials.create({publicKey})
.then(res => res.rawId);
});
}
function promiseWebAuthnSign(tab, key_handle, extensions = {}) {
return ContentTask.spawn(tab.linkedBrowser, [key_handle, extensions],
([key_handle, extensions]) => {
let challenge = content.crypto.getRandomValues(new Uint8Array(16));
let credential = {
id: key_handle,
type: "public-key",
transports: ["usb"]
};
let publicKey = {
challenge,
extensions,
rpId: content.document.domain,
allowCredentials: [credential],
};
return content.navigator.credentials.get({publicKey})
.then(credential => {
return {
authenticatorData: credential.response.authenticatorData,
clientDataJSON: credential.response.clientDataJSON,
extensions: credential.getClientExtensionResults()
};
})
});
}
add_task(function test_setup() {
Services.prefs.setBoolPref("security.webauth.u2f", true);
Services.prefs.setBoolPref("security.webauth.webauthn", true);
Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", true);
Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", false);
});
add_task(async function test_appid() {
// Open a new tab.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
// Get a keyHandle for a FIDO AppId.
let appid = "https://example.com/appId";
let keyHandle = await promiseU2FRegister(tab, appid);
// The FIDO AppId extension can't be used for MakeCredential.
await promiseWebAuthnRegister(tab, appid)
.then(arrivingHereIsBad)
.catch(expectNotSupportedError);
// Using the keyHandle shouldn't work without the FIDO AppId extension.
await promiseWebAuthnSign(tab, keyHandle)
.then(arrivingHereIsBad)
.catch(expectNotAllowedError);
// Invalid app IDs (for the current origin) must be rejected.
await promiseWebAuthnSign(tab, keyHandle, {appid: "https://bogus.com/appId"})
.then(arrivingHereIsBad)
.catch(expectSecurityError);
// Non-matching app IDs must be rejected.
await promiseWebAuthnSign(tab, keyHandle, {appid: appid + "2"})
.then(arrivingHereIsBad)
.catch(expectNotAllowedError);
let rpId = new TextEncoder("utf-8").encode(appid);
let rpIdHash = await crypto.subtle.digest("SHA-256", rpId);
// Succeed with the right fallback rpId.
await promiseWebAuthnSign(tab, keyHandle, {appid})
.then(({authenticatorData, clientDataJSON, extensions}) => {
is(extensions.appid, true, "appid extension was acted upon");
// Check that the correct rpIdHash is returned.
let rpIdHashSign = authenticatorData.slice(0, 32);
ok(memcmp(rpIdHash, rpIdHashSign), "rpIdHash is correct");
let clientData = JSON.parse(buffer2string(clientDataJSON));
is(clientData.clientExtensions.appid, appid, "appid extension sent");
});
// Close tab.
await BrowserTestUtils.removeTab(tab);
});
add_task(function test_cleanup() {
Services.prefs.clearUserPref("security.webauth.u2f");
Services.prefs.clearUserPref("security.webauth.webauthn");
Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
});

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

@ -0,0 +1,65 @@
/* 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/. */
"use strict";
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 bytesToBase64UrlSafe(buf) {
return bytesToBase64(buf)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
function base64ToBytes(b64encoded) {
return new Uint8Array(window.atob(b64encoded).split("").map(function(c) {
return c.charCodeAt(0);
}));
}
function base64ToBytesUrlSafe(str) {
if (!str || 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 buffer2string(buf) {
let str = "";
if (!(buf.constructor === Uint8Array)) {
buf = new Uint8Array(buf);
}
buf.map(function(x){ return str += String.fromCharCode(x) });
return str;
}
function memcmp(x, y) {
let xb = new Uint8Array(x);
let yb = new Uint8Array(y);
if (x.byteLength != y.byteLength) {
return false;
}
for (let i = 0; i < xb.byteLength; ++i) {
if (xb[i] != yb[i]) {
return false;
}
}
return true;
}

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

@ -68,6 +68,10 @@ function() {
is(clientData.hashAlgorithm, "SHA-256", "Hash algorithm is correct");
is(clientData.type, "webauthn.create", "Type is correct");
let extensions = aCredInfo.getClientExtensionResults();
is(extensions.appid, undefined, "appid extension wasn't used");
is(clientData.clientExtensions.appid, undefined, "appid extension wasn't sent");
return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject)
.then(function(aAttestationObj) {
// Make sure the RP ID hash matches what we calculate.

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

@ -82,7 +82,7 @@ fn main() {
flags,
15_000,
chall_bytes,
app_bytes,
vec![app_bytes],
vec![key_handle],
move |rv| { tx.send(rv.unwrap()).unwrap(); },
)

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

@ -9,6 +9,7 @@ use std::{ptr, slice};
use U2FManager;
type U2FAppIds = Vec<::AppId>;
type U2FKeyHandles = Vec<::KeyHandle>;
type U2FResult = HashMap<u8, Vec<u8>>;
type U2FCallback = extern "C" fn(u64, *mut U2FResult);
@ -16,6 +17,7 @@ type U2FCallback = extern "C" fn(u64, *mut U2FResult);
const RESBUF_ID_REGISTRATION: u8 = 0;
const RESBUF_ID_KEYHANDLE: u8 = 1;
const RESBUF_ID_SIGNATURE: u8 = 2;
const RESBUF_ID_APPID: u8 = 3;
// Generates a new 64-bit transaction id with collision probability 2^-32.
fn new_tid() -> u64 {
@ -42,6 +44,27 @@ pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut U2FManager) {
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_new() -> *mut U2FAppIds {
Box::into_raw(Box::new(vec![]))
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_add(
ids: *mut U2FAppIds,
id_ptr: *const u8,
id_len: usize
) {
(*ids).push(from_raw(id_ptr, id_len));
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
if !ids.is_null() {
Box::from_raw(ids);
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
Box::into_raw(Box::new(vec![]))
@ -165,8 +188,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
callback: U2FCallback,
challenge_ptr: *const u8,
challenge_len: usize,
application_ptr: *const u8,
application_len: usize,
app_ids: *const U2FAppIds,
khs: *const U2FKeyHandles,
) -> u64 {
if mgr.is_null() || khs.is_null() {
@ -174,13 +196,18 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
}
// Check buffers.
if challenge_ptr.is_null() || application_ptr.is_null() {
if challenge_ptr.is_null() {
return 0;
}
// Need at least one app_id.
if (*app_ids).len() < 1 {
return 0;
}
let flags = ::SignFlags::from_bits_truncate(flags);
let challenge = from_raw(challenge_ptr, challenge_len);
let application = from_raw(application_ptr, application_len);
let app_ids = (*app_ids).clone();
let key_handles = (*khs).clone();
let tid = new_tid();
@ -188,13 +215,14 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign(
flags,
timeout,
challenge,
application,
app_ids,
key_handles,
move |rv| {
if let Ok((key_handle, signature)) = rv {
if let Ok((app_id, key_handle, signature)) = rv {
let mut result = U2FResult::new();
result.insert(RESBUF_ID_KEYHANDLE, key_handle);
result.insert(RESBUF_ID_SIGNATURE, signature);
result.insert(RESBUF_ID_APPID, app_id);
callback(tid, Box::into_raw(Box::new(result)));
} else {
callback(tid, ptr::null_mut());

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

@ -75,6 +75,10 @@ pub struct KeyHandle {
pub transports: AuthenticatorTransports,
}
pub type AppId = Vec<u8>;
pub type RegisterResult = Vec<u8>;
pub type SignResult = (AppId, Vec<u8>, Vec<u8>);
#[cfg(fuzzing)]
pub use u2fprotocol::*;
#[cfg(fuzzing)]

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

@ -16,17 +16,17 @@ enum QueueAction {
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<Vec<u8>>,
callback: OnceCallback<::RegisterResult>,
},
Sign {
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
callback: OnceCallback<::SignResult>,
},
Cancel,
}
@ -68,7 +68,7 @@ impl U2FManager {
flags,
timeout,
challenge,
application,
app_ids,
key_handles,
callback,
}) => {
@ -77,7 +77,7 @@ impl U2FManager {
flags,
timeout,
challenge,
application,
app_ids,
key_handles,
callback,
);
@ -109,12 +109,12 @@ impl U2FManager {
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> io::Result<()>
where
F: FnOnce(io::Result<Vec<u8>>),
F: FnOnce(io::Result<::RegisterResult>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
@ -150,21 +150,37 @@ impl U2FManager {
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: F,
) -> io::Result<()>
where
F: FnOnce(io::Result<(Vec<u8>, Vec<u8>)>),
F: FnOnce(io::Result<::SignResult>),
F: Send + 'static,
{
if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
if challenge.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid parameter sizes",
));
}
if app_ids.len() < 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"No app IDs given",
));
}
for app_id in &app_ids {
if app_id.len() != PARAMETER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid app_id size",
));
}
}
for key_handle in &key_handles {
if key_handle.credential.len() > 256 {
return Err(io::Error::new(
@ -179,7 +195,7 @@ impl U2FManager {
flags,
timeout,
challenge,
application,
app_ids,
key_handles,
callback,
};

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

@ -14,6 +14,31 @@ fn is_valid_transport(transports: ::AuthenticatorTransports) -> bool {
transports.is_empty() || transports.contains(::AuthenticatorTransports::USB)
}
fn find_valid_key_handles<'a, F>(
app_ids: &'a Vec<::AppId>,
key_handles: &'a Vec<::KeyHandle>,
mut is_valid: F,
) -> (&'a ::AppId, Vec<&'a ::KeyHandle>)
where
F: FnMut(&Vec<u8>, &::KeyHandle) -> bool,
{
// Try all given app_ids in order.
for app_id in app_ids {
// Find all valid key handles for the current app_id.
let valid_handles = key_handles
.iter()
.filter(|key_handle| is_valid(app_id, key_handle))
.collect::<Vec<_>>();
// If there's at least one, stop.
if valid_handles.len() > 0 {
return (app_id, valid_handles);
}
}
return (&app_ids[0], vec![]);
}
#[derive(Default)]
pub struct StateMachine {
transaction: Option<Transaction>,
@ -29,9 +54,9 @@ impl StateMachine {
flags: ::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
application: ::AppId,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<Vec<u8>>,
callback: OnceCallback<::RegisterResult>,
) {
// Abort any prior register/sign calls.
self.cancel();
@ -93,9 +118,9 @@ impl StateMachine {
flags: ::SignFlags,
timeout: u64,
challenge: Vec<u8>,
application: Vec<u8>,
app_ids: Vec<::AppId>,
key_handles: Vec<::KeyHandle>,
callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
callback: OnceCallback<::SignResult>,
) {
// Abort any prior register/sign calls.
self.cancel();
@ -125,14 +150,15 @@ impl StateMachine {
return;
}
// Find all matching key handles.
let key_handles = key_handles
.iter()
.filter(|key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
.unwrap_or(false) /* no match on failure */
})
.collect::<Vec<_>>();
// For each appId, try all key handles. If there's at least one
// valid key handle for an appId, we'll use that appId below.
let (app_id, valid_handles) =
find_valid_key_handles(&app_ids, &key_handles,
|app_id, key_handle| {
u2f_is_keyhandle_valid(dev, &challenge, app_id,
&key_handle.credential)
.unwrap_or(false) /* no match on failure */
});
// Aggregate distinct transports from all given credentials.
let transports = key_handles.iter().fold(
@ -149,7 +175,7 @@ impl StateMachine {
while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
if key_handles.is_empty() {
if valid_handles.is_empty() {
let blank = vec![0u8; PARAMETER_SIZE];
if let Ok(_) = u2f_register(dev, &blank, &blank) {
callback.call(Err(io_err("invalid key")));
@ -157,15 +183,17 @@ impl StateMachine {
}
} else {
// Otherwise, try to sign.
for key_handle in &key_handles {
for key_handle in &valid_handles {
if let Ok(bytes) = u2f_sign(
dev,
&challenge,
&application,
app_id,
&key_handle.credential,
)
{
callback.call(Ok((key_handle.credential.clone(), bytes)));
callback.call(Ok((app_id.clone(),
key_handle.credential.clone(),
bytes)));
break;
}
}

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

@ -14,6 +14,7 @@ extern "C" {
const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
const uint8_t U2F_RESBUF_ID_APPID = 3;
const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
@ -34,6 +35,9 @@ const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
struct rust_u2f_manager;
// The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds`
struct rust_u2f_app_ids;
// The `rust_u2f_key_handles` opaque type is equivalent to the rust type `U2FKeyHandles`
struct rust_u2f_key_handles;
@ -65,13 +69,21 @@ uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr,
rust_u2f_callback,
const uint8_t* challenge_ptr,
size_t challenge_len,
const uint8_t* application_ptr,
size_t application_len,
const rust_u2f_app_ids* app_ids,
const rust_u2f_key_handles* khs);
uint64_t rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
/// U2FAppIds functions.
rust_u2f_app_ids* rust_u2f_app_ids_new();
void rust_u2f_app_ids_add(rust_u2f_app_ids* ids,
const uint8_t* id,
size_t id_len);
/* unsafe */ void rust_u2f_app_ids_free(rust_u2f_app_ids* ids);
/// U2FKeyHandles functions.
rust_u2f_key_handles* rust_u2f_khs_new();

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

@ -13,8 +13,7 @@
interface PublicKeyCredential : Credential {
[SameObject] readonly attribute ArrayBuffer rawId;
[SameObject] readonly attribute AuthenticatorResponse response;
// Extensions are not supported yet.
// [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults; // Add in Bug 1406458
AuthenticationExtensionsClientOutputs getClientExtensionResults();
};
[SecureContext]
@ -104,10 +103,18 @@ dictionary PublicKeyCredentialRequestOptions {
AuthenticationExtensionsClientInputs extensions;
};
// TODO - Use partial dictionaries when bug 1436329 is fixed.
dictionary AuthenticationExtensionsClientInputs {
// FIDO AppID Extension (appid)
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
USVString appid;
};
// TODO - Use partial dictionaries when bug 1436329 is fixed.
dictionary AuthenticationExtensionsClientOutputs {
// FIDO AppID Extension (appid)
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
boolean appid;
};
typedef record<DOMString, DOMString> AuthenticationExtensionsAuthenticatorInputs;
@ -144,3 +151,16 @@ typedef sequence<AAGUID> AuthenticatorSelectionList;
typedef BufferSource AAGUID;
/*
// FIDO AppID Extension (appid)
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
partial dictionary AuthenticationExtensionsClientInputs {
USVString appid;
};
// FIDO AppID Extension (appid)
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
partial dictionary AuthenticationExtensionsClientOutputs {
boolean appid;
};
*/