Bug 1823782 - implement webauthn serialization methods r=jschanck,webidl,saschanaz,tschuster
Differential Revision: https://phabricator.services.mozilla.com/D187239
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "mozilla/Base64.h"
|
||||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||||
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
||||||
#include "mozilla/HoldDropJSObjects.h"
|
#include "mozilla/HoldDropJSObjects.h"
|
||||||
@@ -115,4 +116,55 @@ void AuthenticatorAssertionResponse::SetUserHandle(
|
|||||||
mUserHandle.Assign(aBuffer);
|
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
|
} // namespace mozilla::dom
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "mozilla/Attributes.h"
|
#include "mozilla/Attributes.h"
|
||||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||||
#include "mozilla/dom/BindingDeclarations.h"
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
|
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "nsWrapperCache.h"
|
#include "nsWrapperCache.h"
|
||||||
|
|
||||||
@@ -46,6 +47,8 @@ class AuthenticatorAssertionResponse final : public AuthenticatorResponse {
|
|||||||
|
|
||||||
void SetUserHandle(const nsTArray<uint8_t>& aBuffer);
|
void SetUserHandle(const nsTArray<uint8_t>& aBuffer);
|
||||||
|
|
||||||
|
void ToJSON(AuthenticatorAssertionResponseJSON& aJSON, ErrorResult& aError);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nsTArray<uint8_t> mAuthenticatorData;
|
nsTArray<uint8_t> mAuthenticatorData;
|
||||||
JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
|
JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "AuthrsBridge_ffi.h"
|
#include "AuthrsBridge_ffi.h"
|
||||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
#include "mozilla/Base64.h"
|
||||||
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
|
||||||
|
|
||||||
#include "mozilla/HoldDropJSObjects.h"
|
#include "mozilla/HoldDropJSObjects.h"
|
||||||
|
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||||
|
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||||
|
|
||||||
namespace mozilla::dom {
|
namespace mozilla::dom {
|
||||||
|
|
||||||
@@ -79,21 +79,28 @@ void AuthenticatorAttestationResponse::SetTransports(
|
|||||||
mTransports.Assign(aTransports);
|
mTransports.Assign(aTransports);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthenticatorAttestationResponse::GetAuthenticatorData(
|
nsresult AuthenticatorAttestationResponse::GetAuthenticatorDataBytes(
|
||||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
nsTArray<uint8_t>& aAuthenticatorData) {
|
||||||
if (!mAttestationObjectParsed) {
|
if (!mAttestationObjectParsed) {
|
||||||
nsresult rv = authrs_webauthn_att_obj_constructor(
|
nsresult rv = authrs_webauthn_att_obj_constructor(
|
||||||
mAttestationObject, /* anonymize */ false,
|
mAttestationObject, /* anonymize */ false,
|
||||||
getter_AddRefs(mAttestationObjectParsed));
|
getter_AddRefs(mAttestationObjectParsed));
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
aRv.Throw(rv);
|
return rv;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsTArray<uint8_t> authenticatorData;
|
|
||||||
nsresult rv =
|
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)) {
|
if (NS_FAILED(rv)) {
|
||||||
aRv.Throw(rv);
|
aRv.Throw(rv);
|
||||||
return;
|
return;
|
||||||
@@ -108,19 +115,27 @@ void AuthenticatorAttestationResponse::GetAuthenticatorData(
|
|||||||
aValue.set(buffer);
|
aValue.set(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthenticatorAttestationResponse::GetPublicKey(
|
nsresult AuthenticatorAttestationResponse::GetPublicKeyBytes(
|
||||||
JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
|
nsTArray<uint8_t>& aPublicKeyBytes) {
|
||||||
if (!mAttestationObjectParsed) {
|
if (!mAttestationObjectParsed) {
|
||||||
nsresult rv = authrs_webauthn_att_obj_constructor(
|
nsresult rv = authrs_webauthn_att_obj_constructor(
|
||||||
mAttestationObject, false, getter_AddRefs(mAttestationObjectParsed));
|
mAttestationObject, /* anonymize */ false,
|
||||||
|
getter_AddRefs(mAttestationObjectParsed));
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
aRv.Throw(rv);
|
return rv;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
nsTArray<uint8_t> publicKey;
|
||||||
nsresult rv = mAttestationObjectParsed->GetPublicKey(publicKey);
|
nsresult rv = GetPublicKeyBytes(publicKey);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||||
aValue.set(nullptr);
|
aValue.set(nullptr);
|
||||||
@@ -155,4 +170,77 @@ COSEAlgorithmIdentifier AuthenticatorAttestationResponse::GetPublicKeyAlgorithm(
|
|||||||
return alg;
|
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
|
} // namespace mozilla::dom
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "mozilla/Attributes.h"
|
#include "mozilla/Attributes.h"
|
||||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||||
#include "mozilla/dom/BindingDeclarations.h"
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
|
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "nsIWebAuthnController.h"
|
#include "nsIWebAuthnController.h"
|
||||||
#include "nsWrapperCache.h"
|
#include "nsWrapperCache.h"
|
||||||
@@ -49,7 +50,12 @@ class AuthenticatorAttestationResponse final : public AuthenticatorResponse {
|
|||||||
|
|
||||||
COSEAlgorithmIdentifier GetPublicKeyAlgorithm(ErrorResult& aRv);
|
COSEAlgorithmIdentifier GetPublicKeyAlgorithm(ErrorResult& aRv);
|
||||||
|
|
||||||
|
void ToJSON(AuthenticatorAttestationResponseJSON& aJSON, ErrorResult& aError);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
nsresult GetAuthenticatorDataBytes(nsTArray<uint8_t>& aAuthenticatorData);
|
||||||
|
nsresult GetPublicKeyBytes(nsTArray<uint8_t>& aPublicKeyBytes);
|
||||||
|
|
||||||
nsTArray<uint8_t> mAttestationObject;
|
nsTArray<uint8_t> mAttestationObject;
|
||||||
nsCOMPtr<nsIWebAuthnAttObj> mAttestationObjectParsed;
|
nsCOMPtr<nsIWebAuthnAttObj> mAttestationObjectParsed;
|
||||||
JS::Heap<JSObject*> mAttestationObjectCachedObj;
|
JS::Heap<JSObject*> mAttestationObjectCachedObj;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "mozilla/Base64.h"
|
||||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||||
#include "mozilla/dom/TypedArray.h"
|
#include "mozilla/dom/TypedArray.h"
|
||||||
#include "nsPIDOMWindow.h"
|
#include "nsPIDOMWindow.h"
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ class AuthenticatorResponse : public nsISupports, public nsWrapperCache {
|
|||||||
|
|
||||||
void SetClientDataJSON(const nsCString& aBuffer);
|
void SetClientDataJSON(const nsCString& aBuffer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
nsCString mClientDataJSON;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||||
nsCString mClientDataJSON;
|
|
||||||
JS::Heap<JSObject*> mClientDataJSONCachedObj;
|
JS::Heap<JSObject*> mClientDataJSONCachedObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,16 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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/Promise.h"
|
||||||
#include "mozilla/dom/PublicKeyCredential.h"
|
#include "mozilla/dom/PublicKeyCredential.h"
|
||||||
#include "mozilla/dom/WebAuthenticationBinding.h"
|
#include "mozilla/dom/WebAuthenticationBinding.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "mozilla/dom/AuthenticatorResponse.h"
|
|
||||||
#include "mozilla/HoldDropJSObjects.h"
|
|
||||||
#include "mozilla/StaticPrefs_security.h"
|
|
||||||
|
|
||||||
#ifdef XP_WIN
|
#ifdef XP_WIN
|
||||||
# include "WinWebAuthnManager.h"
|
# include "WinWebAuthnManager.h"
|
||||||
@@ -36,7 +39,8 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PublicKeyCredential,
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PublicKeyCredential,
|
||||||
Credential)
|
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_CYCLE_COLLECTION_TRAVERSE_END
|
||||||
|
|
||||||
NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
|
NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
|
||||||
@@ -72,16 +76,27 @@ void PublicKeyCredential::GetRawId(JSContext* aCx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<AuthenticatorResponse> PublicKeyCredential::Response() const {
|
already_AddRefed<AuthenticatorResponse> PublicKeyCredential::Response() const {
|
||||||
RefPtr<AuthenticatorResponse> temp(mResponse);
|
if (mAttestationResponse) {
|
||||||
return temp.forget();
|
return do_AddRef(mAttestationResponse);
|
||||||
|
}
|
||||||
|
if (mAssertionResponse) {
|
||||||
|
return do_AddRef(mAssertionResponse);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PublicKeyCredential::SetRawId(const nsTArray<uint8_t>& aBuffer) {
|
void PublicKeyCredential::SetRawId(const nsTArray<uint8_t>& aBuffer) {
|
||||||
mRawId.Assign(aBuffer);
|
mRawId.Assign(aBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse) {
|
void PublicKeyCredential::SetAttestationResponse(
|
||||||
mResponse = aResponse;
|
const RefPtr<AuthenticatorAttestationResponse>& aAttestationResponse) {
|
||||||
|
mAttestationResponse = aAttestationResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PublicKeyCredential::SetAssertionResponse(
|
||||||
|
const RefPtr<AuthenticatorAssertionResponse>& aAssertionResponse) {
|
||||||
|
mAssertionResponse = aAssertionResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
@@ -160,6 +175,54 @@ void PublicKeyCredential::GetClientExtensionResults(
|
|||||||
aResult = mClientExtensionOutputs;
|
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) {
|
void PublicKeyCredential::SetClientExtensionResultAppId(bool aResult) {
|
||||||
mClientExtensionOutputs.mAppid.Construct();
|
mClientExtensionOutputs.mAppid.Construct();
|
||||||
mClientExtensionOutputs.mAppid.Value() = aResult;
|
mClientExtensionOutputs.mAppid.Value() = aResult;
|
||||||
@@ -171,4 +234,139 @@ void PublicKeyCredential::SetClientExtensionResultHmacSecret(
|
|||||||
mClientExtensionOutputs.mHmacCreateSecret.Value() = aHmacCreateSecret;
|
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
|
} // namespace mozilla::dom
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include "js/TypeDecls.h"
|
#include "js/TypeDecls.h"
|
||||||
#include "mozilla/Attributes.h"
|
#include "mozilla/Attributes.h"
|
||||||
|
#include "mozilla/dom/AuthenticatorAssertionResponse.h"
|
||||||
|
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
|
||||||
#include "mozilla/dom/BindingDeclarations.h"
|
#include "mozilla/dom/BindingDeclarations.h"
|
||||||
#include "mozilla/dom/Credential.h"
|
#include "mozilla/dom/Credential.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
@@ -38,7 +40,10 @@ class PublicKeyCredential final : public Credential {
|
|||||||
|
|
||||||
void SetRawId(const nsTArray<uint8_t>& aBuffer);
|
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>
|
static already_AddRefed<Promise>
|
||||||
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal,
|
IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal,
|
||||||
@@ -50,14 +55,28 @@ class PublicKeyCredential final : public Credential {
|
|||||||
void GetClientExtensionResults(
|
void GetClientExtensionResults(
|
||||||
AuthenticationExtensionsClientOutputs& aResult);
|
AuthenticationExtensionsClientOutputs& aResult);
|
||||||
|
|
||||||
|
void ToJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aError);
|
||||||
|
|
||||||
void SetClientExtensionResultAppId(bool aResult);
|
void SetClientExtensionResultAppId(bool aResult);
|
||||||
|
|
||||||
void SetClientExtensionResultHmacSecret(bool aHmacCreateSecret);
|
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:
|
private:
|
||||||
nsTArray<uint8_t> mRawId;
|
nsTArray<uint8_t> mRawId;
|
||||||
JS::Heap<JSObject*> mRawIdCachedObj;
|
JS::Heap<JSObject*> mRawIdCachedObj;
|
||||||
RefPtr<AuthenticatorResponse> mResponse;
|
RefPtr<AuthenticatorAttestationResponse> mAttestationResponse;
|
||||||
|
RefPtr<AuthenticatorAssertionResponse> mAssertionResponse;
|
||||||
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
|
AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -717,7 +717,7 @@ void WebAuthnManager::FinishMakeCredential(
|
|||||||
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
||||||
credential->SetType(u"public-key"_ns);
|
credential->SetType(u"public-key"_ns);
|
||||||
credential->SetRawId(aResult.KeyHandle());
|
credential->SetRawId(aResult.KeyHandle());
|
||||||
credential->SetResponse(attestation);
|
credential->SetAttestationResponse(attestation);
|
||||||
|
|
||||||
// Forward client extension results.
|
// Forward client extension results.
|
||||||
for (const auto& ext : aResult.Extensions()) {
|
for (const auto& ext : aResult.Extensions()) {
|
||||||
@@ -765,7 +765,7 @@ void WebAuthnManager::FinishGetAssertion(
|
|||||||
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
|
||||||
credential->SetType(u"public-key"_ns);
|
credential->SetType(u"public-key"_ns);
|
||||||
credential->SetRawId(aResult.KeyHandle());
|
credential->SetRawId(aResult.KeyHandle());
|
||||||
credential->SetResponse(assertion);
|
credential->SetAssertionResponse(assertion);
|
||||||
|
|
||||||
// Forward client extension results.
|
// Forward client extension results.
|
||||||
for (const auto& ext : aResult.Extensions()) {
|
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_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.)
|
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.)
|
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]
|
[test_webauthn_store_credential.html]
|
||||||
fail-if = xorigin # NotAllowedError
|
fail-if = xorigin # NotAllowedError
|
||||||
skip-if =
|
skip-if =
|
||||||
|
|||||||
297
dom/webauthn/tests/test_webauthn_serialization.html
Normal file
297
dom/webauthn/tests/test_webauthn_serialization.html
Normal file
@@ -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, Throws] readonly attribute ArrayBuffer rawId;
|
||||||
[SameObject] readonly attribute AuthenticatorResponse response;
|
[SameObject] readonly attribute AuthenticatorResponse response;
|
||||||
AuthenticationExtensionsClientOutputs getClientExtensionResults();
|
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]
|
[SecureContext]
|
||||||
@@ -24,6 +84,64 @@ partial interface PublicKeyCredential {
|
|||||||
[NewObject] static Promise<boolean> isExternalCTAP2SecurityKeySupported();
|
[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",
|
[SecureContext, Pref="security.webauth.webauthn",
|
||||||
Exposed=Window]
|
Exposed=Window]
|
||||||
interface AuthenticatorResponse {
|
interface AuthenticatorResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user