From f6d984403e37262d7cd1d664f6bda0d0f8f783cd Mon Sep 17 00:00:00 2001 From: Dana Keeler Date: Tue, 19 Sep 2023 20:03:37 +0000 Subject: [PATCH] Bug 1823782 - implement webauthn serialization methods r=jschanck,webidl,saschanaz,tschuster Differential Revision: https://phabricator.services.mozilla.com/D187239 --- .../AuthenticatorAssertionResponse.cpp | 52 +++ dom/webauthn/AuthenticatorAssertionResponse.h | 3 + .../AuthenticatorAttestationResponse.cpp | 120 ++++++- .../AuthenticatorAttestationResponse.h | 6 + dom/webauthn/AuthenticatorResponse.cpp | 1 + dom/webauthn/AuthenticatorResponse.h | 4 +- dom/webauthn/PublicKeyCredential.cpp | 214 ++++++++++++- dom/webauthn/PublicKeyCredential.h | 23 +- dom/webauthn/WebAuthnManager.cpp | 4 +- dom/webauthn/tests/mochitest.ini | 2 + .../tests/test_webauthn_serialization.html | 297 ++++++++++++++++++ dom/webidl/WebAuthentication.webidl | 118 +++++++ 12 files changed, 815 insertions(+), 29 deletions(-) create mode 100644 dom/webauthn/tests/test_webauthn_serialization.html diff --git a/dom/webauthn/AuthenticatorAssertionResponse.cpp b/dom/webauthn/AuthenticatorAssertionResponse.cpp index 8569b324214e..ff0c445e4061 100644 --- a/dom/webauthn/AuthenticatorAssertionResponse.cpp +++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp @@ -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(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 diff --git a/dom/webauthn/AuthenticatorAssertionResponse.h b/dom/webauthn/AuthenticatorAssertionResponse.h index 05d828b9ad46..75982800d135 100644 --- a/dom/webauthn/AuthenticatorAssertionResponse.h +++ b/dom/webauthn/AuthenticatorAssertionResponse.h @@ -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& aBuffer); + void ToJSON(AuthenticatorAssertionResponseJSON& aJSON, ErrorResult& aError); + private: nsTArray mAuthenticatorData; JS::Heap mAuthenticatorDataCachedObj; diff --git a/dom/webauthn/AuthenticatorAttestationResponse.cpp b/dom/webauthn/AuthenticatorAttestationResponse.cpp index c4094e763139..43f928f6566e 100644 --- a/dom/webauthn/AuthenticatorAttestationResponse.cpp +++ b/dom/webauthn/AuthenticatorAttestationResponse.cpp @@ -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 aValue, ErrorResult& aRv) { +nsresult AuthenticatorAttestationResponse::GetAuthenticatorDataBytes( + nsTArray& 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 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 aValue, ErrorResult& aRv) { + nsTArray 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 aValue, ErrorResult& aRv) { +nsresult AuthenticatorAttestationResponse::GetPublicKeyBytes( + nsTArray& 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 aValue, ErrorResult& aRv) { nsTArray 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(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 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 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 diff --git a/dom/webauthn/AuthenticatorAttestationResponse.h b/dom/webauthn/AuthenticatorAttestationResponse.h index 356da50825d5..da559ffd0e10 100644 --- a/dom/webauthn/AuthenticatorAttestationResponse.h +++ b/dom/webauthn/AuthenticatorAttestationResponse.h @@ -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& aAuthenticatorData); + nsresult GetPublicKeyBytes(nsTArray& aPublicKeyBytes); + nsTArray mAttestationObject; nsCOMPtr mAttestationObjectParsed; JS::Heap mAttestationObjectCachedObj; diff --git a/dom/webauthn/AuthenticatorResponse.cpp b/dom/webauthn/AuthenticatorResponse.cpp index 579a3a0ca2eb..ba291ae4cdf4 100644 --- a/dom/webauthn/AuthenticatorResponse.cpp +++ b/dom/webauthn/AuthenticatorResponse.cpp @@ -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" diff --git a/dom/webauthn/AuthenticatorResponse.h b/dom/webauthn/AuthenticatorResponse.h index 35d7f010d864..b0abb3185ca4 100644 --- a/dom/webauthn/AuthenticatorResponse.h +++ b/dom/webauthn/AuthenticatorResponse.h @@ -38,9 +38,11 @@ class AuthenticatorResponse : public nsISupports, public nsWrapperCache { void SetClientDataJSON(const nsCString& aBuffer); + protected: + nsCString mClientDataJSON; + private: nsCOMPtr mParent; - nsCString mClientDataJSON; JS::Heap mClientDataJSONCachedObj; }; diff --git a/dom/webauthn/PublicKeyCredential.cpp b/dom/webauthn/PublicKeyCredential.cpp index bfeafff0e193..f35f326bfb82 100644 --- a/dom/webauthn/PublicKeyCredential.cpp +++ b/dom/webauthn/PublicKeyCredential.cpp @@ -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 PublicKeyCredential::Response() const { - RefPtr temp(mResponse); - return temp.forget(); + if (mAttestationResponse) { + return do_AddRef(mAttestationResponse); + } + if (mAssertionResponse) { + return do_AddRef(mAssertionResponse); + } + return nullptr; } void PublicKeyCredential::SetRawId(const nsTArray& aBuffer) { mRawId.Assign(aBuffer); } -void PublicKeyCredential::SetResponse(RefPtr aResponse) { - mResponse = aResponse; +void PublicKeyCredential::SetAttestationResponse( + const RefPtr& aAttestationResponse) { + mAttestationResponse = aAttestationResponse; +} + +void PublicKeyCredential::SetAssertionResponse( + const RefPtr& aAssertionResponse) { + mAssertionResponse = aAssertionResponse; } /* static */ @@ -160,6 +175,54 @@ void PublicKeyCredential::GetClientExtensionResults( aResult = mClientExtensionOutputs; } +void PublicKeyCredential::ToJSON(JSContext* aCx, + JS::MutableHandle aRetval, + ErrorResult& aError) { + JS::Rooted 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 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 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 diff --git a/dom/webauthn/PublicKeyCredential.h b/dom/webauthn/PublicKeyCredential.h index d44dc4d7b6ab..d6963827c9c8 100644 --- a/dom/webauthn/PublicKeyCredential.h +++ b/dom/webauthn/PublicKeyCredential.h @@ -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& aBuffer); - void SetResponse(RefPtr); + void SetAttestationResponse( + const RefPtr& aAttestationResponse); + void SetAssertionResponse( + const RefPtr& aAssertionResponse); static already_AddRefed IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal, @@ -50,14 +55,28 @@ class PublicKeyCredential final : public Credential { void GetClientExtensionResults( AuthenticationExtensionsClientOutputs& aResult); + void ToJSON(JSContext* aCx, JS::MutableHandle 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 mRawId; JS::Heap mRawIdCachedObj; - RefPtr mResponse; + RefPtr mAttestationResponse; + RefPtr mAssertionResponse; AuthenticationExtensionsClientOutputs mClientExtensionOutputs; }; diff --git a/dom/webauthn/WebAuthnManager.cpp b/dom/webauthn/WebAuthnManager.cpp index 4f0e97eca3fa..38538ff08454 100644 --- a/dom/webauthn/WebAuthnManager.cpp +++ b/dom/webauthn/WebAuthnManager.cpp @@ -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()) { diff --git a/dom/webauthn/tests/mochitest.ini b/dom/webauthn/tests/mochitest.ini index 5321468a5397..d0fb118297c8 100644 --- a/dom/webauthn/tests/mochitest.ini +++ b/dom/webauthn/tests/mochitest.ini @@ -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 = diff --git a/dom/webauthn/tests/test_webauthn_serialization.html b/dom/webauthn/tests/test_webauthn_serialization.html new file mode 100644 index 000000000000..ed8c4cdccd1e --- /dev/null +++ b/dom/webauthn/tests/test_webauthn_serialization.html @@ -0,0 +1,297 @@ + + + + Tests W3C Web Authentication Data Types Serialization + + + + + + +

