File mozilla-bmo2008744.patch of Package MozillaFirefox
Index: firefox-147.0.3/dom/credentialmanagement/CredentialsContainer.cpp
===================================================================
--- firefox-147.0.3.orig/dom/credentialmanagement/CredentialsContainer.cpp
+++ firefox-147.0.3/dom/credentialmanagement/CredentialsContainer.cpp
@@ -197,7 +197,8 @@ already_AddRefed<Promise> CredentialsCon
}
EnsureWebAuthnHandler();
- return mWebAuthnHandler->GetAssertion(aOptions.mPublicKey.Value(),
+ auto *aCx = nsContentUtils::GetCurrentJSContext();
+ return mWebAuthnHandler->GetAssertion(aCx, aOptions.mPublicKey.Value(),
conditionallyMediated,
aOptions.mSignal, aRv);
}
@@ -266,7 +267,8 @@ already_AddRefed<Promise> CredentialsCon
}
EnsureWebAuthnHandler();
- return mWebAuthnHandler->MakeCredential(aOptions.mPublicKey.Value(),
+ auto *aCx = nsContentUtils::GetCurrentJSContext();
+ return mWebAuthnHandler->MakeCredential(aCx, aOptions.mPublicKey.Value(),
aOptions.mSignal, aRv);
}
Index: firefox-147.0.3/dom/webauthn/AndroidWebAuthnService.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/AndroidWebAuthnService.cpp
+++ firefox-147.0.3/dom/webauthn/AndroidWebAuthnService.cpp
@@ -197,9 +197,12 @@ AndroidWebAuthnService::MakeCredential(u
: java::sdk::Boolean::FALSE());
GECKOBUNDLE_FINISH(extensionsBundle);
+ nsString json;
+ (void)aArgs->GetJson(json);
+
auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
credentialBundle, uid, challenge, idList, transportList,
- authSelBundle, extensionsBundle, algs, hash);
+ authSelBundle, extensionsBundle, algs, hash, json);
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
@@ -313,9 +316,12 @@ AndroidWebAuthnService::GetAssertion(uin
GECKOBUNDLE_FINISH(extensionsBundle);
+ nsString json;
+ (void)aArgs->GetJson(json);
+
auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
challenge, idList, transportList, assertionBundle, extensionsBundle,
- hash);
+ hash, json);
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<RefPtr<WebAuthnSignResult>, AndroidWebAuthnError,
true>::FromGeckoResult(geckoResult)
Index: firefox-147.0.3/dom/webauthn/PWebAuthnTransaction.ipdl
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/PWebAuthnTransaction.ipdl
+++ firefox-147.0.3/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -144,6 +144,7 @@ struct WebAuthnMakeCredentialInfo {
WebAuthnAuthenticatorSelection AuthenticatorSelection;
nsString attestationConveyancePreference;
nsString[] Hints;
+ nsString Json;
};
struct WebAuthnMakeCredentialResult {
@@ -170,6 +171,7 @@ struct WebAuthnGetAssertionInfo {
nsString userVerificationRequirement;
bool ConditionallyMediated;
nsString[] Hints;
+ nsString Json;
};
struct WebAuthnGetAssertionResult {
Index: firefox-147.0.3/dom/webauthn/WebAuthnArgs.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnArgs.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnArgs.cpp
@@ -252,6 +252,12 @@ WebAuthnRegisterArgs::GetHints(nsTArray<
return NS_OK;
}
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetJson(nsAString& aJSON) {
+ aJSON = mInfo.Json();
+ return NS_OK;
+}
+
NS_IMPL_ISUPPORTS(WebAuthnSignArgs, nsIWebAuthnSignArgs)
NS_IMETHODIMP
@@ -497,4 +503,10 @@ WebAuthnSignArgs::GetHints(nsTArray<nsSt
return NS_OK;
}
+NS_IMETHODIMP
+WebAuthnSignArgs::GetJson(nsAString& aJSON) {
+ aJSON = mInfo.Json();
+ return NS_OK;
+}
+
} // namespace mozilla::dom
Index: firefox-147.0.3/dom/webauthn/WebAuthnHandler.h
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnHandler.h
+++ firefox-147.0.3/dom/webauthn/WebAuthnHandler.h
@@ -84,10 +84,12 @@ class WebAuthnHandler final : public Abo
}
already_AddRefed<Promise> MakeCredential(
+ JSContext *aCx,
const PublicKeyCredentialCreationOptions& aOptions,
const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
already_AddRefed<Promise> GetAssertion(
+ JSContext *aCx,
const PublicKeyCredentialRequestOptions& aOptions,
const bool aConditionallyMediated,
const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
Index: firefox-147.0.3/dom/webauthn/WebAuthnHandler.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnHandler.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnHandler.cpp
@@ -17,10 +17,12 @@
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
#include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
#include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/glean/DomWebauthnMetrics.h"
+#include "nsContentUtils.h"
#include "nsHTMLDocument.h"
#include "nsIURIMutator.h"
#include "nsThreadUtils.h"
@@ -116,6 +118,7 @@ void WebAuthnHandler::ActorDestroyed() {
}
already_AddRefed<Promise> WebAuthnHandler::MakeCredential(
+ JSContext *aCx,
const PublicKeyCredentialCreationOptions& aOptions,
const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
MOZ_ASSERT(NS_IsMainThread());
@@ -173,13 +176,7 @@ already_AddRefed<Promise> WebAuthnHandle
// If timeoutSeconds was specified, check if its value lies within a
// reasonable range as defined by the platform and if not, correct it to the
// closest value lying within that range.
-
- uint32_t adjustedTimeout = 30000;
- if (aOptions.mTimeout.WasPassed()) {
- adjustedTimeout = aOptions.mTimeout.Value();
- adjustedTimeout = std::max(15000u, adjustedTimeout);
- adjustedTimeout = std::min(120000u, adjustedTimeout);
- }
+ uint32_t adjustedTimeout = WebAuthnTimeout(aOptions.mTimeout);
// <https://w3c.github.io/webauthn/#sctn-appid-extension>
if (aOptions.mExtensions.mAppid.WasPassed()) {
@@ -375,21 +372,23 @@ already_AddRefed<Promise> WebAuthnHandle
// Abort the request if aborted flag is already set.
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
- AutoJSAPI jsapi;
- if (!jsapi.Init(global)) {
- promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
- return promise.forget();
- }
- JSContext* cx = jsapi.cx();
- JS::Rooted<JS::Value> reason(cx);
- aSignal.Value().GetReason(cx, &reason);
+ JS::Rooted<JS::Value> reason(aCx);
+ aSignal.Value().GetReason(aCx, &reason);
promise->MaybeReject(reason);
return promise.forget();
}
- WebAuthnMakeCredentialInfo info(rpId, challenge, adjustedTimeout, excludeList,
- rpInfo, userInfo, coseAlgos, extensions,
- authSelection, attestation, aOptions.mHints);
+ nsString json;
+ nsresult rv = SerializeWebAuthnCreationOptions(
+ aCx, NS_ConvertUTF8toUTF16(rpId), aOptions, json);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ WebAuthnMakeCredentialInfo info(
+ rpId, challenge, adjustedTimeout, excludeList, rpInfo, userInfo,
+ coseAlgos, extensions, authSelection, attestation, aOptions.mHints, json);
// Set up the transaction state. Fallible operations should not be performed
// below this line, as we must not leave the transaction state partially
@@ -430,6 +429,7 @@ already_AddRefed<Promise> WebAuthnHandle
const size_t MAX_ALLOWED_CREDENTIALS = 20;
already_AddRefed<Promise> WebAuthnHandler::GetAssertion(
+ JSContext *aCx,
const PublicKeyCredentialRequestOptions& aOptions,
const bool aConditionallyMediated,
const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
@@ -478,13 +478,7 @@ already_AddRefed<Promise> WebAuthnHandle
// If timeoutSeconds was specified, check if its value lies within a
// reasonable range as defined by the platform and if not, correct it to the
// closest value lying within that range.
-
- uint32_t adjustedTimeout = 30000;
- if (aOptions.mTimeout.WasPassed()) {
- adjustedTimeout = aOptions.mTimeout.Value();
- adjustedTimeout = std::max(15000u, adjustedTimeout);
- adjustedTimeout = std::min(120000u, adjustedTimeout);
- }
+ uint32_t adjustedTimeout = WebAuthnTimeout(aOptions.mTimeout);
// Abort the request if the allowCredentials set is too large
if (aOptions.mAllowCredentials.Length() > MAX_ALLOWED_CREDENTIALS) {
@@ -645,21 +639,23 @@ already_AddRefed<Promise> WebAuthnHandle
// Abort the request if aborted flag is already set.
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
- AutoJSAPI jsapi;
- if (!jsapi.Init(global)) {
- promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
- return promise.forget();
- }
- JSContext* cx = jsapi.cx();
- JS::Rooted<JS::Value> reason(cx);
- aSignal.Value().GetReason(cx, &reason);
+ JS::Rooted<JS::Value> reason(aCx);
+ aSignal.Value().GetReason(aCx, &reason);
promise->MaybeReject(reason);
return promise.forget();
}
- WebAuthnGetAssertionInfo info(
- rpId, maybeAppId, challenge, adjustedTimeout, allowList, extensions,
- aOptions.mUserVerification, aConditionallyMediated, aOptions.mHints);
+ nsString json;
+ nsresult rv = SerializeWebAuthnRequestOptions(
+ aCx, NS_ConvertUTF8toUTF16(rpId), aOptions, json);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+ WebAuthnGetAssertionInfo info(rpId, maybeAppId, challenge, adjustedTimeout,
+ allowList, extensions,
+ aOptions.mUserVerification,
+ aConditionallyMediated, aOptions.mHints, json);
// Set up the transaction state. Fallible operations should not be performed
// below this line, as we must not leave the transaction state partially
Index: firefox-147.0.3/dom/webauthn/WebAuthnUtil.h
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnUtil.h
+++ firefox-147.0.3/dom/webauthn/WebAuthnUtil.h
@@ -32,6 +32,18 @@ bool IsValidRpId(const nsCOMPtr<nsIPrinc
nsresult HashCString(const nsACString& aIn, /* out */ nsTArray<uint8_t>& aOut);
+uint32_t WebAuthnTimeout(const Optional<uint32_t>& aTimeout);
+
+nsresult SerializeWebAuthnCreationOptions(
+ JSContext* aCx, const nsString& aRpId,
+ const PublicKeyCredentialCreationOptions& aOptions,
+ /* out */ nsString& aOut);
+
+nsresult SerializeWebAuthnRequestOptions(
+ JSContext* aCx, const nsString& aRpId,
+ const PublicKeyCredentialRequestOptions& aOptions,
+ /* out */ nsString& aOut);
+
} // namespace mozilla::dom
#endif // mozilla_dom_WebAuthnUtil_h
Index: firefox-147.0.3/dom/webauthn/WebAuthnUtil.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnUtil.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnUtil.cpp
@@ -7,11 +7,14 @@
#include "mozilla/dom/WebAuthnUtil.h"
#include "hasht.h"
+#include "mozilla/Base64.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozpkix/pkixutil.h"
#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
#include "nsHTMLDocument.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
@@ -269,4 +272,331 @@ nsresult HashCString(const nsACString& a
return NS_OK;
}
+uint32_t WebAuthnTimeout(const Optional<uint32_t>& aTimeout) {
+ uint32_t adjustedTimeout = 30000;
+ if (aTimeout.WasPassed()) {
+ adjustedTimeout = aTimeout.Value();
+ adjustedTimeout = std::max(15000u, adjustedTimeout);
+ adjustedTimeout = std::min(120000u, adjustedTimeout);
+ }
+ return adjustedTimeout;
+}
+
+static nsresult SerializeWebAuthnData(
+ const OwningArrayBufferViewOrArrayBuffer& aData, nsString& aOut) {
+ return ProcessTypedArrays(
+ aData, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
+ nsAutoCString result;
+ nsresult rv = mozilla::Base64URLEncode(
+ aData.Length(), aData.Elements(),
+ Base64URLEncodePaddingPolicy::Omit, result);
+ if (NS_SUCCEEDED(rv)) {
+ aOut.Assign(NS_ConvertUTF8toUTF16(result));
+ }
+ return rv;
+ });
+}
+
+nsresult SerializeWebAuthnCreationOptions(
+ JSContext* aCx, const nsString& aRpId,
+ const PublicKeyCredentialCreationOptions& aOptions, nsString& aOut) {
+ nsresult rv;
+ PublicKeyCredentialCreationOptionsJSON json;
+
+ json.mRp.mId.Construct(aRpId);
+
+ json.mRp.mName.Assign(aOptions.mRp.mName);
+
+ json.mUser.mName.Assign(aOptions.mUser.mName);
+
+ rv = SerializeWebAuthnData(aOptions.mUser.mId, json.mUser.mId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ json.mUser.mDisplayName.Assign(aOptions.mUser.mDisplayName);
+
+ rv = SerializeWebAuthnData(aOptions.mChallenge, json.mChallenge);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ json.mPubKeyCredParams = aOptions.mPubKeyCredParams;
+
+ json.mTimeout.Construct(WebAuthnTimeout(aOptions.mTimeout));
+
+ for (const auto& excludeCredential : aOptions.mExcludeCredentials) {
+ PublicKeyCredentialDescriptorJSON* excludeCredentialJSON =
+ json.mExcludeCredentials.AppendElement(fallible);
+ if (!excludeCredentialJSON) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ excludeCredentialJSON->mType = excludeCredential.mType;
+ rv = SerializeWebAuthnData(excludeCredential.mId,
+ excludeCredentialJSON->mId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (excludeCredential.mTransports.WasPassed()) {
+ excludeCredentialJSON->mTransports.Construct(
+ excludeCredential.mTransports.Value());
+ }
+ }
+
+ json.mAuthenticatorSelection.Construct(aOptions.mAuthenticatorSelection);
+
+ json.mHints = aOptions.mHints;
+
+ json.mAttestation = aOptions.mAttestation;
+
+ AuthenticationExtensionsClientInputsJSON& extensionsJSON =
+ json.mExtensions.Construct();
+
+ if (aOptions.mExtensions.mAppid.WasPassed()) {
+ extensionsJSON.mAppid.Construct(aOptions.mExtensions.mAppid.Value());
+ }
+
+ if (aOptions.mExtensions.mCredentialProtectionPolicy.WasPassed()) {
+ extensionsJSON.mCredentialProtectionPolicy.Construct(
+ aOptions.mExtensions.mCredentialProtectionPolicy.Value());
+ }
+
+ if (aOptions.mExtensions.mEnforceCredentialProtectionPolicy.WasPassed()) {
+ extensionsJSON.mEnforceCredentialProtectionPolicy.Construct(
+ aOptions.mExtensions.mEnforceCredentialProtectionPolicy.Value());
+ }
+
+ if (aOptions.mExtensions.mCredProps.WasPassed()) {
+ extensionsJSON.mCredProps.Construct(
+ aOptions.mExtensions.mCredProps.Value());
+ }
+
+ if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
+ extensionsJSON.mHmacCreateSecret.Construct(
+ aOptions.mExtensions.mHmacCreateSecret.Value());
+ }
+
+ if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+ extensionsJSON.mMinPinLength.Construct(
+ aOptions.mExtensions.mMinPinLength.Value());
+ }
+
+ if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
+ const AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
+ aOptions.mExtensions.mLargeBlob.Value();
+ AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
+ extensionsJSON.mLargeBlob.Construct();
+
+ if (largeBlobInputs.mSupport.WasPassed()) {
+ largeBlobInputsJSON.mSupport.Construct(largeBlobInputs.mSupport.Value());
+ }
+
+ if (largeBlobInputs.mRead.WasPassed()) {
+ largeBlobInputsJSON.mRead.Construct(largeBlobInputs.mRead.Value());
+ }
+
+ if (largeBlobInputs.mWrite.WasPassed()) {
+ nsString write;
+ rv = SerializeWebAuthnData(largeBlobInputs.mWrite.Value(), write);
+ NS_ENSURE_SUCCESS(rv, rv);
+ largeBlobInputsJSON.mWrite.Construct(write);
+ }
+ }
+
+ if (aOptions.mExtensions.mPrf.WasPassed()) {
+ const AuthenticationExtensionsPRFInputs& prfInputs =
+ aOptions.mExtensions.mPrf.Value();
+ AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
+ extensionsJSON.mPrf.Construct();
+
+ if (prfInputs.mEval.WasPassed()) {
+ AuthenticationExtensionsPRFValuesJSON& evalJSON =
+ prfInputsJSON.mEval.Construct();
+ rv = SerializeWebAuthnData(prfInputs.mEval.Value().mFirst,
+ evalJSON.mFirst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (prfInputs.mEval.Value().mSecond.WasPassed()) {
+ nsString second;
+ rv = SerializeWebAuthnData(prfInputs.mEval.Value().mSecond.Value(),
+ second);
+ NS_ENSURE_SUCCESS(rv, rv);
+ evalJSON.mSecond.Construct(second);
+ }
+ }
+
+ if (prfInputs.mEvalByCredential.WasPassed()) {
+ for (const auto& entry : prfInputs.mEvalByCredential.Value().Entries()) {
+ auto* jsonEntry =
+ prfInputsJSON.mEvalByCredential.Construct().Entries().AppendElement(
+ fallible);
+ if (!jsonEntry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ jsonEntry->mKey = entry.mKey;
+ AuthenticationExtensionsPRFValuesJSON& valuesJSON = jsonEntry->mValue;
+
+ rv = SerializeWebAuthnData(entry.mValue.mFirst, valuesJSON.mFirst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (entry.mValue.mSecond.WasPassed()) {
+ nsString second;
+ rv = SerializeWebAuthnData(entry.mValue.mSecond.Value(), second);
+ NS_ENSURE_SUCCESS(rv, rv);
+ valuesJSON.mSecond.Construct(second);
+ }
+ }
+ }
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, json, &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString jsonString;
+ if (!nsContentUtils::StringifyJSON(aCx, value, jsonString,
+ UndefinedIsNullStringLiteral)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aOut = jsonString;
+ return NS_OK;
+}
+
+nsresult SerializeWebAuthnRequestOptions(
+ JSContext* aCx, const nsString& aRpId,
+ const PublicKeyCredentialRequestOptions& aOptions, nsString& aOut) {
+ nsresult rv;
+ PublicKeyCredentialRequestOptionsJSON json;
+
+ rv = SerializeWebAuthnData(aOptions.mChallenge, json.mChallenge);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ json.mTimeout.Construct(WebAuthnTimeout(aOptions.mTimeout));
+
+ json.mRpId.Construct(aRpId);
+
+ for (const auto& allowCredential : aOptions.mAllowCredentials) {
+ PublicKeyCredentialDescriptorJSON* allowCredentialJSON =
+ json.mAllowCredentials.AppendElement(fallible);
+ if (!allowCredentialJSON) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ allowCredentialJSON->mType = allowCredential.mType;
+ rv = SerializeWebAuthnData(allowCredential.mId, allowCredentialJSON->mId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (allowCredential.mTransports.WasPassed()) {
+ allowCredentialJSON->mTransports.Construct(
+ allowCredential.mTransports.Value());
+ }
+ }
+
+ json.mUserVerification = aOptions.mUserVerification;
+
+ json.mHints = aOptions.mHints;
+
+ AuthenticationExtensionsClientInputsJSON& extensionsJSON =
+ json.mExtensions.Construct();
+
+ if (aOptions.mExtensions.mAppid.WasPassed()) {
+ extensionsJSON.mAppid.Construct(aOptions.mExtensions.mAppid.Value());
+ }
+
+ if (aOptions.mExtensions.mCredProps.WasPassed()) {
+ extensionsJSON.mCredProps.Construct(
+ aOptions.mExtensions.mCredProps.Value());
+ }
+
+ if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
+ extensionsJSON.mHmacCreateSecret.Construct(
+ aOptions.mExtensions.mHmacCreateSecret.Value());
+ }
+
+ if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+ extensionsJSON.mMinPinLength.Construct(
+ aOptions.mExtensions.mMinPinLength.Value());
+ }
+
+ if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
+ const AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
+ aOptions.mExtensions.mLargeBlob.Value();
+ AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
+ extensionsJSON.mLargeBlob.Construct();
+
+ if (largeBlobInputs.mSupport.WasPassed()) {
+ largeBlobInputsJSON.mSupport.Construct(largeBlobInputs.mSupport.Value());
+ }
+
+ if (largeBlobInputs.mRead.WasPassed()) {
+ largeBlobInputsJSON.mRead.Construct(largeBlobInputs.mRead.Value());
+ }
+
+ if (largeBlobInputs.mWrite.WasPassed()) {
+ nsString write;
+ rv = SerializeWebAuthnData(largeBlobInputs.mWrite.Value(), write);
+ NS_ENSURE_SUCCESS(rv, rv);
+ largeBlobInputsJSON.mWrite.Construct(write);
+ }
+ }
+
+ if (aOptions.mExtensions.mPrf.WasPassed()) {
+ const AuthenticationExtensionsPRFInputs& prfInputs =
+ aOptions.mExtensions.mPrf.Value();
+ AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
+ extensionsJSON.mPrf.Construct();
+
+ if (prfInputs.mEval.WasPassed()) {
+ AuthenticationExtensionsPRFValuesJSON& evalJSON =
+ prfInputsJSON.mEval.Construct();
+ rv = SerializeWebAuthnData(prfInputs.mEval.Value().mFirst,
+ evalJSON.mFirst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (prfInputs.mEval.Value().mSecond.WasPassed()) {
+ nsString second;
+ rv = SerializeWebAuthnData(prfInputs.mEval.Value().mSecond.Value(),
+ second);
+ NS_ENSURE_SUCCESS(rv, rv);
+ evalJSON.mSecond.Construct(second);
+ }
+ }
+
+ if (prfInputs.mEvalByCredential.WasPassed()) {
+ for (const auto& entry : prfInputs.mEvalByCredential.Value().Entries()) {
+ auto* jsonEntry =
+ prfInputsJSON.mEvalByCredential.Construct().Entries().AppendElement(
+ fallible);
+ if (!jsonEntry) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ jsonEntry->mKey = entry.mKey;
+ AuthenticationExtensionsPRFValuesJSON& valuesJSON = jsonEntry->mValue;
+
+ rv = SerializeWebAuthnData(entry.mValue.mFirst, valuesJSON.mFirst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (entry.mValue.mSecond.WasPassed()) {
+ nsString second;
+ rv = SerializeWebAuthnData(entry.mValue.mSecond.Value(), second);
+ NS_ENSURE_SUCCESS(rv, rv);
+ valuesJSON.mSecond.Construct(second);
+ }
+ }
+ }
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, json, &value)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString jsonString;
+ if (!nsContentUtils::StringifyJSON(aCx, value, jsonString,
+ UndefinedIsNullStringLiteral)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aOut = jsonString;
+ return NS_OK;
+}
+
} // namespace mozilla::dom
Index: firefox-147.0.3/dom/webauthn/moz.build
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/moz.build
+++ firefox-147.0.3/dom/webauthn/moz.build
@@ -94,3 +94,7 @@ MOCHITEST_MANIFESTS += ["tests/mochitest
BROWSER_CHROME_MANIFESTS += [
"tests/browser/browser.toml",
]
+
+TEST_DIRS += [
+ "tests/gtest",
+]
Index: firefox-147.0.3/dom/webauthn/nsIWebAuthnArgs.idl
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/nsIWebAuthnArgs.idl
+++ firefox-147.0.3/dom/webauthn/nsIWebAuthnArgs.idl
@@ -67,6 +67,8 @@ interface nsIWebAuthnRegisterArgs : nsIS
readonly attribute Array<AString> hints;
+ readonly attribute AString json;
+
readonly attribute boolean privateBrowsing;
};
@@ -115,6 +117,8 @@ interface nsIWebAuthnSignArgs : nsISuppo
readonly attribute Array<AString> hints;
+ readonly attribute AString json;
+
readonly attribute boolean conditionallyMediated;
readonly attribute boolean privateBrowsing;
Index: firefox-147.0.3/dom/webauthn/tests/gtest/TestWebAuthnSerialization.cpp
===================================================================
--- /dev/null
+++ firefox-147.0.3/dom/webauthn/tests/gtest/TestWebAuthnSerialization.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "js/ArrayBuffer.h"
+#include "js/CharacterEncoding.h"
+#include "js/GlobalObject.h"
+#include "js/JSON.h"
+#include "js/Realm.h"
+#include "js/RootingAPI.h"
+#include "js/String.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnUtil.h"
+#include "mozilla/gtest/MozHelpers.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class WebAuthnSerializationTest : public ::testing::Test {
+ protected:
+ JSContext* Context() { return mCx; }
+
+ GlobalObject CreateGlobalObject(JS::Handle<JSObject*> aGlobal) {
+ return GlobalObject(mCx, aGlobal);
+ }
+
+ bool JSONValuesEqual(JS::Handle<JS::Value> aVal1,
+ JS::Handle<JS::Value> aVal2) {
+ if (aVal1.type() != aVal2.type()) {
+ return false;
+ }
+
+ if (aVal1.isString()) {
+ int32_t result;
+ if (!JS_CompareStrings(mCx, aVal1.toString(), aVal2.toString(),
+ &result)) {
+ return false;
+ }
+ return result == 0;
+ }
+
+ if (aVal1.isNumber()) {
+ return aVal1.toNumber() == aVal2.toNumber();
+ }
+
+ if (aVal1.isBoolean()) {
+ return aVal1.toBoolean() == aVal2.toBoolean();
+ }
+
+ if (aVal1.isUndefined() || aVal1.isNull()) {
+ return true;
+ }
+
+ if (aVal1.isObject()) {
+ JS::Rooted<JSObject*> obj1(mCx, &aVal1.toObject());
+ JS::Rooted<JSObject*> obj2(mCx, &aVal2.toObject());
+
+ bool isArray1, isArray2;
+ if (!JS::IsArrayObject(mCx, obj1, &isArray1) ||
+ !JS::IsArrayObject(mCx, obj2, &isArray2)) {
+ return false;
+ }
+
+ if (isArray1 && isArray2) {
+ uint32_t length1, length2;
+ if (!JS::GetArrayLength(mCx, obj1, &length1) ||
+ !JS::GetArrayLength(mCx, obj2, &length2)) {
+ return false;
+ }
+ if (length1 != length2) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < length1; i++) {
+ JS::Rooted<JS::Value> elem1(mCx);
+ JS::Rooted<JS::Value> elem2(mCx);
+ if (!JS_GetElement(mCx, obj1, i, &elem1) ||
+ !JS_GetElement(mCx, obj2, i, &elem2)) {
+ return false;
+ }
+ if (!JSONValuesEqual(elem1, elem2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ JS::Rooted<JS::IdVector> ids1(mCx, JS::IdVector(mCx));
+ JS::Rooted<JS::IdVector> ids2(mCx, JS::IdVector(mCx));
+ if (!JS_Enumerate(mCx, obj1, &ids1) || !JS_Enumerate(mCx, obj2, &ids2)) {
+ return false;
+ }
+
+ if (ids1.length() != ids2.length()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < ids1.length(); i++) {
+ JS::Rooted<JS::Value> val1(mCx);
+ JS::Rooted<JS::Value> val2(mCx);
+ if (!JS_GetPropertyById(mCx, obj1, ids1[i], &val1) ||
+ !JS_GetPropertyById(mCx, obj2, ids1[i], &val2)) {
+ return false;
+ }
+
+ if (!JSONValuesEqual(val1, val2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ JSContext* mCx;
+};
+
+TEST_F(WebAuthnSerializationTest, JSONStringRoundTripForCreationOptions) {
+ JS::Rooted<JSObject*> global(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(global));
+ mCx = jsAPI.cx();
+
+ const nsLiteralString inputJSONStr =
+ uR"({
+ "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",
+ "extensions": {
+ "appid": "https://www.example.com/appID",
+ "credProps": true,
+ "hmacCreateSecret": true,
+ "minPinLength": true,
+ "credentialProtectionPolicy": "userVerificationOptional",
+ "enforceCredentialProtectionPolicy": true,
+ "largeBlob": {
+ "support": "required"
+ },
+ "prf": {
+ "eval": {
+ "first": "Zmlyc3Q",
+ "second": "c2Vjb25k"
+ },
+ "evalByCredential": {
+ "19TVpqBBOAM": {
+ "first": "Zmlyc3Q",
+ "second": "c2Vjb25k"
+ }
+ }
+ }
+ }
+ })"_ns;
+
+ GlobalObject globalObject = CreateGlobalObject(global);
+
+ JS::Rooted<JS::Value> inputJSONValue(mCx);
+ ASSERT_TRUE(JS_ParseJSON(mCx, inputJSONStr.get(), inputJSONStr.Length(),
+ &inputJSONValue));
+
+ RootedDictionary<PublicKeyCredentialCreationOptionsJSON> jsonOptions(mCx);
+ ASSERT_TRUE(jsonOptions.Init(mCx, inputJSONValue));
+
+ RootedDictionary<PublicKeyCredentialCreationOptions> options(mCx);
+ IgnoredErrorResult error;
+
+ PublicKeyCredential::ParseCreationOptionsFromJSON(globalObject, jsonOptions,
+ options, error);
+ ASSERT_FALSE(error.Failed());
+
+ nsString outputJSONStr;
+ nsresult rv = SerializeWebAuthnCreationOptions(mCx, options.mRp.mId.Value(),
+ options, outputJSONStr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ JS::Rooted<JS::Value> outputJSONValue(mCx);
+ ASSERT_TRUE(JS_ParseJSON(mCx, outputJSONStr.get(), outputJSONStr.Length(),
+ &outputJSONValue));
+ ASSERT_FALSE(JS_IsExceptionPending(mCx));
+
+ EXPECT_TRUE(JSONValuesEqual(inputJSONValue, outputJSONValue));
+}
+
+TEST_F(WebAuthnSerializationTest, JSONStringRoundTripForRequestOptions) {
+ JS::Rooted<JSObject*> global(
+ mozilla::dom::RootingCx(),
+ mozilla::dom::SimpleGlobalObject::Create(
+ mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+ mozilla::dom::AutoJSAPI jsAPI;
+ ASSERT_TRUE(jsAPI.Init(global));
+ mCx = jsAPI.cx();
+
+ const nsLiteralString inputJSONStr =
+ uR"({
+ "challenge": "QAfaZwEQCkQ",
+ "timeout": 25000,
+ "rpId": "example.com",
+ "allowCredentials": [{
+ "type": "public-key",
+ "id": "BTBXXGuXRTk",
+ "transports": ["smart-card"]
+ }],
+ "userVerification": "discouraged",
+ "hints": ["client-device"],
+ "extensions": {
+ "appid": "https://www.example.com/anotherAppID",
+ "largeBlob": {
+ "read": true,
+ "write": "YmxvYmRhdGE"
+ },
+ "prf": {
+ "eval": {
+ "first": "Zmlyc3Q",
+ "second": "c2Vjb25k"
+ },
+ "evalByCredential": {
+ "19TVpqBBOAM": {
+ "first": "Zmlyc3Q",
+ "second": "c2Vjb25k"
+ }
+ }
+ }
+ }
+ })"_ns;
+
+ GlobalObject globalObject = CreateGlobalObject(global);
+
+ JS::Rooted<JS::Value> inputJSONValue(mCx);
+ ASSERT_TRUE(JS_ParseJSON(mCx, inputJSONStr.get(), inputJSONStr.Length(),
+ &inputJSONValue));
+
+ RootedDictionary<PublicKeyCredentialRequestOptionsJSON> jsonOptions(mCx);
+ ASSERT_TRUE(jsonOptions.Init(mCx, inputJSONValue));
+
+ RootedDictionary<PublicKeyCredentialRequestOptions> options(mCx);
+ IgnoredErrorResult error;
+
+ PublicKeyCredential::ParseRequestOptionsFromJSON(globalObject, jsonOptions,
+ options, error);
+ ASSERT_FALSE(error.Failed());
+
+ nsString outputJSONStr;
+ nsresult rv = SerializeWebAuthnRequestOptions(mCx, options.mRpId.Value(),
+ options, outputJSONStr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ JS::Rooted<JS::Value> outputJSONValue(mCx);
+ ASSERT_TRUE(JS_ParseJSON(mCx, outputJSONStr.get(), outputJSONStr.Length(),
+ &outputJSONValue));
+ ASSERT_FALSE(JS_IsExceptionPending(mCx));
+
+ EXPECT_TRUE(JSONValuesEqual(inputJSONValue, outputJSONValue));
+}
Index: firefox-147.0.3/dom/webauthn/tests/gtest/moz.build
===================================================================
--- /dev/null
+++ firefox-147.0.3/dom/webauthn/tests/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestWebAuthnSerialization.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
Index: firefox-147.0.3/dom/webidl/WebAuthentication.webidl
===================================================================
--- firefox-147.0.3.orig/dom/webidl/WebAuthentication.webidl
+++ firefox-147.0.3/dom/webidl/WebAuthentication.webidl
@@ -89,6 +89,7 @@ partial interface PublicKeyCredential {
[Throws, Pref="security.webauthn.enable_json_serialization_methods"] static PublicKeyCredentialCreationOptions parseCreationOptionsFromJSON(PublicKeyCredentialCreationOptionsJSON options);
};
+[GenerateConversionToJS]
dictionary PublicKeyCredentialCreationOptionsJSON {
required PublicKeyCredentialRpEntity rp;
required PublicKeyCredentialUserEntityJSON user;
@@ -99,7 +100,6 @@ dictionary PublicKeyCredentialCreationOp
AuthenticatorSelectionCriteria authenticatorSelection;
sequence<DOMString> hints = [];
DOMString attestation = "none";
- sequence<DOMString> attestationFormats = [];
AuthenticationExtensionsClientInputsJSON extensions;
};
@@ -123,6 +123,7 @@ partial interface PublicKeyCredential {
[Throws, Pref="security.webauthn.enable_json_serialization_methods"] static PublicKeyCredentialRequestOptions parseRequestOptionsFromJSON(PublicKeyCredentialRequestOptionsJSON options);
};
+[GenerateConversionToJS]
dictionary PublicKeyCredentialRequestOptionsJSON {
required Base64URLString challenge;
unsigned long timeout;
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
@@ -21,11 +21,10 @@ import android.credentials.PrepareGetCre
import android.os.Build;
import android.os.Bundle;
import android.os.OutcomeReceiver;
-import android.util.Base64;
import android.util.Log;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.WebAuthnUtils;
import org.mozilla.geckoview.GeckoResult;
@@ -69,79 +68,57 @@ public class WebAuthnCredentialManager {
}
private static Bundle getRequestBundleForMakeCredential(
- final GeckoBundle credentialBundle,
- final byte[] userId,
- final byte[] challenge,
- final int[] algs,
- final WebAuthnUtils.WebAuthnPublicCredential[] excludeList,
- final GeckoBundle authenticatorSelection,
- final GeckoBundle extensions,
- final byte[] clientDataHash) {
+ final byte[] clientDataHash, final String requestJSON) {
+ final Bundle bundle = getRequestBundle(requestJSON, clientDataHash);
+ if (bundle == null) {
+ return null;
+ }
+
+ final JSONObject json;
try {
- final JSONObject requestJSON =
- WebAuthnUtils.getJSONObjectForMakeCredential(
- credentialBundle,
- userId,
- challenge,
- algs,
- excludeList,
- authenticatorSelection,
- extensions);
- final Bundle bundle = getRequestBundle(requestJSON.toString(), clientDataHash);
- if (bundle == null) {
- return null;
- }
-
- final Bundle displayInfoBundle = new Bundle();
- displayInfoBundle.putCharSequence(
- BUNDLE_KEY_USER_ID,
- Base64.encodeToString(userId, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
- final GeckoBundle userBundle = credentialBundle.getBundle("user");
- displayInfoBundle.putString(
- BUNDLE_KEY_USER_DISPLAY_NAME, userBundle.getString("displayName", ""));
- bundle.putBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO, displayInfoBundle);
-
- bundle.putString(
- BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST);
- return bundle;
+ json = new JSONObject(requestJSON);
} catch (final JSONException e) {
- Log.e(LOGTAG, "Couldn't generate JSON object for request", e);
+ return null;
}
- return null;
+
+ final JSONObject userField = json.optJSONObject("user");
+ if (userField == null) {
+ return null;
+ }
+
+ final Bundle displayInfoBundle = new Bundle();
+ displayInfoBundle.putCharSequence(BUNDLE_KEY_USER_ID, userField.optString("id"));
+ displayInfoBundle.putString(BUNDLE_KEY_USER_DISPLAY_NAME, userField.optString("displayName"));
+ bundle.putBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO, displayInfoBundle);
+
+ bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST);
+ return bundle;
}
private static Bundle getRequestBundleForGetAssertion(
- final byte[] challenge,
- final WebAuthnUtils.WebAuthnPublicCredential[] allowList,
- final GeckoBundle assertionBundle,
- final GeckoBundle extensionsBundle,
- final byte[] clientDataHash) {
+ final byte[] clientDataHash, final String requestJSON) {
+ final Bundle bundle = getRequestBundle(requestJSON, clientDataHash);
+ if (bundle == null) {
+ return null;
+ }
+
+ final JSONObject json;
try {
- final JSONObject requestJSON =
- WebAuthnUtils.getJSONObjectForGetAssertion(
- challenge, allowList, assertionBundle, extensionsBundle);
- final Bundle bundle = getRequestBundle(requestJSON.toString(), clientDataHash);
- if (bundle == null) {
- return null;
- }
- bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
- return bundle;
+ json = new JSONObject(requestJSON);
} catch (final JSONException e) {
- Log.e(LOGTAG, "Couldn't generate JSON object for request", e);
+ return null;
}
- return null;
+ final JSONArray allowList = json.optJSONArray("allowCredentials");
+ bundle.putBoolean(
+ BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, allowList != null && allowList.length() > 0);
+
+ bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
+ return bundle;
}
@SuppressLint("MissingPermission")
public static GeckoResult<WebAuthnUtils.MakeCredentialResponse> makeCredential(
- final GeckoBundle credentialBundle,
- final byte[] userId,
- final byte[] challenge,
- final int[] algs,
- final WebAuthnUtils.WebAuthnPublicCredential[] excludeList,
- final GeckoBundle authenticatorSelection,
- final GeckoBundle extensions,
- final byte[] clientDataHash) {
+ final String origin, final byte[] clientDataHash, final String requestJSON) {
// We use Credential Manager first. If it doesn't work, we use GMS FIDO2.
// Credential manager may support non-discoverable keys,
@@ -158,16 +135,7 @@ public class WebAuthnCredentialManager {
return GeckoResult.fromException(new WebAuthnUtils.Exception("NOT_SUPPORTED_ERR"));
}
- final Bundle requestBundle =
- getRequestBundleForMakeCredential(
- credentialBundle,
- userId,
- challenge,
- algs,
- excludeList,
- authenticatorSelection,
- extensions,
- clientDataHash);
+ final Bundle requestBundle = getRequestBundleForMakeCredential(clientDataHash, requestJSON);
if (requestBundle == null) {
return GeckoResult.fromException(new WebAuthnUtils.Exception("UNKNOWN_ERR"));
}
@@ -179,7 +147,7 @@ public class WebAuthnCredentialManager {
new CreateCredentialRequest.Builder(
TYPE_PUBLIC_KEY_CREDENTIAL, requestBundle, requestBundle)
.setAlwaysSendAppInfoToProvider(true)
- .setOrigin(credentialBundle.getString("origin"))
+ .setOrigin(origin)
.build();
final CredentialManager manager =
@@ -247,24 +215,16 @@ public class WebAuthnCredentialManager {
@SuppressLint("MissingPermission")
public static GeckoResult<PrepareGetCredentialResponse.PendingGetCredentialHandle>
prepareGetAssertion(
- final byte[] challenge,
- final WebAuthnUtils.WebAuthnPublicCredential[] allowList,
- final GeckoBundle assertionBundle,
- final GeckoBundle extensions,
- final byte[] clientDataHash) {
+ final String origin, final byte[] clientDataHash, final String requestJSON) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// No credential manager. Relay to GMS FIDO2
return GeckoResult.fromValue(null);
}
- final Bundle requestBundle =
- getRequestBundleForGetAssertion(
- challenge, allowList, assertionBundle, extensions, clientDataHash);
+ final Bundle requestBundle = getRequestBundleForGetAssertion(clientDataHash, requestJSON);
if (requestBundle == null) {
return GeckoResult.fromValue(null);
}
- requestBundle.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, allowList.length > 0);
-
final CredentialOption credentialOption =
new CredentialOption.Builder(TYPE_PUBLIC_KEY_CREDENTIAL, requestBundle, requestBundle)
.build();
@@ -273,7 +233,7 @@ public class WebAuthnCredentialManager {
new GetCredentialRequest.Builder(requestBundle)
.addCredentialOption(credentialOption)
.setAlwaysSendAppInfoToProvider(true)
- .setOrigin(assertionBundle.getString("origin"))
+ .setOrigin(origin)
.build();
final Context context = GeckoAppShell.getApplicationContext();
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
@@ -324,103 +324,6 @@ public class WebAuthnUtils {
}
}
- @NonNull
- public static JSONObject getJSONObjectForMakeCredential(
- final GeckoBundle credentialBundle,
- final byte[] userId,
- final byte[] challenge,
- final int[] algs,
- final WebAuthnPublicCredential[] excludeList,
- final GeckoBundle authenticatorSelection,
- final GeckoBundle extensions)
- throws JSONException {
- final JSONObject json = credentialBundle.toJSONObject();
- // origin is unnecessary for requestJSON.
- json.remove("origin");
- json.remove("isWebAuthn");
-
- json.put(
- "challenge",
- Base64.encodeToString(challenge, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
- final JSONObject user = json.getJSONObject("user");
- user.put(
- "id", Base64.encodeToString(userId, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
- final JSONArray pubKeyCredParams = new JSONArray();
- for (final int alg : algs) {
- final JSONObject item = new JSONObject();
- item.put("alg", alg);
- item.put("type", "public-key");
- pubKeyCredParams.put(item);
- }
- json.put("pubKeyCredParams", pubKeyCredParams);
-
- final JSONArray excludeCredentials = new JSONArray();
- for (final WebAuthnPublicCredential cred : excludeList) {
- excludeCredentials.put(cred.toJSONObject());
- }
- json.put("excludeCredentials", excludeCredentials);
-
- final JSONObject authenticatorSelectionJSON = authenticatorSelection.toJSONObject();
- /*
- dom/webauthn/WebAuthnHandler.cpp: WebAuthnHandler::MakeCredential set `residentKey`
- to "required" if there is no `residentKey` and `requireResidentKey` is true, and
- `requireResidentKey` should be true if `residentKey` is "required". So we can retrieve
- `requireResidentKey`'s value from `residentKey`.
- `requireResidentKey` is only used if `residentKey` isn't set, so it shouldn't be used by any
- authenticator that follows the specs.
- */
- authenticatorSelectionJSON.put(
- "requireResidentKey",
- authenticatorSelection.getString("residentKey", "").equals("required"));
- json.put("authenticatorSelection", authenticatorSelectionJSON);
-
- final JSONObject extensionsJSON = extensions.toJSONObject();
- json.put("extensions", extensionsJSON);
-
- if (DEBUG) {
- Log.d(LOGTAG, "getJSONObjectForMakeCredential: JSON=\"" + json.toString() + "\"");
- }
-
- return json;
- }
-
- @NonNull
- public static JSONObject getJSONObjectForGetAssertion(
- final byte[] challenge,
- final WebAuthnPublicCredential[] allowList,
- final GeckoBundle assertionBundle,
- final GeckoBundle extensionsBundle)
- throws JSONException {
- final JSONObject json = assertionBundle.toJSONObject();
- // origin is unnecessary for requestJSON.
- json.remove("origin");
- json.remove("isWebAuthn");
-
- json.put(
- "challenge",
- Base64.encodeToString(challenge, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
- final JSONArray allowCredentials = new JSONArray();
- for (final WebAuthnPublicCredential cred : allowList) {
- allowCredentials.put(cred.toJSONObject());
- }
- json.put("allowCredentials", allowCredentials);
-
- if (extensionsBundle.containsKey("fidoAppId")) {
- final JSONObject extensions = new JSONObject();
- extensions.put("appid", extensionsBundle.getString("fidoAppId"));
- json.put("extensions", extensions);
- }
-
- if (DEBUG) {
- Log.d(LOGTAG, "getJSONObjectForGetAssertion: JSON=\"" + json.toString() + "\"");
- }
-
- return json;
- }
-
public static MakeCredentialResponse getMakeCredentialResponse(final @NonNull String responseJson)
throws JSONException, IllegalArgumentException {
final JSONObject json = new JSONObject(responseJson);
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
@@ -307,7 +307,8 @@ import org.mozilla.gecko.util.WebAuthnUt
final GeckoBundle authenticatorSelection,
final GeckoBundle extensions,
final int[] algs,
- final ByteBuffer clientDataHash) {
+ final ByteBuffer clientDataHash,
+ final String requestJSON) {
final ArrayList<WebAuthnUtils.WebAuthnPublicCredential> excludeArrayList;
final byte[] challBytes = new byte[challenge.remaining()];
@@ -331,14 +332,7 @@ import org.mozilla.gecko.util.WebAuthnUt
final GeckoResult<WebAuthnUtils.MakeCredentialResponse> result = new GeckoResult<>();
WebAuthnCredentialManager.makeCredential(
- credentialBundle,
- userBytes,
- challBytes,
- algs,
- excludeList,
- authenticatorSelection,
- extensions,
- clientDataHashBytes)
+ credentialBundle.getString("origin"), clientDataHashBytes, requestJSON)
.accept(
response -> result.complete(response),
exceptionInCredManager -> {
@@ -510,7 +504,8 @@ import org.mozilla.gecko.util.WebAuthnUt
final ByteBuffer transportList,
final GeckoBundle assertionBundle,
final GeckoBundle extensions,
- final ByteBuffer clientDataHash) {
+ final ByteBuffer clientDataHash,
+ final String requestJSON) {
final ArrayList<WebAuthnUtils.WebAuthnPublicCredential> allowArrayList;
final byte[] challBytes = new byte[challenge.remaining()];
@@ -549,7 +544,7 @@ import org.mozilla.gecko.util.WebAuthnUt
}
WebAuthnCredentialManager.prepareGetAssertion(
- challBytes, allowList, assertionBundle, extensions, clientDataHashBytes)
+ assertionBundle.getString("origin"), clientDataHashBytes, requestJSON)
.accept(
pendingHandle -> {
if (pendingHandle != null) {
Index: firefox-147.0.3/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
+++ firefox-147.0.3/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
@@ -11,7 +11,6 @@ import java.util.Arrays;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
-import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -19,106 +18,6 @@ import org.robolectric.RobolectricTestRu
@RunWith(RobolectricTestRunner.class)
public class WebAuthnUtilsTest {
@Test
- public void requestJSONForMakeCredentail() throws Exception {
- final GeckoBundle credentialBundle = new GeckoBundle(7);
-
- final GeckoBundle rpBundle = new GeckoBundle(2);
- rpBundle.putString("id", "example.com");
- rpBundle.putString("name", "Example");
- credentialBundle.putBundle("rp", rpBundle);
-
- final GeckoBundle userBundle = new GeckoBundle(2);
- userBundle.putString("name", "Foo");
- userBundle.putString("displayName", "Foo");
- credentialBundle.putBundle("user", userBundle);
-
- credentialBundle.putString("origin", "example.com");
- credentialBundle.putDouble("timeout", 5000.0);
- credentialBundle.putString("attestation", "none");
-
- final GeckoBundle authenticatorSelection = new GeckoBundle(3);
- authenticatorSelection.putString("userVerification", "preferred");
- authenticatorSelection.putString("authenticatorAttachment", "platform");
- authenticatorSelection.putString("residentKey", "required");
-
- final GeckoBundle extensions = new GeckoBundle(1);
- extensions.putBoolean("credProps", true);
-
- final byte[] userId = new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
- final byte[] challenge =
- new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- final int[] algs = new int[] {-7};
- final WebAuthnUtils.WebAuthnPublicCredential[] excludeList =
- new WebAuthnUtils.WebAuthnPublicCredential[0];
-
- final JSONObject request =
- WebAuthnUtils.getJSONObjectForMakeCredential(
- credentialBundle,
- userId,
- challenge,
- algs,
- excludeList,
- authenticatorSelection,
- extensions);
-
- final JSONObject expected =
- new JSONObject(
- "{"
- + "\"attestation\":\"none\","
- + "\"challenge\":\"AAECAwQFBgcICQoLDA0ODw\","
- + "\"authenticatorSelection\":{"
- + "\"userVerification\":\"preferred\",\"requireResidentKey\":true,"
- + "\"residentKey\":\"required\",\"authenticatorAttachment\":\"platform\"},"
- + "\"user\":{\"displayName\":\"Foo\",\"name\":\"Foo\",\"id\":\"AAECAwQFBgc\"},"
- + "\"rp\":{\"name\":\"Example\",\"id\":\"example.com\"},"
- + "\"excludeCredentials\":[],"
- + "\"timeout\":5000,"
- + "\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],"
- + "\"extensions\":{\"credProps\":true}"
- + "}");
-
- // No method to compare JSONObject. Use GeckoBundle instead.
- assertEquals(
- "request JSON for MakeCredential should be matched",
- GeckoBundle.fromJSONObject(expected),
- GeckoBundle.fromJSONObject(request));
- }
-
- @Test
- public void requestJSONForGetAssertion() throws Exception {
- final GeckoBundle assertionBundle = new GeckoBundle(3);
- assertionBundle.putDouble("timeout", 5000.0);
- assertionBundle.putString("rpId", "example.com");
- assertionBundle.putString("userVerification", "preferred");
- final GeckoBundle extensionsBundle = new GeckoBundle(0);
-
- final byte[] challenge =
- new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- final WebAuthnUtils.WebAuthnPublicCredential[] allowList =
- new WebAuthnUtils.WebAuthnPublicCredential[0];
-
- final JSONObject response =
- WebAuthnUtils.getJSONObjectForGetAssertion(
- challenge, allowList, assertionBundle, extensionsBundle);
-
- final JSONObject expected =
- new JSONObject(
- "{"
- + "\"allowCredentials\":[],"
- + "\"challenge\":\"AAECAwQFBgcICQoLDA0ODw\","
- + "\"timeout\":5000,"
- + "\"rpId\":\"example.com\","
- + "\"userVerification\":\"preferred\""
- + "}");
-
- // No method to compare JSONObject. Use GeckoBundle instead.
- assertEquals(
- "request JSON for GetAssertion should be matched",
- GeckoBundle.fromJSONObject(expected),
- GeckoBundle.fromJSONObject(response));
- }
-
- @Test
public void responseJSONForMakeCredential() throws Exception {
// attestationObject isn't valid format, but this unit test is that parsing JSON and building
// parameters.