зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1823782 - implement webauthn serialization methods r=jschanck,webidl,saschanaz,tschuster
Differential Revision: https://phabricator.services.mozilla.com/D187239
This commit is contained in:
Родитель
23be0b7524
Коммит
a5cfd59207
|
@ -4,6 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
|
@ -115,4 +116,55 @@ void AuthenticatorAssertionResponse::SetUserHandle(
|
|||
mUserHandle.Assign(aBuffer);
|
||||
}
|
||||
|
||||
void AuthenticatorAssertionResponse::ToJSON(
|
||||
AuthenticatorAssertionResponseJSON& aJSON, ErrorResult& aError) {
|
||||
nsAutoCString clientDataJSONBase64;
|
||||
nsresult rv = Base64URLEncode(
|
||||
mClientDataJSON.Length(),
|
||||
reinterpret_cast<const uint8_t*>(mClientDataJSON.get()),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit, clientDataJSONBase64);
|
||||
// This will only fail if the length is so long that it overflows 32-bits
|
||||
// when calculating the encoded size.
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("clientDataJSON too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mClientDataJSON.Assign(NS_ConvertUTF8toUTF16(clientDataJSONBase64));
|
||||
|
||||
nsAutoCString authenticatorDataBase64;
|
||||
rv = Base64URLEncode(
|
||||
mAuthenticatorData.Length(), mAuthenticatorData.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit, authenticatorDataBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("authenticatorData too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mAuthenticatorData.Assign(
|
||||
NS_ConvertUTF8toUTF16(authenticatorDataBase64));
|
||||
|
||||
nsAutoCString signatureBase64;
|
||||
rv = Base64URLEncode(mSignature.Length(), mSignature.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit,
|
||||
signatureBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("signature too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mSignature.Assign(NS_ConvertUTF8toUTF16(signatureBase64));
|
||||
|
||||
if (!mUserHandle.IsEmpty()) {
|
||||
nsAutoCString userHandleBase64;
|
||||
rv = Base64URLEncode(mUserHandle.Length(), mUserHandle.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit,
|
||||
userHandleBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("userHandle too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mUserHandle.Construct(NS_ConvertUTF8toUTF16(userHandleBase64));
|
||||
}
|
||||
|
||||
// attestationObject is not currently supported on assertion responses
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
|
@ -46,6 +47,8 @@ class AuthenticatorAssertionResponse final : public AuthenticatorResponse {
|
|||
|
||||
void SetUserHandle(const nsTArray<uint8_t>& aBuffer);
|
||||
|
||||
void ToJSON(AuthenticatorAssertionResponseJSON& aJSON, ErrorResult& aError);
|
||||
|
||||
private:
|
||||
nsTArray<uint8_t> mAuthenticatorData;
|
||||
JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AuthrsBridge_ffi.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
|
@ -79,21 +79,28 @@ void AuthenticatorAttestationResponse::SetTransports(
|
|||
mTransports.Assign(aTransports);
|
||||
}
|
||||
|
||||
void AuthenticatorAttestationResponse::GetAuthenticatorData(
|
||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
||||
nsresult AuthenticatorAttestationResponse::GetAuthenticatorDataBytes(
|
||||
nsTArray<uint8_t>& aAuthenticatorData) {
|
||||
if (!mAttestationObjectParsed) {
|
||||
nsresult rv = authrs_webauthn_att_obj_constructor(
|
||||
mAttestationObject, /* anonymize */ false,
|
||||
getter_AddRefs(mAttestationObjectParsed));
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> authenticatorData;
|
||||
nsresult rv =
|
||||
mAttestationObjectParsed->GetAuthenticatorData(authenticatorData);
|
||||
mAttestationObjectParsed->GetAuthenticatorData(aAuthenticatorData);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AuthenticatorAttestationResponse::GetAuthenticatorData(
|
||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
||||
nsTArray<uint8_t> authenticatorData;
|
||||
nsresult rv = GetAuthenticatorDataBytes(authenticatorData);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
|
@ -108,19 +115,27 @@ void AuthenticatorAttestationResponse::GetAuthenticatorData(
|
|||
aValue.set(buffer);
|
||||
}
|
||||
|
||||
void AuthenticatorAttestationResponse::GetPublicKey(
|
||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
||||
nsresult AuthenticatorAttestationResponse::GetPublicKeyBytes(
|
||||
nsTArray<uint8_t>& aPublicKeyBytes) {
|
||||
if (!mAttestationObjectParsed) {
|
||||
nsresult rv = authrs_webauthn_att_obj_constructor(
|
||||
mAttestationObject, false, getter_AddRefs(mAttestationObjectParsed));
|
||||
mAttestationObject, /* anonymize */ false,
|
||||
getter_AddRefs(mAttestationObjectParsed));
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
nsresult rv = mAttestationObjectParsed->GetPublicKey(aPublicKeyBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AuthenticatorAttestationResponse::GetPublicKey(
|
||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
||||
nsTArray<uint8_t> publicKey;
|
||||
nsresult rv = mAttestationObjectParsed->GetPublicKey(publicKey);
|
||||
nsresult rv = GetPublicKeyBytes(publicKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
aValue.set(nullptr);
|
||||
|
@ -155,4 +170,77 @@ COSEAlgorithmIdentifier AuthenticatorAttestationResponse::GetPublicKeyAlgorithm(
|
|||
return alg;
|
||||
}
|
||||
|
||||
void AuthenticatorAttestationResponse::ToJSON(
|
||||
AuthenticatorAttestationResponseJSON& aJSON, ErrorResult& aError) {
|
||||
nsAutoCString clientDataJSONBase64;
|
||||
nsresult rv = Base64URLEncode(
|
||||
mClientDataJSON.Length(),
|
||||
reinterpret_cast<const uint8_t*>(mClientDataJSON.get()),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit, clientDataJSONBase64);
|
||||
// This will only fail if the length is so long that it overflows 32-bits
|
||||
// when calculating the encoded size.
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("clientDataJSON too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mClientDataJSON.Assign(NS_ConvertUTF8toUTF16(clientDataJSONBase64));
|
||||
|
||||
nsTArray<uint8_t> authenticatorData;
|
||||
rv = GetAuthenticatorDataBytes(authenticatorData);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowUnknownError("could not get authenticatorData");
|
||||
return;
|
||||
}
|
||||
nsAutoCString authenticatorDataBase64;
|
||||
rv = Base64URLEncode(authenticatorData.Length(), authenticatorData.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit,
|
||||
authenticatorDataBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("authenticatorData too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mAuthenticatorData.Assign(
|
||||
NS_ConvertUTF8toUTF16(authenticatorDataBase64));
|
||||
|
||||
if (!aJSON.mTransports.Assign(mTransports, mozilla::fallible)) {
|
||||
aError.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> publicKeyBytes;
|
||||
rv = GetPublicKeyBytes(publicKeyBytes);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoCString publicKeyBytesBase64;
|
||||
rv = Base64URLEncode(publicKeyBytes.Length(), publicKeyBytes.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit,
|
||||
publicKeyBytesBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("publicKey too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mPublicKey.Construct(NS_ConvertUTF8toUTF16(publicKeyBytesBase64));
|
||||
} else if (rv != NS_ERROR_NOT_AVAILABLE) {
|
||||
aError.ThrowUnknownError("could not get publicKey");
|
||||
return;
|
||||
}
|
||||
|
||||
COSEAlgorithmIdentifier publicKeyAlgorithm = GetPublicKeyAlgorithm(aError);
|
||||
if (aError.Failed()) {
|
||||
aError.ThrowUnknownError("could not get publicKeyAlgorithm");
|
||||
return;
|
||||
}
|
||||
aJSON.mPublicKeyAlgorithm = publicKeyAlgorithm;
|
||||
|
||||
nsAutoCString attestationObjectBase64;
|
||||
rv = Base64URLEncode(
|
||||
mAttestationObject.Length(), mAttestationObject.Elements(),
|
||||
mozilla::Base64URLEncodePaddingPolicy::Omit, attestationObjectBase64);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.ThrowDataError("attestationObject too long");
|
||||
return;
|
||||
}
|
||||
aJSON.mAttestationObject.Assign(
|
||||
NS_ConvertUTF8toUTF16(attestationObjectBase64));
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIWebAuthnController.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
@ -49,7 +50,12 @@ class AuthenticatorAttestationResponse final : public AuthenticatorResponse {
|
|||
|
||||
COSEAlgorithmIdentifier GetPublicKeyAlgorithm(ErrorResult& aRv);
|
||||
|
||||
void ToJSON(AuthenticatorAttestationResponseJSON& aJSON, ErrorResult& aError);
|
||||
|
||||
private:
|
||||
nsresult GetAuthenticatorDataBytes(nsTArray<uint8_t>& aAuthenticatorData);
|
||||
nsresult GetPublicKeyBytes(nsTArray<uint8_t>& aPublicKeyBytes);
|
||||
|
||||
nsTArray<uint8_t> mAttestationObject;
|
||||
nsCOMPtr<nsIWebAuthnAttObj> mAttestationObjectParsed;
|
||||
JS::Heap<JSObject*> mAttestationObjectCachedObj;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
#include "mozilla/dom/TypedArray.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
|
|
@ -38,9 +38,11 @@ class AuthenticatorResponse : public nsISupports, public nsWrapperCache {
|
|||
|
||||
void SetClientDataJSON(const nsCString& aBuffer);
|
||||
|
||||
protected:
|
||||
nsCString mClientDataJSON;
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsCString mClientDataJSON;
|
||||
JS::Heap<JSObject*> mClientDataJSONCachedObj;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
#include "mozilla/dom/ChromeUtils.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PublicKeyCredential.h"
|
||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
# include "WinWebAuthnManager.h"
|
||||
|
@ -36,7 +39,8 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PublicKeyCredential,
|
||||
Credential)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttestationResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAssertionResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
|
||||
|
@ -72,16 +76,27 @@ void PublicKeyCredential::GetRawId(JSContext* aCx,
|
|||
}
|
||||
|
||||
already_AddRefed<AuthenticatorResponse> PublicKeyCredential::Response() const {
|
||||
RefPtr<AuthenticatorResponse> temp(mResponse);
|
||||
return temp.forget();
|
||||
if (mAttestationResponse) {
|
||||
return do_AddRef(mAttestationResponse);
|
||||
}
|
||||
if (mAssertionResponse) {
|
||||
return do_AddRef(mAssertionResponse);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PublicKeyCredential::SetRawId(const nsTArray<uint8_t>& aBuffer) {
|
||||
mRawId.Assign(aBuffer);
|
||||
}
|
||||
|
||||
void PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse) {
|
||||
mResponse = aResponse;
|
||||
void PublicKeyCredential::SetAttestationResponse(
|
||||
const RefPtr<AuthenticatorAttestationResponse>& aAttestationResponse) {
|
||||
mAttestationResponse = aAttestationResponse;
|
||||
}
|
||||
|
||||
void PublicKeyCredential::SetAssertionResponse(
|
||||
const RefPtr<AuthenticatorAssertionResponse>& aAssertionResponse) {
|
||||
mAssertionResponse = aAssertionResponse;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
@ -160,6 +175,54 @@ void PublicKeyCredential::GetClientExtensionResults(
|
|||
aResult = mClientExtensionOutputs;
|
||||
}
|
||||
|
||||
void PublicKeyCredential::ToJSON(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aError) {
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (mAttestationResponse) {
|
||||
RegistrationResponseJSON json;
|
||||
GetId(json.mId);
|
||||
GetId(json.mRawId);
|
||||
mAttestationResponse->ToJSON(json.mResponse, aError);
|
||||
if (aError.Failed()) {
|
||||
return;
|
||||
}
|
||||
// TODO(bug 1810851): authenticatorAttachment
|
||||
if (mClientExtensionOutputs.mHmacCreateSecret.WasPassed()) {
|
||||
json.mClientExtensionResults.mHmacCreateSecret.Construct(
|
||||
mClientExtensionOutputs.mHmacCreateSecret.Value());
|
||||
}
|
||||
json.mType.Assign(u"public-key"_ns);
|
||||
if (!ToJSValue(aCx, json, &value)) {
|
||||
aError.StealExceptionFromJSContext(aCx);
|
||||
return;
|
||||
}
|
||||
} else if (mAssertionResponse) {
|
||||
AuthenticationResponseJSON json;
|
||||
GetId(json.mId);
|
||||
GetId(json.mRawId);
|
||||
mAssertionResponse->ToJSON(json.mResponse, aError);
|
||||
if (aError.Failed()) {
|
||||
return;
|
||||
}
|
||||
// TODO(bug 1810851): authenticatorAttachment
|
||||
if (mClientExtensionOutputs.mAppid.WasPassed()) {
|
||||
json.mClientExtensionResults.mAppid.Construct(
|
||||
mClientExtensionOutputs.mAppid.Value());
|
||||
}
|
||||
json.mType.Assign(u"public-key"_ns);
|
||||
if (!ToJSValue(aCx, json, &value)) {
|
||||
aError.StealExceptionFromJSContext(aCx);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"either mAttestationResponse or mAssertionResponse should be set");
|
||||
}
|
||||
JS::Rooted<JSObject*> result(aCx, &value.toObject());
|
||||
aRetval.set(result);
|
||||
}
|
||||
|
||||
void PublicKeyCredential::SetClientExtensionResultAppId(bool aResult) {
|
||||
mClientExtensionOutputs.mAppid.Construct();
|
||||
mClientExtensionOutputs.mAppid.Value() = aResult;
|
||||
|
@ -171,4 +234,139 @@ void PublicKeyCredential::SetClientExtensionResultHmacSecret(
|
|||
mClientExtensionOutputs.mHmacCreateSecret.Value() = aHmacCreateSecret;
|
||||
}
|
||||
|
||||
bool Base64DecodeToArrayBuffer(GlobalObject& aGlobal, const nsAString& aString,
|
||||
ArrayBuffer& aArrayBuffer, ErrorResult& aRv) {
|
||||
JSContext* cx = aGlobal.Context();
|
||||
JS::Rooted<JSObject*> result(cx);
|
||||
Base64URLDecodeOptions options;
|
||||
options.mPadding = Base64URLDecodePadding::Ignore;
|
||||
mozilla::dom::ChromeUtils::Base64URLDecode(
|
||||
aGlobal, NS_ConvertUTF16toUTF8(aString), options, &result, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
return aArrayBuffer.Init(result);
|
||||
}
|
||||
|
||||
void PublicKeyCredential::ParseCreationOptionsFromJSON(
|
||||
GlobalObject& aGlobal,
|
||||
const PublicKeyCredentialCreationOptionsJSON& aOptions,
|
||||
PublicKeyCredentialCreationOptions& aResult, ErrorResult& aRv) {
|
||||
if (aOptions.mRp.mId.WasPassed()) {
|
||||
aResult.mRp.mId.Construct(aOptions.mRp.mId.Value());
|
||||
}
|
||||
aResult.mRp.mName.Assign(aOptions.mRp.mName);
|
||||
|
||||
aResult.mUser.mName.Assign(aOptions.mUser.mName);
|
||||
if (!Base64DecodeToArrayBuffer(aGlobal, aOptions.mUser.mId,
|
||||
aResult.mUser.mId.SetAsArrayBuffer(), aRv)) {
|
||||
aRv.ThrowEncodingError("could not decode user ID as urlsafe base64");
|
||||
return;
|
||||
}
|
||||
aResult.mUser.mDisplayName.Assign(aOptions.mUser.mDisplayName);
|
||||
|
||||
if (!Base64DecodeToArrayBuffer(aGlobal, aOptions.mChallenge,
|
||||
aResult.mChallenge.SetAsArrayBuffer(), aRv)) {
|
||||
aRv.ThrowEncodingError("could not decode challenge as urlsafe base64");
|
||||
return;
|
||||
}
|
||||
|
||||
aResult.mPubKeyCredParams = aOptions.mPubKeyCredParams;
|
||||
|
||||
if (aOptions.mTimeout.WasPassed()) {
|
||||
aResult.mTimeout.Construct(aOptions.mTimeout.Value());
|
||||
}
|
||||
|
||||
for (const auto& excludeCredentialJSON : aOptions.mExcludeCredentials) {
|
||||
PublicKeyCredentialDescriptor* excludeCredential =
|
||||
aResult.mExcludeCredentials.AppendElement(fallible);
|
||||
if (!excludeCredential) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
excludeCredential->mType = excludeCredentialJSON.mType;
|
||||
if (!Base64DecodeToArrayBuffer(aGlobal, excludeCredentialJSON.mId,
|
||||
excludeCredential->mId.SetAsArrayBuffer(),
|
||||
aRv)) {
|
||||
aRv.ThrowEncodingError(
|
||||
"could not decode excluded credential ID as urlsafe base64");
|
||||
return;
|
||||
}
|
||||
if (excludeCredentialJSON.mTransports.WasPassed()) {
|
||||
excludeCredential->mTransports.Construct(
|
||||
excludeCredentialJSON.mTransports.Value());
|
||||
}
|
||||
}
|
||||
|
||||
if (aOptions.mAuthenticatorSelection.WasPassed()) {
|
||||
aResult.mAuthenticatorSelection = aOptions.mAuthenticatorSelection.Value();
|
||||
}
|
||||
|
||||
aResult.mAttestation = aOptions.mAttestation;
|
||||
|
||||
if (aOptions.mExtensions.WasPassed()) {
|
||||
if (aOptions.mExtensions.Value().mAppid.WasPassed()) {
|
||||
aResult.mExtensions.mAppid.Construct(
|
||||
aOptions.mExtensions.Value().mAppid.Value());
|
||||
}
|
||||
if (aOptions.mExtensions.Value().mHmacCreateSecret.WasPassed()) {
|
||||
aResult.mExtensions.mHmacCreateSecret.Construct(
|
||||
aOptions.mExtensions.Value().mHmacCreateSecret.Value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PublicKeyCredential::ParseRequestOptionsFromJSON(
|
||||
GlobalObject& aGlobal,
|
||||
const PublicKeyCredentialRequestOptionsJSON& aOptions,
|
||||
PublicKeyCredentialRequestOptions& aResult, ErrorResult& aRv) {
|
||||
if (!Base64DecodeToArrayBuffer(aGlobal, aOptions.mChallenge,
|
||||
aResult.mChallenge.SetAsArrayBuffer(), aRv)) {
|
||||
aRv.ThrowEncodingError("could not decode challenge as urlsafe base64");
|
||||
return;
|
||||
}
|
||||
|
||||
if (aOptions.mTimeout.WasPassed()) {
|
||||
aResult.mTimeout.Construct(aOptions.mTimeout.Value());
|
||||
}
|
||||
|
||||
if (aOptions.mRpId.WasPassed()) {
|
||||
aResult.mRpId.Construct(aOptions.mRpId.Value());
|
||||
}
|
||||
|
||||
for (const auto& allowCredentialJSON : aOptions.mAllowCredentials) {
|
||||
PublicKeyCredentialDescriptor* allowCredential =
|
||||
aResult.mAllowCredentials.AppendElement(fallible);
|
||||
if (!allowCredential) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
allowCredential->mType = allowCredentialJSON.mType;
|
||||
if (!Base64DecodeToArrayBuffer(aGlobal, allowCredentialJSON.mId,
|
||||
allowCredential->mId.SetAsArrayBuffer(),
|
||||
aRv)) {
|
||||
aRv.ThrowEncodingError(
|
||||
"could not decode allowed credential ID as urlsafe base64");
|
||||
return;
|
||||
}
|
||||
if (allowCredentialJSON.mTransports.WasPassed()) {
|
||||
allowCredential->mTransports.Construct(
|
||||
allowCredentialJSON.mTransports.Value());
|
||||
}
|
||||
}
|
||||
|
||||
aResult.mUserVerification = aOptions.mUserVerification;
|
||||
|
||||
if (aOptions.mExtensions.WasPassed()) {
|
||||
if (aOptions.mExtensions.Value().mAppid.WasPassed()) {
|
||||
aResult.mExtensions.mAppid.Construct(
|
||||
aOptions.mExtensions.Value().mAppid.Value());
|
||||
}
|
||||
if (aOptions.mExtensions.Value().mHmacCreateSecret.WasPassed()) {
|
||||
aResult.mExtensions.mHmacCreateSecret.Construct(
|
||||
aOptions.mExtensions.Value().mHmacCreateSecret.Value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
||||
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/Credential.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
@ -38,7 +40,10 @@ class PublicKeyCredential final : public Credential {
|
|||
|
||||
void SetRawId(const nsTArray<uint8_t>& aBuffer);
|
||||
|
||||
void SetResponse(RefPtr<AuthenticatorResponse>);
|
||||
void SetAttestationResponse(
|
||||
const RefPtr<AuthenticatorAttestationResponse>& aAttestationResponse);
|
||||
void SetAssertionResponse(
|
||||
const RefPtr<AuthenticatorAssertionResponse>& aAssertionResponse);
|
||||
|
||||
static already_AddRefed<Promise>
|
||||
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal,
|
||||
|
@ -50,14 +55,28 @@ class PublicKeyCredential final : public Credential {
|
|||
void GetClientExtensionResults(
|
||||
AuthenticationExtensionsClientOutputs& aResult);
|
||||
|
||||
void ToJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aError);
|
||||
|
||||
void SetClientExtensionResultAppId(bool aResult);
|
||||
|
||||
void SetClientExtensionResultHmacSecret(bool aHmacCreateSecret);
|
||||
|
||||
static void ParseCreationOptionsFromJSON(
|
||||
GlobalObject& aGlobal,
|
||||
const PublicKeyCredentialCreationOptionsJSON& aOptions,
|
||||
PublicKeyCredentialCreationOptions& aResult, ErrorResult& aRv);
|
||||
|
||||
static void ParseRequestOptionsFromJSON(
|
||||
GlobalObject& aGlobal,
|
||||
const PublicKeyCredentialRequestOptionsJSON& aOptions,
|
||||
PublicKeyCredentialRequestOptions& aResult, ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
nsTArray<uint8_t> mRawId;
|
||||
JS::Heap<JSObject*> mRawIdCachedObj;
|
||||
RefPtr<AuthenticatorResponse> mResponse;
|
||||
RefPtr<AuthenticatorAttestationResponse> mAttestationResponse;
|
||||
RefPtr<AuthenticatorAssertionResponse> mAssertionResponse;
|
||||
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
|
||||
};
|
||||
|
||||
|
|
|
@ -717,7 +717,7 @@ void WebAuthnManager::FinishMakeCredential(
|
|||
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
||||
credential->SetType(u"public-key"_ns);
|
||||
credential->SetRawId(aResult.KeyHandle());
|
||||
credential->SetResponse(attestation);
|
||||
credential->SetAttestationResponse(attestation);
|
||||
|
||||
// Forward client extension results.
|
||||
for (const auto& ext : aResult.Extensions()) {
|
||||
|
@ -765,7 +765,7 @@ void WebAuthnManager::FinishGetAssertion(
|
|||
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
||||
credential->SetType(u"public-key"_ns);
|
||||
credential->SetRawId(aResult.KeyHandle());
|
||||
credential->SetResponse(assertion);
|
||||
credential->SetAssertionResponse(assertion);
|
||||
|
||||
// Forward client extension results.
|
||||
for (const auto& ext : aResult.Extensions()) {
|
||||
|
|
|
@ -88,6 +88,8 @@ skip-if =
|
|||
win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.)
|
||||
[test_webauthn_serialization.html]
|
||||
fail-if = xorigin
|
||||
[test_webauthn_store_credential.html]
|
||||
fail-if = xorigin # NotAllowedError
|
||||
skip-if =
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>Tests W3C Web Authentication Data Types Serialization</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Tests W3C Web Authentication Data Types Serialization</h1>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1823782">Mozilla Bug 1823782</a>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
const { Assert } = SpecialPowers.ChromeUtils.importESModule(
|
||||
"resource://testing-common/Assert.sys.mjs"
|
||||
);
|
||||
|
||||
function arrayBufferEqualsArray(actual, expected, description) {
|
||||
ok(actual instanceof ArrayBuffer, `${description} (actual should be array)`);
|
||||
ok(expected instanceof Array, `${description} (expected should be array)`);
|
||||
is(actual.byteLength, expected.length, `${description} (actual and expected should have same length)`);
|
||||
let actualView = new Uint8Array(actual);
|
||||
for (let i = 0; i < actualView.length; i++) {
|
||||
if (actualView[i] != expected[i]) {
|
||||
throw new Error(`actual and expected differ in byte ${i}: ${actualView[i]} vs ${expected[i]}`);
|
||||
}
|
||||
}
|
||||
ok(true, description);
|
||||
}
|
||||
|
||||
function isEmptyArray(arr, description) {
|
||||
ok(arr instanceof Array, `${description} (expecting Array)`);
|
||||
is(arr.length, 0, `${description} (array should be empty)`);
|
||||
}
|
||||
|
||||
function shouldThrow(func, expectedError, description) {
|
||||
let threw = false;
|
||||
try {
|
||||
func();
|
||||
} catch (e) {
|
||||
is(e.message, expectedError);
|
||||
threw = true;
|
||||
}
|
||||
ok(threw, description);
|
||||
}
|
||||
|
||||
add_task(function test_parseCreationOptionsFromJSON_minimal() {
|
||||
let creationOptionsJSON = {
|
||||
rp: { name: "Example" },
|
||||
user: { id: "-l3qNLTKJng", name: "username", displayName: "display name" },
|
||||
challenge: "XNJTTB3kfqk",
|
||||
pubKeyCredParams: [],
|
||||
};
|
||||
let creationOptions = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
|
||||
is(Object.getOwnPropertyNames(creationOptions).length, 8, "creation options should have 8 properties");
|
||||
is(creationOptions.rp.id, undefined, "rp.id should be undefined");
|
||||
is(creationOptions.rp.name, "Example", "rp.name should be Example");
|
||||
arrayBufferEqualsArray(creationOptions.user.id, [ 250, 93, 234, 52, 180, 202, 38, 120 ], "user.id should be as expected");
|
||||
is(creationOptions.user.displayName, "display name", "user.displayName should be 'display name'");
|
||||
is(creationOptions.user.name, "username", "user.name should be username");
|
||||
arrayBufferEqualsArray(creationOptions.challenge, [ 92, 210, 83, 76, 29, 228, 126, 169 ], "challenge should be as expected");
|
||||
isEmptyArray(creationOptions.pubKeyCredParams, "pubKeyCredParams should be an empty array");
|
||||
is(creationOptions.timeout, undefined, "timeout should be undefined");
|
||||
isEmptyArray(creationOptions.excludeCredentials, "excludeCredentials should be an empty array");
|
||||
is(creationOptions.authenticatorSelection.authenticatorAttachment, undefined, "authenticatorSelection.authenticatorAttachment should be undefined");
|
||||
is(creationOptions.authenticatorSelection.residentKey, undefined, "creationOptions.authenticatorSelection.residentKey should be undefined");
|
||||
is(creationOptions.authenticatorSelection.requireResidentKey, false, "creationOptions.authenticatorSelection.requireResidentKey should be false");
|
||||
is(creationOptions.authenticatorSelection.userVerification, "preferred", "creationOptions.authenticatorSelection.userVerification should be preferred");
|
||||
is(creationOptions.attestation, "none", "attestation should be none");
|
||||
is(Object.getOwnPropertyNames(creationOptions.extensions).length, 0, "extensions should be an empty object");
|
||||
});
|
||||
|
||||
add_task(function test_parseCreationOptionsFromJSON() {
|
||||
let creationOptionsJSON = {
|
||||
rp: { name: "Example", id: "example.com" },
|
||||
user: { id: "19TVpqBBOAM", name: "username2", displayName: "another display name" },
|
||||
challenge: "dR82FeUh5q4",
|
||||
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
||||
timeout: 20000,
|
||||
excludeCredentials: [{ type: "public-key", id: "TeM2k_di7Dk", transports: [ "usb" ]}],
|
||||
authenticatorSelection: { authenticatorAttachment: "platform", residentKey: "required", requireResidentKey: true, userVerification: "discouraged" },
|
||||
hints: ["hybrid"],
|
||||
attestation: "indirect",
|
||||
attestationFormats: ["fido-u2f"],
|
||||
extensions: { appid: "https://www.example.com/appID", hmacCreateSecret: true },
|
||||
};
|
||||
let creationOptions = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
|
||||
is(Object.getOwnPropertyNames(creationOptions).length, 9, "creation options should have 9 properties");
|
||||
is(creationOptions.rp.name, "Example", "rp.name should be Example");
|
||||
is(creationOptions.rp.id, "example.com", "rp.id should be example.com");
|
||||
arrayBufferEqualsArray(creationOptions.user.id, [ 215, 212, 213, 166, 160, 65, 56, 3 ], "user.id should be as expected");
|
||||
is(creationOptions.user.displayName, "another display name", "user.displayName should be 'another display name'");
|
||||
is(creationOptions.user.name, "username2", "user.name should be username2");
|
||||
arrayBufferEqualsArray(creationOptions.challenge, [ 117, 31, 54, 21, 229, 33, 230, 174 ], "challenge should be as expected");
|
||||
is(creationOptions.pubKeyCredParams.length, 1, "pubKeyCredParams should have one element");
|
||||
is(creationOptions.pubKeyCredParams[0].type, "public-key", "pubKeyCredParams[0].type should be public-key");
|
||||
is(creationOptions.pubKeyCredParams[0].alg, -7, "pubKeyCredParams[0].alg should be -7");
|
||||
is(creationOptions.timeout, 20000, "timeout should be 20000");
|
||||
is(creationOptions.excludeCredentials.length, 1, "excludeCredentials should have one element");
|
||||
is(creationOptions.excludeCredentials[0].type, "public-key", "excludeCredentials[0].type should be public-key");
|
||||
arrayBufferEqualsArray(creationOptions.excludeCredentials[0].id, [ 77, 227, 54, 147, 247, 98, 236, 57 ], "excludeCredentials[0].id should be as expected");
|
||||
is(creationOptions.excludeCredentials[0].transports.length, 1, "excludeCredentials[0].transports should have one element");
|
||||
is(creationOptions.excludeCredentials[0].transports[0], "usb", "excludeCredentials[0].transports[0] should be usb");
|
||||
is(creationOptions.authenticatorSelection.authenticatorAttachment, "platform", "authenticatorSelection.authenticatorAttachment should be platform");
|
||||
is(creationOptions.authenticatorSelection.residentKey, "required", "creationOptions.authenticatorSelection.residentKey should be required");
|
||||
is(creationOptions.authenticatorSelection.requireResidentKey, true, "creationOptions.authenticatorSelection.requireResidentKey should be true");
|
||||
is(creationOptions.authenticatorSelection.userVerification, "discouraged", "creationOptions.authenticatorSelection.userVerification should be discouraged");
|
||||
is(creationOptions.attestation, "indirect", "attestation should be indirect");
|
||||
is(creationOptions.extensions.appid, "https://www.example.com/appID", "extensions.appid should be https://www.example.com/appID");
|
||||
is(creationOptions.extensions.hmacCreateSecret, true, "extensions.hmacCreateSecret should be true");
|
||||
});
|
||||
|
||||
add_task(function test_parseCreationOptionsFromJSON_malformed() {
|
||||
let userIdNotBase64 = {
|
||||
rp: { name: "Example" },
|
||||
user: { id: "/not urlsafe base64+", name: "username", displayName: "display name" },
|
||||
challenge: "XNJTTB3kfqk",
|
||||
pubKeyCredParams: [],
|
||||
};
|
||||
shouldThrow(
|
||||
() => { PublicKeyCredential.parseCreationOptionsFromJSON(userIdNotBase64); },
|
||||
"PublicKeyCredential.parseCreationOptionsFromJSON: could not decode user ID as urlsafe base64",
|
||||
"should get encoding error if user.id is not urlsafe base64"
|
||||
);
|
||||
|
||||
let challengeNotBase64 = {
|
||||
rp: { name: "Example" },
|
||||
user: { id: "-l3qNLTKJng", name: "username", displayName: "display name" },
|
||||
challenge: "this is not urlsafe base64!",
|
||||
pubKeyCredParams: [],
|
||||
};
|
||||
shouldThrow(
|
||||
() => { PublicKeyCredential.parseCreationOptionsFromJSON(challengeNotBase64); },
|
||||
"PublicKeyCredential.parseCreationOptionsFromJSON: could not decode challenge as urlsafe base64",
|
||||
"should get encoding error if challenge is not urlsafe base64"
|
||||
);
|
||||
|
||||
let excludeCredentialsIdNotBase64 = {
|
||||
rp: { name: "Example", id: "example.com" },
|
||||
user: { id: "-l3qNLTKJng", name: "username", displayName: "display name" },
|
||||
challenge: "dR82FeUh5q4",
|
||||
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
||||
timeout: 20000,
|
||||
excludeCredentials: [{ type: "public-key", id: "@#$%&^", transports: [ "usb" ]}],
|
||||
authenticatorselection: { authenticatorattachment: "platform", residentkey: "required", requireresidentkey: true, userverification: "discouraged" },
|
||||
hints: ["hybrid"],
|
||||
attestation: "indirect",
|
||||
attestationformats: ["fido-u2f"],
|
||||
extensions: { appid: "https://www.example.com/appid", hmaccreatesecret: true },
|
||||
};
|
||||
shouldThrow(
|
||||
() => { PublicKeyCredential.parseCreationOptionsFromJSON(excludeCredentialsIdNotBase64); },
|
||||
"PublicKeyCredential.parseCreationOptionsFromJSON: could not decode excluded credential ID as urlsafe base64",
|
||||
"should get encoding error if excludeCredentials[0].id is not urlsafe base64"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_parseRequestOptionsFromJSON_minimal() {
|
||||
let requestOptionsJSON = {
|
||||
challenge: "3yW2WHD_jbU",
|
||||
};
|
||||
let requestOptions = PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJSON);
|
||||
is(Object.getOwnPropertyNames(requestOptions).length, 4, "request options should have 4 properties");
|
||||
arrayBufferEqualsArray(requestOptions.challenge, [ 223, 37, 182, 88, 112, 255, 141, 181 ], "challenge should be as expected");
|
||||
is(requestOptions.timeout, undefined, "timeout should be undefined");
|
||||
is(requestOptions.rpId, undefined, "rpId should be undefined");
|
||||
isEmptyArray(requestOptions.allowCredentials, "allowCredentials should be an empty array");
|
||||
is(requestOptions.userVerification, "preferred", "userVerification should be preferred");
|
||||
is(Object.getOwnPropertyNames(requestOptions.extensions).length, 0, "extensions should be an empty object");
|
||||
});
|
||||
|
||||
add_task(function test_parseRequestOptionsFromJSON() {
|
||||
let requestOptionsJSON = {
|
||||
challenge: "QAfaZwEQCkQ",
|
||||
timeout: 25000,
|
||||
rpId: "example.com",
|
||||
allowCredentials: [{type: "public-key", id: "BTBXXGuXRTk", transports: ["smart-card"] }],
|
||||
userVerification: "discouraged",
|
||||
hints: ["client-device"],
|
||||
attestation: "enterprise",
|
||||
attestationFormats: ["packed"],
|
||||
extensions: { appid: "https://www.example.com/anotherAppID", hmacCreateSecret: false },
|
||||
};
|
||||
let requestOptions = PublicKeyCredential.parseRequestOptionsFromJSON(requestOptionsJSON);
|
||||
is(Object.getOwnPropertyNames(requestOptions).length, 6, "request options should have 6 properties");
|
||||
arrayBufferEqualsArray(requestOptions.challenge, [ 64, 7, 218, 103, 1, 16, 10, 68 ], "challenge should be as expected");
|
||||
is(requestOptions.timeout, 25000, "timeout should be 25000");
|
||||
is(requestOptions.rpId, "example.com", "rpId should be example.com");
|
||||
is(requestOptions.allowCredentials.length, 1, "allowCredentials should have one element");
|
||||
is(requestOptions.allowCredentials[0].type, "public-key", "allowCredentials[0].type should be public-key");
|
||||
arrayBufferEqualsArray(requestOptions.allowCredentials[0].id, [ 5, 48, 87, 92, 107, 151, 69, 57 ], "allowCredentials[0].id should be as expected");
|
||||
is(requestOptions.allowCredentials[0].transports.length, 1, "allowCredentials[0].transports should have one element");
|
||||
is(requestOptions.allowCredentials[0].transports[0], "smart-card", "allowCredentials[0].transports[0] should be usb");
|
||||
is(requestOptions.userVerification, "discouraged", "userVerification should be discouraged");
|
||||
is(requestOptions.extensions.appid, "https://www.example.com/anotherAppID", "extensions.appid should be https://www.example.com/anotherAppID");
|
||||
is(requestOptions.extensions.hmacCreateSecret, false, "extensions.hmacCreateSecret should be false");
|
||||
});
|
||||
|
||||
add_task(function test_parseRequestOptionsFromJSON_malformed() {
|
||||
let challengeNotBase64 = {
|
||||
challenge: "/not+urlsafe+base64/",
|
||||
};
|
||||
shouldThrow(
|
||||
() => { PublicKeyCredential.parseRequestOptionsFromJSON(challengeNotBase64); },
|
||||
"PublicKeyCredential.parseRequestOptionsFromJSON: could not decode challenge as urlsafe base64",
|
||||
"should get encoding error if challenge is not urlsafe base64"
|
||||
);
|
||||
|
||||
let allowCredentialsIdNotBase64 = {
|
||||
challenge: "QAfaZwEQCkQ",
|
||||
timeout: 25000,
|
||||
rpId: "example.com",
|
||||
allowCredentials: [{type: "public-key", id: "not urlsafe base64", transports: ["smart-card"] }],
|
||||
userVerification: "discouraged",
|
||||
hints: ["client-device"],
|
||||
attestation: "enterprise",
|
||||
attestationFormats: ["packed"],
|
||||
extensions: { appid: "https://www.example.com/anotherAppID", hmacCreateSecret: false },
|
||||
};
|
||||
shouldThrow(
|
||||
() => { PublicKeyCredential.parseRequestOptionsFromJSON(allowCredentialsIdNotBase64); },
|
||||
"PublicKeyCredential.parseRequestOptionsFromJSON: could not decode allowed credential ID as urlsafe base64",
|
||||
"should get encoding error if allowCredentials[0].id is not urlsafe base64"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async () => {
|
||||
await addVirtualAuthenticator();
|
||||
});
|
||||
|
||||
function isUrlsafeBase64(urlsafeBase64) {
|
||||
try {
|
||||
atob(urlsafeBase64.replace(/_/g, "/").replace(/-/g, "+"));
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
add_task(async function test_registrationResponse_toJSON() {
|
||||
let publicKey = {
|
||||
rp: {id: document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
};
|
||||
let registrationResponse = await navigator.credentials.create({publicKey});
|
||||
let registrationResponseJSON = registrationResponse.toJSON();
|
||||
is(Object.keys(registrationResponseJSON).length, 5, "registrationResponseJSON should have 5 properties");
|
||||
is(registrationResponseJSON.id, registrationResponseJSON.rawId, "registrationResponseJSON.id and rawId should be the same");
|
||||
ok(isUrlsafeBase64(registrationResponseJSON.id), "registrationResponseJSON.id should be urlsafe base64");
|
||||
is(Object.keys(registrationResponseJSON.response).length, 6, "registrationResponseJSON.response should have 6 properties");
|
||||
ok(isUrlsafeBase64(registrationResponseJSON.response.clientDataJSON), "registrationResponseJSON.response.clientDataJSON should be urlsafe base64");
|
||||
ok(isUrlsafeBase64(registrationResponseJSON.response.authenticatorData), "registrationResponseJSON.response.authenticatorData should be urlsafe base64");
|
||||
ok(isUrlsafeBase64(registrationResponseJSON.response.publicKey), "registrationResponseJSON.response.publicKey should be urlsafe base64");
|
||||
ok(isUrlsafeBase64(registrationResponseJSON.response.attestationObject), "registrationResponseJSON.response.attestationObject should be urlsafe base64");
|
||||
is(registrationResponseJSON.response.publicKeyAlgorithm, cose_alg_ECDSA_w_SHA256, "registrationResponseJSON.response.publicKeyAlgorithm should be ECDSA with SHA256 (COSE)");
|
||||
is(registrationResponseJSON.response.transports.length, 1, "registrationResponseJSON.response.transports.length should be 1");
|
||||
is(registrationResponseJSON.response.transports[0], "usb", "registrationResponseJSON.response.transports[0] should be usb");
|
||||
is(registrationResponseJSON.type, "public-key", "registrationResponseJSON.type should be public-key");
|
||||
});
|
||||
|
||||
add_task(async function test_assertionResponse_toJSON() {
|
||||
let registrationRequest = {
|
||||
publicKey: {
|
||||
rp: {id: document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
},
|
||||
};
|
||||
let registrationResponse = await navigator.credentials.create(registrationRequest);
|
||||
|
||||
let assertionRequest = {
|
||||
publicKey: {
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
allowCredentials: [{ type: "public-key", id: registrationResponse.rawId }],
|
||||
},
|
||||
};
|
||||
let assertionResponse = await navigator.credentials.get(assertionRequest);
|
||||
let assertionResponseJSON = assertionResponse.toJSON();
|
||||
is(Object.keys(assertionResponseJSON).length, 5, "assertionResponseJSON should have 5 properties");
|
||||
is(assertionResponseJSON.id, assertionResponseJSON.rawId, "assertionResponseJSON.id and rawId should be the same");
|
||||
ok(isUrlsafeBase64(assertionResponseJSON.id), "assertionResponseJSON.id should be urlsafe base64");
|
||||
is(Object.keys(assertionResponseJSON.response).length, 3, "assertionResponseJSON.response should have 3 properties");
|
||||
ok(isUrlsafeBase64(assertionResponseJSON.response.clientDataJSON), "assertionResponseJSON.response.clientDataJSON should be urlsafe base64");
|
||||
ok(isUrlsafeBase64(assertionResponseJSON.response.authenticatorData), "assertionResponseJSON.response.authenticatorData should be urlsafe base64");
|
||||
ok(isUrlsafeBase64(assertionResponseJSON.response.signature), "assertionResponseJSON.response.signature should be urlsafe base64");
|
||||
is(Object.keys(assertionResponseJSON.clientExtensionResults).length, 0, "assertionResponseJSON.clientExtensionResults should be an empty dictionary");
|
||||
is(assertionResponseJSON.type, "public-key", "assertionResponseJSON.type should be public-key");
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -15,6 +15,66 @@ interface PublicKeyCredential : Credential {
|
|||
[SameObject, Throws] readonly attribute ArrayBuffer rawId;
|
||||
[SameObject] readonly attribute AuthenticatorResponse response;
|
||||
AuthenticationExtensionsClientOutputs getClientExtensionResults();
|
||||
[Throws] object toJSON();
|
||||
};
|
||||
|
||||
typedef DOMString Base64URLString;
|
||||
|
||||
[GenerateConversionToJS]
|
||||
dictionary RegistrationResponseJSON {
|
||||
required Base64URLString id;
|
||||
required Base64URLString rawId;
|
||||
required AuthenticatorAttestationResponseJSON response;
|
||||
DOMString authenticatorAttachment;
|
||||
required AuthenticationExtensionsClientOutputsJSON clientExtensionResults;
|
||||
required DOMString type;
|
||||
};
|
||||
|
||||
[GenerateConversionToJS]
|
||||
dictionary AuthenticatorAttestationResponseJSON {
|
||||
required Base64URLString clientDataJSON;
|
||||
required Base64URLString authenticatorData;
|
||||
required sequence<DOMString> transports;
|
||||
// The publicKey field will be missing if pubKeyCredParams was used to
|
||||
// negotiate a public-key algorithm that the user agent doesn’t
|
||||
// understand. (See section “Easily accessing credential data” for a
|
||||
// list of which algorithms user agents must support.) If using such an
|
||||
// algorithm then the public key must be parsed directly from
|
||||
// attestationObject or authenticatorData.
|
||||
Base64URLString publicKey;
|
||||
required long long publicKeyAlgorithm;
|
||||
// This value contains copies of some of the fields above. See
|
||||
// section “Easily accessing credential data”.
|
||||
required Base64URLString attestationObject;
|
||||
};
|
||||
|
||||
[GenerateConversionToJS]
|
||||
dictionary AuthenticationResponseJSON {
|
||||
required Base64URLString id;
|
||||
required Base64URLString rawId;
|
||||
required AuthenticatorAssertionResponseJSON response;
|
||||
DOMString authenticatorAttachment;
|
||||
required AuthenticationExtensionsClientOutputsJSON clientExtensionResults;
|
||||
required DOMString type;
|
||||
};
|
||||
|
||||
[GenerateConversionToJS]
|
||||
dictionary AuthenticatorAssertionResponseJSON {
|
||||
required Base64URLString clientDataJSON;
|
||||
required Base64URLString authenticatorData;
|
||||
required Base64URLString signature;
|
||||
Base64URLString userHandle;
|
||||
Base64URLString attestationObject;
|
||||
};
|
||||
|
||||
[GenerateConversionToJS]
|
||||
dictionary AuthenticationExtensionsClientOutputsJSON {
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
boolean appid;
|
||||
|
||||
// <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension>
|
||||
boolean hmacCreateSecret;
|
||||
};
|
||||
|
||||
[SecureContext]
|
||||
|
@ -24,6 +84,64 @@ partial interface PublicKeyCredential {
|
|||
[NewObject] static Promise<boolean> isExternalCTAP2SecurityKeySupported();
|
||||
};
|
||||
|
||||
[SecureContext]
|
||||
partial interface PublicKeyCredential {
|
||||
[Throws] static PublicKeyCredentialCreationOptions parseCreationOptionsFromJSON(PublicKeyCredentialCreationOptionsJSON options);
|
||||
};
|
||||
|
||||
dictionary PublicKeyCredentialCreationOptionsJSON {
|
||||
required PublicKeyCredentialRpEntity rp;
|
||||
required PublicKeyCredentialUserEntityJSON user;
|
||||
required Base64URLString challenge;
|
||||
required sequence<PublicKeyCredentialParameters> pubKeyCredParams;
|
||||
unsigned long timeout;
|
||||
sequence<PublicKeyCredentialDescriptorJSON> excludeCredentials = [];
|
||||
AuthenticatorSelectionCriteria authenticatorSelection;
|
||||
sequence<DOMString> hints = [];
|
||||
DOMString attestation = "none";
|
||||
sequence<DOMString> attestationFormats = [];
|
||||
AuthenticationExtensionsClientInputsJSON extensions;
|
||||
};
|
||||
|
||||
dictionary PublicKeyCredentialUserEntityJSON {
|
||||
required Base64URLString id;
|
||||
required DOMString name;
|
||||
required DOMString displayName;
|
||||
};
|
||||
|
||||
dictionary PublicKeyCredentialDescriptorJSON {
|
||||
required Base64URLString id;
|
||||
required DOMString type;
|
||||
sequence<DOMString> transports;
|
||||
};
|
||||
|
||||
dictionary AuthenticationExtensionsClientInputsJSON {
|
||||
// FIDO AppID Extension (appid)
|
||||
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
|
||||
USVString appid;
|
||||
|
||||
// hmac-secret
|
||||
// <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension>
|
||||
boolean hmacCreateSecret;
|
||||
};
|
||||
|
||||
[SecureContext]
|
||||
partial interface PublicKeyCredential {
|
||||
[Throws] static PublicKeyCredentialRequestOptions parseRequestOptionsFromJSON(PublicKeyCredentialRequestOptionsJSON options);
|
||||
};
|
||||
|
||||
dictionary PublicKeyCredentialRequestOptionsJSON {
|
||||
required Base64URLString challenge;
|
||||
unsigned long timeout;
|
||||
DOMString rpId;
|
||||
sequence<PublicKeyCredentialDescriptorJSON> allowCredentials = [];
|
||||
DOMString userVerification = "preferred";
|
||||
sequence<DOMString> hints = [];
|
||||
DOMString attestation = "none";
|
||||
sequence<DOMString> attestationFormats = [];
|
||||
AuthenticationExtensionsClientInputsJSON extensions;
|
||||
};
|
||||
|
||||
[SecureContext, Pref="security.webauth.webauthn",
|
||||
Exposed=Window]
|
||||
interface AuthenticatorResponse {
|
||||
|
|
Загрузка…
Ссылка в новой задаче