Tests W3C Web Authentication Data Types Serialization

+ Mozilla Bug 1823782 + + + + + diff --git a/dom/webidl/WebAuthentication.webidl b/dom/webidl/WebAuthentication.webidl index f79c0760997f..9fc499da2e40 100644 --- a/dom/webidl/WebAuthentication.webidl +++ b/dom/webidl/WebAuthentication.webidl @@ -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 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) + // + boolean appid; + + // + boolean hmacCreateSecret; }; [SecureContext] @@ -24,6 +84,64 @@ partial interface PublicKeyCredential { [NewObject] static Promise isExternalCTAP2SecurityKeySupported(); }; +[SecureContext] +partial interface PublicKeyCredential { + [Throws] static PublicKeyCredentialCreationOptions parseCreationOptionsFromJSON(PublicKeyCredentialCreationOptionsJSON options); +}; + +dictionary PublicKeyCredentialCreationOptionsJSON { + required PublicKeyCredentialRpEntity rp; + required PublicKeyCredentialUserEntityJSON user; + required Base64URLString challenge; + required sequence pubKeyCredParams; + unsigned long timeout; + sequence excludeCredentials = []; + AuthenticatorSelectionCriteria authenticatorSelection; + sequence hints = []; + DOMString attestation = "none"; + sequence attestationFormats = []; + AuthenticationExtensionsClientInputsJSON extensions; +}; + +dictionary PublicKeyCredentialUserEntityJSON { + required Base64URLString id; + required DOMString name; + required DOMString displayName; +}; + +dictionary PublicKeyCredentialDescriptorJSON { + required Base64URLString id; + required DOMString type; + sequence transports; +}; + +dictionary AuthenticationExtensionsClientInputsJSON { + // FIDO AppID Extension (appid) + // + USVString appid; + + // hmac-secret + // + boolean hmacCreateSecret; +}; + +[SecureContext] +partial interface PublicKeyCredential { + [Throws] static PublicKeyCredentialRequestOptions parseRequestOptionsFromJSON(PublicKeyCredentialRequestOptionsJSON options); +}; + +dictionary PublicKeyCredentialRequestOptionsJSON { + required Base64URLString challenge; + unsigned long timeout; + DOMString rpId; + sequence allowCredentials = []; + DOMString userVerification = "preferred"; + sequence hints = []; + DOMString attestation = "none"; + sequence attestationFormats = []; + AuthenticationExtensionsClientInputsJSON extensions; +}; + [SecureContext, Pref="security.webauth.webauthn", Exposed=Window] interface AuthenticatorResponse {