summaryrefslogtreecommitdiffstats
path: root/dom/webauthn
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webauthn')
-rw-r--r--dom/webauthn/AndroidWebAuthnService.cpp428
-rw-r--r--dom/webauthn/AndroidWebAuthnService.h93
-rw-r--r--dom/webauthn/AuthenticatorAssertionResponse.cpp165
-rw-r--r--dom/webauthn/AuthenticatorAssertionResponse.h63
-rw-r--r--dom/webauthn/AuthenticatorAttestationResponse.cpp241
-rw-r--r--dom/webauthn/AuthenticatorAttestationResponse.h67
-rw-r--r--dom/webauthn/AuthenticatorResponse.cpp52
-rw-r--r--dom/webauthn/AuthenticatorResponse.h51
-rw-r--r--dom/webauthn/AuthrsBridge_ffi.h25
-rw-r--r--dom/webauthn/MacOSWebAuthnService.h21
-rw-r--r--dom/webauthn/MacOSWebAuthnService.mm1166
-rw-r--r--dom/webauthn/PWebAuthnTransaction.ipdl157
-rw-r--r--dom/webauthn/PublicKeyCredential.cpp377
-rw-r--r--dom/webauthn/PublicKeyCredential.h93
-rw-r--r--dom/webauthn/WebAuthnArgs.cpp257
-rw-r--r--dom/webauthn/WebAuthnArgs.h91
-rw-r--r--dom/webauthn/WebAuthnAutoFillEntry.cpp35
-rw-r--r--dom/webauthn/WebAuthnAutoFillEntry.h51
-rw-r--r--dom/webauthn/WebAuthnCoseIdentifiers.h22
-rw-r--r--dom/webauthn/WebAuthnEnumStrings.h50
-rw-r--r--dom/webauthn/WebAuthnManager.cpp838
-rw-r--r--dom/webauthn/WebAuthnManager.h160
-rw-r--r--dom/webauthn/WebAuthnManagerBase.cpp67
-rw-r--r--dom/webauthn/WebAuthnManagerBase.h63
-rw-r--r--dom/webauthn/WebAuthnPromiseHolder.cpp88
-rw-r--r--dom/webauthn/WebAuthnPromiseHolder.h69
-rw-r--r--dom/webauthn/WebAuthnResult.cpp192
-rw-r--r--dom/webauthn/WebAuthnResult.h229
-rw-r--r--dom/webauthn/WebAuthnService.cpp220
-rw-r--r--dom/webauthn/WebAuthnService.h87
-rw-r--r--dom/webauthn/WebAuthnTransactionChild.cpp87
-rw-r--r--dom/webauthn/WebAuthnTransactionChild.h60
-rw-r--r--dom/webauthn/WebAuthnTransactionParent.cpp409
-rw-r--r--dom/webauthn/WebAuthnTransactionParent.h60
-rw-r--r--dom/webauthn/WebAuthnTransportIdentifiers.h16
-rw-r--r--dom/webauthn/WebAuthnUtil.cpp156
-rw-r--r--dom/webauthn/WebAuthnUtil.h26
-rw-r--r--dom/webauthn/WinWebAuthnService.cpp1047
-rw-r--r--dom/webauthn/WinWebAuthnService.h49
-rw-r--r--dom/webauthn/authrs_bridge/Cargo.toml24
-rw-r--r--dom/webauthn/authrs_bridge/src/about_webauthn_controller.rs166
-rw-r--r--dom/webauthn/authrs_bridge/src/lib.rs1534
-rw-r--r--dom/webauthn/authrs_bridge/src/test_token.rs974
-rw-r--r--dom/webauthn/components.conf14
-rw-r--r--dom/webauthn/libudev-sys/Cargo.toml38
-rw-r--r--dom/webauthn/libudev-sys/src/lib.rs182
-rw-r--r--dom/webauthn/moz.build97
-rw-r--r--dom/webauthn/nsIWebAuthnArgs.idl98
-rw-r--r--dom/webauthn/nsIWebAuthnAttObj.idl20
-rw-r--r--dom/webauthn/nsIWebAuthnPromise.idl21
-rw-r--r--dom/webauthn/nsIWebAuthnResult.idl62
-rw-r--r--dom/webauthn/nsIWebAuthnService.idl135
-rw-r--r--dom/webauthn/tests/browser/browser.toml35
-rw-r--r--dom/webauthn/tests/browser/browser_abort_visibility.js277
-rw-r--r--dom/webauthn/tests/browser/browser_fido_appid_extension.js131
-rw-r--r--dom/webauthn/tests/browser/browser_webauthn_conditional_mediation.js177
-rw-r--r--dom/webauthn/tests/browser/browser_webauthn_ipaddress.js30
-rw-r--r--dom/webauthn/tests/browser/browser_webauthn_prompts.js501
-rw-r--r--dom/webauthn/tests/browser/head.js259
-rw-r--r--dom/webauthn/tests/browser/tab_webauthn_result.html14
-rw-r--r--dom/webauthn/tests/cbor.js406
-rw-r--r--dom/webauthn/tests/empty.html0
-rw-r--r--dom/webauthn/tests/get_assertion_dead_object.html21
-rw-r--r--dom/webauthn/tests/mochitest.toml118
-rw-r--r--dom/webauthn/tests/pkijs/LICENSE30
-rw-r--r--dom/webauthn/tests/pkijs/README1
-rw-r--r--dom/webauthn/tests/pkijs/asn1.js5466
-rw-r--r--dom/webauthn/tests/pkijs/common.js1542
-rw-r--r--dom/webauthn/tests/pkijs/x509_schema.js1889
-rw-r--r--dom/webauthn/tests/pkijs/x509_simpl.js7239
-rw-r--r--dom/webauthn/tests/test_webauthn_abort_signal.html146
-rw-r--r--dom/webauthn/tests/test_webauthn_attestation_conveyance.html103
-rw-r--r--dom/webauthn/tests/test_webauthn_authenticator_selection.html152
-rw-r--r--dom/webauthn/tests/test_webauthn_authenticator_transports.html165
-rw-r--r--dom/webauthn/tests/test_webauthn_crossorigin_featurepolicy.html258
-rw-r--r--dom/webauthn/tests/test_webauthn_ctap2_omitted_credential_id.html68
-rw-r--r--dom/webauthn/tests/test_webauthn_get_assertion.html212
-rw-r--r--dom/webauthn/tests/test_webauthn_get_assertion_dead_object.html34
-rw-r--r--dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html48
-rw-r--r--dom/webauthn/tests/test_webauthn_loopback.html179
-rw-r--r--dom/webauthn/tests/test_webauthn_make_credential.html459
-rw-r--r--dom/webauthn/tests/test_webauthn_no_token.html87
-rw-r--r--dom/webauthn/tests/test_webauthn_sameorigin.html317
-rw-r--r--dom/webauthn/tests/test_webauthn_sameoriginwithancestors.html108
-rw-r--r--dom/webauthn/tests/test_webauthn_serialization.html304
-rw-r--r--dom/webauthn/tests/test_webauthn_store_credential.html58
-rw-r--r--dom/webauthn/tests/test_webauthn_webdriver_virtual_authenticator.html58
-rw-r--r--dom/webauthn/tests/u2futil.js511
-rw-r--r--dom/webauthn/winwebauthn/.gitignore330
-rw-r--r--dom/webauthn/winwebauthn/LICENSE21
-rw-r--r--dom/webauthn/winwebauthn/README.md26
-rw-r--r--dom/webauthn/winwebauthn/webauthn.h1145
92 files changed, 33718 insertions, 0 deletions
diff --git a/dom/webauthn/AndroidWebAuthnService.cpp b/dom/webauthn/AndroidWebAuthnService.cpp
new file mode 100644
index 0000000000..162cc52033
--- /dev/null
+++ b/dom/webauthn/AndroidWebAuthnService.cpp
@@ -0,0 +1,428 @@
+/* -*- 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 "mozilla/StaticPtr.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/jni/GeckoBundleUtils.h"
+
+#include "AndroidWebAuthnService.h"
+#include "JavaBuiltins.h"
+#include "JavaExceptions.h"
+#include "WebAuthnPromiseHolder.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnResult.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/java/WebAuthnTokenManagerWrappers.h"
+#include "mozilla/jni/Conversions.h"
+
+namespace mozilla {
+namespace jni {
+template <>
+dom::AndroidWebAuthnError Java2Native(mozilla::jni::Object::Param aData,
+ JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Throwable>());
+ java::sdk::Throwable::LocalRef throwable(aData);
+ return dom::AndroidWebAuthnError(throwable->GetMessage()->ToString());
+}
+} // namespace jni
+
+namespace dom {
+
+NS_IMPL_ISUPPORTS(AndroidWebAuthnService, nsIWebAuthnService)
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetIsUVPAA(bool* aAvailable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::MakeCredential(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnRegisterArgs* aArgs,
+ nsIWebAuthnRegisterPromise* aPromise) {
+ Reset();
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnMakeCredential",
+ [aArgs = RefPtr{aArgs}, aPromise = RefPtr{aPromise}]() {
+ AssertIsOnMainThread();
+
+ GECKOBUNDLE_START(credentialBundle);
+ GECKOBUNDLE_PUT(credentialBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ GECKOBUNDLE_PUT(credentialBundle, "rpId", jni::StringParam(rpId));
+
+ nsString rpName;
+ Unused << aArgs->GetRpName(rpName);
+ GECKOBUNDLE_PUT(credentialBundle, "rpName", jni::StringParam(rpName));
+
+ nsString userName;
+ Unused << aArgs->GetUserName(userName);
+ GECKOBUNDLE_PUT(credentialBundle, "userName",
+ jni::StringParam(userName));
+
+ nsString userDisplayName;
+ Unused << aArgs->GetUserDisplayName(userDisplayName);
+ GECKOBUNDLE_PUT(credentialBundle, "userDisplayName",
+ jni::StringParam(userDisplayName));
+
+ nsString origin;
+ Unused << aArgs->GetOrigin(origin);
+ GECKOBUNDLE_PUT(credentialBundle, "origin", jni::StringParam(origin));
+
+ uint32_t timeout;
+ Unused << aArgs->GetTimeoutMS(&timeout);
+ GECKOBUNDLE_PUT(credentialBundle, "timeoutMS",
+ java::sdk::Double::New(timeout));
+ GECKOBUNDLE_FINISH(credentialBundle);
+
+ nsTArray<uint8_t> userId;
+ Unused << aArgs->GetUserId(userId);
+ jni::ByteBuffer::LocalRef uid = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(userId.Elements())),
+ userId.Length());
+
+ nsTArray<uint8_t> challBuf;
+ Unused << aArgs->GetChallenge(challBuf);
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ nsTArray<nsTArray<uint8_t>> excludeList;
+ Unused << aArgs->GetExcludeList(excludeList);
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(excludeList.Length());
+ int ix = 0;
+ for (const nsTArray<uint8_t>& credId : excludeList) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(credId.Elements())),
+ credId.Length());
+
+ idList->SetElement(ix, id);
+
+ ix += 1;
+ }
+
+ nsTArray<uint8_t> transportBuf;
+ Unused << aArgs->GetExcludeListTransports(transportBuf);
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ GECKOBUNDLE_START(authSelBundle);
+ // Add UI support to consent to attestation, bug 1550164
+ GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
+ jni::StringParam(u"none"_ns));
+
+ nsString residentKey;
+ Unused << aArgs->GetResidentKey(residentKey);
+
+ // Get extensions
+ bool requestedCredProps;
+ Unused << aArgs->GetCredProps(&requestedCredProps);
+
+ // Unfortunately, GMS's FIDO2 API has no option for Passkey. If using
+ // residentKey, credential will be synced with Passkey via Google
+ // account or credential provider service. So this is experimental.
+ Maybe<bool> credPropsResponse;
+ if (requestedCredProps &&
+ StaticPrefs::
+ security_webauthn_webauthn_enable_android_fido2_residentkey()) {
+ GECKOBUNDLE_PUT(authSelBundle, "residentKey",
+ jni::StringParam(residentKey));
+ bool residentKeyRequired = residentKey.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED);
+ credPropsResponse = Some(residentKeyRequired);
+ }
+
+ nsString userVerification;
+ Unused << aArgs->GetUserVerification(userVerification);
+ if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requireUserVerification",
+ java::sdk::Integer::ValueOf(1));
+ }
+
+ nsString authenticatorAttachment;
+ nsresult rv =
+ aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(rv);
+ return;
+ }
+ if (authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requirePlatformAttachment",
+ java::sdk::Integer::ValueOf(1));
+ } else if (
+ authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requireCrossPlatformAttachment",
+ java::sdk::Integer::ValueOf(1));
+ }
+ }
+ GECKOBUNDLE_FINISH(authSelBundle);
+
+ GECKOBUNDLE_START(extensionsBundle);
+ GECKOBUNDLE_FINISH(extensionsBundle);
+
+ auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
+ credentialBundle, uid, challenge, idList, transportList,
+ authSelBundle, extensionsBundle);
+
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+
+ MozPromise<RefPtr<WebAuthnRegisterResult>, AndroidWebAuthnError,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aPromise, credPropsResponse = std::move(credPropsResponse)](
+ RefPtr<WebAuthnRegisterResult>&& aValue) {
+ // We don't have a way for the user to consent to attestation
+ // on Android, so always anonymize the result.
+ nsresult rv = aValue->Anonymize();
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+ if (credPropsResponse.isSome()) {
+ Unused << aValue->SetCredPropsRk(credPropsResponse.ref());
+ }
+ aPromise->Resolve(aValue);
+ },
+ [aPromise](AndroidWebAuthnError&& aValue) {
+ aPromise->Reject(aValue.GetError());
+ });
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetAssertion(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnSignArgs* aArgs,
+ nsIWebAuthnSignPromise* aPromise) {
+ Reset();
+
+ bool conditionallyMediated;
+ Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
+ if (conditionallyMediated) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return NS_OK;
+ }
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnGetAssertion",
+ [self = RefPtr{this}, aArgs = RefPtr{aArgs},
+ aPromise = RefPtr{aPromise}]() {
+ AssertIsOnMainThread();
+
+ nsTArray<uint8_t> challBuf;
+ Unused << aArgs->GetChallenge(challBuf);
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ nsTArray<nsTArray<uint8_t>> allowList;
+ Unused << aArgs->GetAllowList(allowList);
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(allowList.Length());
+ int ix = 0;
+ for (const nsTArray<uint8_t>& credId : allowList) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(credId.Elements())),
+ credId.Length());
+
+ idList->SetElement(ix, id);
+
+ ix += 1;
+ }
+
+ nsTArray<uint8_t> transportBuf;
+ Unused << aArgs->GetAllowListTransports(transportBuf);
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ GECKOBUNDLE_START(assertionBundle);
+
+ GECKOBUNDLE_PUT(assertionBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ GECKOBUNDLE_PUT(assertionBundle, "rpId", jni::StringParam(rpId));
+
+ nsString origin;
+ Unused << aArgs->GetOrigin(origin);
+ GECKOBUNDLE_PUT(assertionBundle, "origin", jni::StringParam(origin));
+
+ uint32_t timeout;
+ Unused << aArgs->GetTimeoutMS(&timeout);
+ GECKOBUNDLE_PUT(assertionBundle, "timeoutMS",
+ java::sdk::Double::New(timeout));
+
+ // User Verification Requirement is not currently used in the
+ // Android FIDO API.
+
+ GECKOBUNDLE_FINISH(assertionBundle);
+
+ GECKOBUNDLE_START(extensionsBundle);
+
+ nsString appId;
+ nsresult rv = aArgs->GetAppId(appId);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ GECKOBUNDLE_PUT(extensionsBundle, "fidoAppId",
+ jni::StringParam(appId));
+ }
+
+ GECKOBUNDLE_FINISH(extensionsBundle);
+
+ auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
+ challenge, idList, transportList, assertionBundle,
+ extensionsBundle);
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+ MozPromise<RefPtr<WebAuthnSignResult>, AndroidWebAuthnError,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aPromise](RefPtr<WebAuthnSignResult>&& aValue) {
+ aPromise->Resolve(aValue);
+ },
+ [aPromise](AndroidWebAuthnError&& aValue) {
+ aPromise->Reject(aValue.GetError());
+ });
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Reset() {
+ mRegisterCredPropsRk.reset();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Cancel(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
+ const nsAString& aOrigin,
+ uint64_t* aRv) {
+ // Signal that there is no pending conditional get request, so the caller
+ // will not attempt to call GetAutoFillEntries, SelectAutoFillEntry, or
+ // ResumeConditionalGet (as these are not implemented).
+ *aRv = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetAutoFillEntries(
+ uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SelectAutoFillEntry(
+ uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::PinCallback(uint64_t aTransactionId,
+ const nsACString& aPin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
+ bool aForceNoneAttestation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SelectionCallback(uint64_t aTransactionId,
+ uint64_t aIndex) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::AddVirtualAuthenticator(
+ const nsACString& protocol, const nsACString& transport,
+ bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
+ bool isUserVerified, uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::AddCredential(uint64_t authenticatorId,
+ const nsACString& credentialId,
+ bool isResidentCredential,
+ const nsACString& rpId,
+ const nsACString& privateKey,
+ const nsACString& userHandle,
+ uint32_t signCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetCredentials(
+ uint64_t authenticatorId,
+ nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveCredential(uint64_t authenticatorId,
+ const nsACString& credentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SetUserVerified(uint64_t authenticatorId,
+ bool isUserVerified) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RunCommand(const nsACString& cmd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/webauthn/AndroidWebAuthnService.h b/dom/webauthn/AndroidWebAuthnService.h
new file mode 100644
index 0000000000..ab48be4867
--- /dev/null
+++ b/dom/webauthn/AndroidWebAuthnService.h
@@ -0,0 +1,93 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_AndroidWebAuthnService_h_
+#define mozilla_dom_AndroidWebAuthnService_h_
+
+#include "mozilla/java/WebAuthnTokenManagerNatives.h"
+#include "nsIWebAuthnService.h"
+
+namespace mozilla {
+
+namespace dom {
+
+// Collected from
+// https://developers.google.com/android/reference/com/google/android/gms/fido/fido2/api/common/ErrorCode
+constexpr auto kSecurityError = u"SECURITY_ERR"_ns;
+constexpr auto kConstraintError = u"CONSTRAINT_ERR"_ns;
+constexpr auto kNotSupportedError = u"NOT_SUPPORTED_ERR"_ns;
+constexpr auto kInvalidStateError = u"INVALID_STATE_ERR"_ns;
+constexpr auto kNotAllowedError = u"NOT_ALLOWED_ERR"_ns;
+constexpr auto kAbortError = u"ABORT_ERR"_ns;
+constexpr auto kEncodingError = u"ENCODING_ERR"_ns;
+constexpr auto kDataError = u"DATA_ERR"_ns;
+constexpr auto kTimeoutError = u"TIMEOUT_ERR"_ns;
+constexpr auto kNetworkError = u"NETWORK_ERR"_ns;
+constexpr auto kUnknownError = u"UNKNOWN_ERR"_ns;
+
+class AndroidWebAuthnError {
+ public:
+ explicit AndroidWebAuthnError(const nsAString& aErrorCode)
+ : mErrorCode(aErrorCode) {}
+
+ nsresult GetError() const {
+ if (mErrorCode.Equals(kSecurityError)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ } else if (mErrorCode.Equals(kConstraintError)) {
+ // TODO: The message is right, but it's not about indexeddb.
+ // See https://heycam.github.io/webidl/#constrainterror
+ return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+ } else if (mErrorCode.Equals(kNotSupportedError)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ } else if (mErrorCode.Equals(kInvalidStateError)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (mErrorCode.Equals(kNotAllowedError)) {
+ return NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ } else if (mErrorCode.Equals(kEncodingError)) {
+ return NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR;
+ } else if (mErrorCode.Equals(kDataError)) {
+ return NS_ERROR_DOM_DATA_ERR;
+ } else if (mErrorCode.Equals(kTimeoutError)) {
+ return NS_ERROR_DOM_TIMEOUT_ERR;
+ } else if (mErrorCode.Equals(kNetworkError)) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ } else if (mErrorCode.Equals(kAbortError)) {
+ return NS_ERROR_DOM_ABORT_ERR;
+ } else if (mErrorCode.Equals(kUnknownError)) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ } else {
+ __android_log_print(ANDROID_LOG_ERROR, "Gecko",
+ "RegisterAbort unknown code: %s",
+ NS_ConvertUTF16toUTF8(mErrorCode).get());
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ }
+
+ private:
+ const nsString mErrorCode;
+};
+
+class AndroidWebAuthnService final : public nsIWebAuthnService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSERVICE
+
+ AndroidWebAuthnService() = default;
+
+ private:
+ ~AndroidWebAuthnService() = default;
+
+ // The Android FIDO2 API doesn't accept the credProps extension. However, the
+ // appropriate value for CredentialPropertiesOutput.rk can be determined
+ // entirely from the input, so we cache it here until mRegisterPromise
+ // resolves.
+ Maybe<bool> mRegisterCredPropsRk;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AndroidWebAuthnService_h_
diff --git a/dom/webauthn/AuthenticatorAssertionResponse.cpp b/dom/webauthn/AuthenticatorAssertionResponse.cpp
new file mode 100644
index 0000000000..9824edc30b
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp
@@ -0,0 +1,165 @@
+/* -*- 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 "mozilla/Base64.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/AuthenticatorAssertionResponse.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorAssertionResponse)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AuthenticatorAssertionResponse,
+ AuthenticatorResponse)
+ tmp->mAuthenticatorDataCachedObj = nullptr;
+ tmp->mSignatureCachedObj = nullptr;
+ tmp->mUserHandleCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AuthenticatorAssertionResponse,
+ AuthenticatorResponse)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAuthenticatorDataCachedObj)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mSignatureCachedObj)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mUserHandleCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ AuthenticatorAssertionResponse, AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
+NS_IMPL_RELEASE_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorAssertionResponse)
+NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
+
+AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(
+ nsPIDOMWindowInner* aParent)
+ : AuthenticatorResponse(aParent),
+ mAuthenticatorDataCachedObj(nullptr),
+ mSignatureCachedObj(nullptr),
+ mUserHandleCachedObj(nullptr) {
+ mozilla::HoldJSObjects(this);
+}
+
+AuthenticatorAssertionResponse::~AuthenticatorAssertionResponse() {
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* AuthenticatorAssertionResponse::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return AuthenticatorAssertionResponse_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AuthenticatorAssertionResponse::GetAuthenticatorData(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ if (!mAuthenticatorDataCachedObj) {
+ mAuthenticatorDataCachedObj =
+ ArrayBuffer::Create(aCx, mAuthenticatorData, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mAuthenticatorDataCachedObj);
+}
+
+void AuthenticatorAssertionResponse::SetAuthenticatorData(
+ const nsTArray<uint8_t>& aBuffer) {
+ mAuthenticatorData.Assign(aBuffer);
+}
+
+void AuthenticatorAssertionResponse::GetSignature(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ if (!mSignatureCachedObj) {
+ mSignatureCachedObj = ArrayBuffer::Create(aCx, mSignature, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mSignatureCachedObj);
+}
+
+void AuthenticatorAssertionResponse::SetSignature(
+ const nsTArray<uint8_t>& aBuffer) {
+ mSignature.Assign(aBuffer);
+}
+
+void AuthenticatorAssertionResponse::GetUserHandle(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ // Per
+ // https://w3c.github.io/webauthn/#ref-for-dom-authenticatorassertionresponse-userhandle%E2%91%A0
+ // this should return null if the handle is unset.
+ if (mUserHandle.IsEmpty()) {
+ aValue.set(nullptr);
+ } else {
+ if (!mUserHandleCachedObj) {
+ mUserHandleCachedObj = ArrayBuffer::Create(aCx, mUserHandle, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mUserHandleCachedObj);
+ }
+}
+
+void AuthenticatorAssertionResponse::SetUserHandle(
+ const nsTArray<uint8_t>& 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
diff --git a/dom/webauthn/AuthenticatorAssertionResponse.h b/dom/webauthn/AuthenticatorAssertionResponse.h
new file mode 100644
index 0000000000..75982800d1
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAssertionResponse.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_AuthenticatorAssertionResponse_h
+#define mozilla_dom_AuthenticatorAssertionResponse_h
+
+#include "js/TypeDecls.h"
+#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"
+
+namespace mozilla::dom {
+
+class AuthenticatorAssertionResponse final : public AuthenticatorResponse {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ AuthenticatorAssertionResponse, AuthenticatorResponse)
+
+ explicit AuthenticatorAssertionResponse(nsPIDOMWindowInner* aParent);
+
+ protected:
+ ~AuthenticatorAssertionResponse() override;
+
+ public:
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void SetAuthenticatorData(const nsTArray<uint8_t>& aBuffer);
+
+ void GetSignature(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void SetSignature(const nsTArray<uint8_t>& aBuffer);
+
+ void GetUserHandle(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void SetUserHandle(const nsTArray<uint8_t>& aBuffer);
+
+ void ToJSON(AuthenticatorAssertionResponseJSON& aJSON, ErrorResult& aError);
+
+ private:
+ nsTArray<uint8_t> mAuthenticatorData;
+ JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
+ nsTArray<uint8_t> mSignature;
+ JS::Heap<JSObject*> mSignatureCachedObj;
+ nsTArray<uint8_t> mUserHandle;
+ JS::Heap<JSObject*> mUserHandleCachedObj;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AuthenticatorAssertionResponse_h
diff --git a/dom/webauthn/AuthenticatorAttestationResponse.cpp b/dom/webauthn/AuthenticatorAttestationResponse.cpp
new file mode 100644
index 0000000000..7fe493dae3
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAttestationResponse.cpp
@@ -0,0 +1,241 @@
+/* -*- 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 "AuthrsBridge_ffi.h"
+#include "mozilla/Base64.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/AuthenticatorAttestationResponse.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorAttestationResponse)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
+ AuthenticatorAttestationResponse, AuthenticatorResponse)
+ tmp->mAttestationObjectCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(AuthenticatorAttestationResponse,
+ AuthenticatorResponse)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAttestationObjectCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ AuthenticatorAttestationResponse, AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(AuthenticatorAttestationResponse,
+ AuthenticatorResponse)
+NS_IMPL_RELEASE_INHERITED(AuthenticatorAttestationResponse,
+ AuthenticatorResponse)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorAttestationResponse)
+NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
+
+AuthenticatorAttestationResponse::AuthenticatorAttestationResponse(
+ nsPIDOMWindowInner* aParent)
+ : AuthenticatorResponse(aParent), mAttestationObjectCachedObj(nullptr) {
+ mozilla::HoldJSObjects(this);
+}
+
+AuthenticatorAttestationResponse::~AuthenticatorAttestationResponse() {
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* AuthenticatorAttestationResponse::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return AuthenticatorAttestationResponse_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AuthenticatorAttestationResponse::GetAttestationObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ if (!mAttestationObjectCachedObj) {
+ mAttestationObjectCachedObj =
+ ArrayBuffer::Create(aCx, mAttestationObject, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mAttestationObjectCachedObj);
+}
+
+void AuthenticatorAttestationResponse::SetAttestationObject(
+ const nsTArray<uint8_t>& aBuffer) {
+ mAttestationObject.Assign(aBuffer);
+}
+
+void AuthenticatorAttestationResponse::GetTransports(
+ nsTArray<nsString>& aTransports) {
+ aTransports.Assign(mTransports);
+}
+
+void AuthenticatorAttestationResponse::SetTransports(
+ const nsTArray<nsString>& aTransports) {
+ mTransports.Assign(aTransports);
+}
+
+nsresult AuthenticatorAttestationResponse::GetAuthenticatorDataBytes(
+ nsTArray<uint8_t>& aAuthenticatorData) {
+ if (!mAttestationObjectParsed) {
+ nsresult rv = authrs_webauthn_att_obj_constructor(
+ mAttestationObject, /* anonymize */ false,
+ getter_AddRefs(mAttestationObjectParsed));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ nsresult rv =
+ mAttestationObjectParsed->GetAuthenticatorData(aAuthenticatorData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void AuthenticatorAttestationResponse::GetAuthenticatorData(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ nsTArray<uint8_t> authenticatorData;
+ nsresult rv = GetAuthenticatorDataBytes(authenticatorData);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ JS::Heap<JSObject*> buffer(ArrayBuffer::Create(aCx, authenticatorData, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+ aValue.set(buffer);
+}
+
+nsresult AuthenticatorAttestationResponse::GetPublicKeyBytes(
+ nsTArray<uint8_t>& aPublicKeyBytes) {
+ if (!mAttestationObjectParsed) {
+ nsresult rv = authrs_webauthn_att_obj_constructor(
+ mAttestationObject, /* anonymize */ false,
+ getter_AddRefs(mAttestationObjectParsed));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ nsresult rv = mAttestationObjectParsed->GetPublicKey(aPublicKeyBytes);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void AuthenticatorAttestationResponse::GetPublicKey(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ nsTArray<uint8_t> publicKey;
+ nsresult rv = GetPublicKeyBytes(publicKey);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ aValue.set(nullptr);
+ } else {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+ return;
+ }
+
+ JS::Heap<JSObject*> buffer(ArrayBuffer::Create(aCx, publicKey, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+ aValue.set(buffer);
+}
+
+COSEAlgorithmIdentifier AuthenticatorAttestationResponse::GetPublicKeyAlgorithm(
+ ErrorResult& aRv) {
+ if (!mAttestationObjectParsed) {
+ nsresult rv = authrs_webauthn_att_obj_constructor(
+ mAttestationObject, false, getter_AddRefs(mAttestationObjectParsed));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return 0;
+ }
+ }
+
+ COSEAlgorithmIdentifier alg;
+ mAttestationObjectParsed->GetPublicKeyAlgorithm(&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
diff --git a/dom/webauthn/AuthenticatorAttestationResponse.h b/dom/webauthn/AuthenticatorAttestationResponse.h
new file mode 100644
index 0000000000..04b28be397
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAttestationResponse.h
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_AuthenticatorAttestationResponse_h
+#define mozilla_dom_AuthenticatorAttestationResponse_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWebAuthnAttObj.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class AuthenticatorAttestationResponse final : public AuthenticatorResponse {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ AuthenticatorAttestationResponse, AuthenticatorResponse)
+
+ explicit AuthenticatorAttestationResponse(nsPIDOMWindowInner* aParent);
+
+ protected:
+ ~AuthenticatorAttestationResponse() override;
+
+ public:
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetAttestationObject(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void SetAttestationObject(const nsTArray<uint8_t>& aBuffer);
+
+ void GetTransports(nsTArray<nsString>& aTransports);
+
+ void SetTransports(const nsTArray<nsString>& aTransports);
+
+ void GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void GetPublicKey(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ COSEAlgorithmIdentifier GetPublicKeyAlgorithm(ErrorResult& aRv);
+
+ void ToJSON(AuthenticatorAttestationResponseJSON& aJSON, ErrorResult& aError);
+
+ private:
+ nsresult GetAuthenticatorDataBytes(nsTArray<uint8_t>& aAuthenticatorData);
+ nsresult GetPublicKeyBytes(nsTArray<uint8_t>& aPublicKeyBytes);
+
+ nsTArray<uint8_t> mAttestationObject;
+ nsCOMPtr<nsIWebAuthnAttObj> mAttestationObjectParsed;
+ JS::Heap<JSObject*> mAttestationObjectCachedObj;
+ nsTArray<nsString> mTransports;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AuthenticatorAttestationResponse_h
diff --git a/dom/webauthn/AuthenticatorResponse.cpp b/dom/webauthn/AuthenticatorResponse.cpp
new file mode 100644
index 0000000000..f0c3888fa8
--- /dev/null
+++ b/dom/webauthn/AuthenticatorResponse.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "mozilla/Base64.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
+ AuthenticatorResponse, (mParent), (mClientDataJSONCachedObj))
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AuthenticatorResponse)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorResponse)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AuthenticatorResponse::AuthenticatorResponse(nsPIDOMWindowInner* aParent)
+ : mParent(aParent), mClientDataJSONCachedObj(nullptr) {
+ // Call HoldJSObjects() in subclasses.
+}
+
+AuthenticatorResponse::~AuthenticatorResponse() {
+ // Call DropJSObjects() in subclasses.
+}
+
+nsISupports* AuthenticatorResponse::GetParentObject() const { return mParent; }
+
+void AuthenticatorResponse::GetClientDataJSON(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv) {
+ if (!mClientDataJSONCachedObj) {
+ mClientDataJSONCachedObj = ArrayBuffer::Create(aCx, mClientDataJSON, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mClientDataJSONCachedObj);
+}
+
+void AuthenticatorResponse::SetClientDataJSON(const nsCString& aBuffer) {
+ mClientDataJSON.Assign(aBuffer);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/AuthenticatorResponse.h b/dom/webauthn/AuthenticatorResponse.h
new file mode 100644
index 0000000000..b0abb3185c
--- /dev/null
+++ b/dom/webauthn/AuthenticatorResponse.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_AuthenticatorResponse_h
+#define mozilla_dom_AuthenticatorResponse_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+class AuthenticatorResponse : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AuthenticatorResponse)
+
+ explicit AuthenticatorResponse(nsPIDOMWindowInner* aParent);
+
+ protected:
+ virtual ~AuthenticatorResponse();
+
+ public:
+ nsISupports* GetParentObject() const;
+
+ void GetFormat(nsString& aRetVal) const;
+
+ void GetClientDataJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void SetClientDataJSON(const nsCString& aBuffer);
+
+ protected:
+ nsCString mClientDataJSON;
+
+ private:
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ JS::Heap<JSObject*> mClientDataJSONCachedObj;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AuthenticatorResponse_h
diff --git a/dom/webauthn/AuthrsBridge_ffi.h b/dom/webauthn/AuthrsBridge_ffi.h
new file mode 100644
index 0000000000..72ca7f31ec
--- /dev/null
+++ b/dom/webauthn/AuthrsBridge_ffi.h
@@ -0,0 +1,25 @@
+/* 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/. */
+
+#ifndef mozilla_dom_AuthrsBridge_ffi_h
+#define mozilla_dom_AuthrsBridge_ffi_h
+
+// TODO(Bug 1850592): Autogenerate this with cbindgen.
+
+#include "nsTArray.h"
+
+class nsIWebAuthnAttObj;
+class nsIWebAuthnService;
+
+extern "C" {
+
+nsresult authrs_service_constructor(nsIWebAuthnService** result);
+
+nsresult authrs_webauthn_att_obj_constructor(
+ const nsTArray<uint8_t>& attestation, bool anonymize,
+ nsIWebAuthnAttObj** result);
+
+} // extern "C"
+
+#endif // mozilla_dom_AuthrsBridge_ffi_h
diff --git a/dom/webauthn/MacOSWebAuthnService.h b/dom/webauthn/MacOSWebAuthnService.h
new file mode 100644
index 0000000000..08c912027e
--- /dev/null
+++ b/dom/webauthn/MacOSWebAuthnService.h
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_MacOSWebAuthnService_h
+#define mozilla_dom_MacOSWebAuthnService_h
+
+#include <os/availability.h>
+
+#include "nsIWebAuthnService.h"
+
+namespace mozilla::dom {
+
+API_AVAILABLE(macos(13.3))
+already_AddRefed<nsIWebAuthnService> NewMacOSWebAuthnServiceIfAvailable();
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_MacOSWebAuthnService_h
diff --git a/dom/webauthn/MacOSWebAuthnService.mm b/dom/webauthn/MacOSWebAuthnService.mm
new file mode 100644
index 0000000000..79b9030541
--- /dev/null
+++ b/dom/webauthn/MacOSWebAuthnService.mm
@@ -0,0 +1,1166 @@
+/* -*- 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/. */
+
+#import <AuthenticationServices/AuthenticationServices.h>
+
+#include "MacOSWebAuthnService.h"
+
+#include "CFTypeRefPtr.h"
+#include "WebAuthnAutoFillEntry.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnResult.h"
+#include "WebAuthnTransportIdentifiers.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsCocoaUtils.h"
+#include "nsIWebAuthnPromise.h"
+#include "nsThreadUtils.h"
+
+// The documentation for the platform APIs used here can be found at:
+// https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys
+
+namespace {
+static mozilla::LazyLogModule gMacOSWebAuthnServiceLog("macoswebauthnservice");
+} // namespace
+
+namespace mozilla::dom {
+class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService;
+} // namespace mozilla::dom
+
+// The following ASC* declarations are from the private framework
+// AuthenticationServicesCore. The full definitions can be found in WebKit's
+// source at Source/WebKit/Platform/spi/Cocoa/AuthenticationServicesCoreSPI.h.
+// Overriding ASAuthorizationController's _requestContextWithRequests is
+// currently the only way to provide the right information to the macOS
+// WebAuthn API (namely, the clientDataHash for requests made to physical
+// tokens).
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ASCPublicKeyCredentialDescriptor;
+@interface ASCPublicKeyCredentialDescriptor : NSObject <NSSecureCoding>
+- (instancetype)initWithCredentialID:(NSData*)credentialID
+ transports:
+ (nullable NSArray<NSString*>*)allowedTransports;
+@end
+
+@protocol ASCPublicKeyCredentialCreationOptions
+@property(nonatomic, copy) NSData* clientDataHash;
+@property(nonatomic, nullable, copy) NSData* challenge;
+@property(nonatomic, copy)
+ NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials;
+@end
+
+@protocol ASCPublicKeyCredentialAssertionOptions <NSCopying>
+@property(nonatomic, copy) NSData* clientDataHash;
+@end
+
+@protocol ASCCredentialRequestContext
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
+ platformKeyCredentialCreationOptions;
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialCreationOptions>
+ securityKeyCredentialCreationOptions;
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
+ platformKeyCredentialAssertionOptions;
+@property(nonatomic, nullable, copy) id<ASCPublicKeyCredentialAssertionOptions>
+ securityKeyCredentialAssertionOptions;
+@end
+
+@interface ASAuthorizationController (Secrets)
+- (id<ASCCredentialRequestContext>)
+ _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
+ error:(NSError**)outError;
+@end
+
+NSArray<NSString*>* TransportsByteToTransportsArray(const uint8_t aTransports)
+ API_AVAILABLE(macos(13.3)) {
+ NSMutableArray<NSString*>* transportsNS = [[NSMutableArray alloc] init];
+ if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) ==
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
+ [transportsNS
+ addObject:
+ ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportUSB];
+ }
+ if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) ==
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
+ [transportsNS
+ addObject:
+ ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportNFC];
+ }
+ if ((aTransports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) ==
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
+ [transportsNS
+ addObject:
+ ASAuthorizationSecurityKeyPublicKeyCredentialDescriptorTransportBluetooth];
+ }
+ // TODO (bug 1859367): the platform doesn't have a definition for "internal"
+ // transport. When it does, this code should be updated to handle it.
+ return transportsNS;
+}
+
+NSArray* CredentialListsToCredentialDescriptorArray(
+ const nsTArray<nsTArray<uint8_t>>& aCredentialList,
+ const nsTArray<uint8_t>& aCredentialListTransports,
+ const Class credentialDescriptorClass) API_AVAILABLE(macos(13.3)) {
+ MOZ_ASSERT(aCredentialList.Length() == aCredentialListTransports.Length());
+ NSMutableArray* credentials = [[NSMutableArray alloc] init];
+ for (size_t i = 0; i < aCredentialList.Length(); i++) {
+ const nsTArray<uint8_t>& credentialId = aCredentialList[i];
+ const uint8_t& credentialTransports = aCredentialListTransports[i];
+ NSData* credentialIdNS = [NSData dataWithBytes:credentialId.Elements()
+ length:credentialId.Length()];
+ NSArray<NSString*>* credentialTransportsNS =
+ TransportsByteToTransportsArray(credentialTransports);
+ NSObject* credential = [[credentialDescriptorClass alloc]
+ initWithCredentialID:credentialIdNS
+ transports:credentialTransportsNS];
+ [credentials addObject:credential];
+ }
+ return credentials;
+}
+
+// MacOSAuthorizationController is an ASAuthorizationController that overrides
+// _requestContextWithRequests so that the implementation can set some options
+// that aren't directly settable using the public API.
+API_AVAILABLE(macos(13.3))
+@interface MacOSAuthorizationController : ASAuthorizationController
+@end
+
+@implementation MacOSAuthorizationController {
+ nsTArray<uint8_t> mClientDataHash;
+ nsTArray<nsTArray<uint8_t>> mCredentialList;
+ nsTArray<uint8_t> mCredentialListTransports;
+}
+
+- (void)setRegistrationOptions:
+ (id<ASCPublicKeyCredentialCreationOptions>)registrationOptions {
+ registrationOptions.clientDataHash =
+ [NSData dataWithBytes:mClientDataHash.Elements()
+ length:mClientDataHash.Length()];
+ // Unset challenge so that the implementation uses clientDataHash (the API
+ // returns an error otherwise).
+ registrationOptions.challenge = nil;
+ const Class publicKeyCredentialDescriptorClass =
+ NSClassFromString(@"ASCPublicKeyCredentialDescriptor");
+ NSArray<ASCPublicKeyCredentialDescriptor*>* excludedCredentials =
+ CredentialListsToCredentialDescriptorArray(
+ mCredentialList, mCredentialListTransports,
+ publicKeyCredentialDescriptorClass);
+ if ([excludedCredentials count] > 0) {
+ registrationOptions.excludedCredentials = excludedCredentials;
+ }
+}
+
+- (void)stashClientDataHash:(nsTArray<uint8_t>&&)clientDataHash
+ andCredentialList:(nsTArray<nsTArray<uint8_t>>&&)credentialList
+ andCredentialListTransports:(nsTArray<uint8_t>&&)credentialListTransports {
+ mClientDataHash = std::move(clientDataHash);
+ mCredentialList = std::move(credentialList);
+ mCredentialListTransports = std::move(credentialListTransports);
+}
+
+- (id<ASCCredentialRequestContext>)
+ _requestContextWithRequests:(NSArray<ASAuthorizationRequest*>*)requests
+ error:(NSError**)outError {
+ id<ASCCredentialRequestContext> context =
+ [super _requestContextWithRequests:requests error:outError];
+
+ if (context.platformKeyCredentialCreationOptions) {
+ [self setRegistrationOptions:context.platformKeyCredentialCreationOptions];
+ }
+ if (context.securityKeyCredentialCreationOptions) {
+ [self setRegistrationOptions:context.securityKeyCredentialCreationOptions];
+ }
+
+ if (context.platformKeyCredentialAssertionOptions) {
+ id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
+ context.platformKeyCredentialAssertionOptions;
+ assertionOptions.clientDataHash =
+ [NSData dataWithBytes:mClientDataHash.Elements()
+ length:mClientDataHash.Length()];
+ context.platformKeyCredentialAssertionOptions =
+ [assertionOptions copyWithZone:nil];
+ }
+ if (context.securityKeyCredentialAssertionOptions) {
+ id<ASCPublicKeyCredentialAssertionOptions> assertionOptions =
+ context.securityKeyCredentialAssertionOptions;
+ assertionOptions.clientDataHash =
+ [NSData dataWithBytes:mClientDataHash.Elements()
+ length:mClientDataHash.Length()];
+ context.securityKeyCredentialAssertionOptions =
+ [assertionOptions copyWithZone:nil];
+ }
+
+ return context;
+}
+@end
+
+// MacOSAuthenticatorRequestDelegate is an ASAuthorizationControllerDelegate,
+// which can be set as an ASAuthorizationController's delegate to be called
+// back when a request for a platform authenticator attestation or assertion
+// response completes either with an attestation or assertion
+// (didCompleteWithAuthorization) or with an error (didCompleteWithError).
+API_AVAILABLE(macos(13.3))
+@interface MacOSAuthenticatorRequestDelegate
+ : NSObject <ASAuthorizationControllerDelegate>
+- (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback;
+@end
+
+// MacOSAuthenticatorPresentationContextProvider is an
+// ASAuthorizationControllerPresentationContextProviding, which can be set as
+// an ASAuthorizationController's presentationContextProvider, and provides a
+// presentation anchor for the ASAuthorizationController. Basically, this
+// provides the API a handle to the window that made the request.
+API_AVAILABLE(macos(13.3))
+@interface MacOSAuthenticatorPresentationContextProvider
+ : NSObject <ASAuthorizationControllerPresentationContextProviding>
+@property(nonatomic, strong) NSWindow* window;
+@end
+
+namespace mozilla::dom {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+class API_AVAILABLE(macos(13.3)) MacOSWebAuthnService final
+ : public nsIWebAuthnService {
+ public:
+ MacOSWebAuthnService()
+ : mTransactionState(Nothing(),
+ "MacOSWebAuthnService::mTransactionState") {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSERVICE
+
+ void FinishMakeCredential(const nsTArray<uint8_t>& aRawAttestationObject,
+ const nsTArray<uint8_t>& aCredentialId,
+ const nsTArray<nsString>& aTransports,
+ const Maybe<nsString>& aAuthenticatorAttachment);
+
+ void FinishGetAssertion(const nsTArray<uint8_t>& aCredentialId,
+ const nsTArray<uint8_t>& aSignature,
+ const nsTArray<uint8_t>& aAuthenticatorData,
+ const nsTArray<uint8_t>& aUserHandle,
+ const Maybe<nsString>& aAuthenticatorAttachment);
+ void ReleasePlatformResources();
+ void AbortTransaction(nsresult aError);
+
+ private:
+ ~MacOSWebAuthnService() = default;
+
+ void PerformRequests(NSArray<ASAuthorizationRequest*>* aRequests,
+ nsTArray<uint8_t>&& aClientDataHash,
+ nsTArray<nsTArray<uint8_t>>&& aCredentialList,
+ nsTArray<uint8_t>&& aCredentialListTransports,
+ uint64_t aBrowsingContextId);
+
+ struct TransactionState {
+ uint64_t transactionId;
+ uint64_t browsingContextId;
+ Maybe<RefPtr<nsIWebAuthnSignArgs>> pendingSignArgs;
+ Maybe<RefPtr<nsIWebAuthnSignPromise>> pendingSignPromise;
+ Maybe<nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>> autoFillEntries;
+ };
+
+ using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
+ void DoGetAssertion(Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
+ const TransactionStateMutex::AutoLock& aGuard);
+
+ TransactionStateMutex mTransactionState;
+
+ // Main thread only:
+ ASAuthorizationWebBrowserPublicKeyCredentialManager* mCredentialManager = nil;
+ nsCOMPtr<nsIWebAuthnRegisterPromise> mRegisterPromise;
+ nsCOMPtr<nsIWebAuthnSignPromise> mSignPromise;
+ MacOSAuthorizationController* mAuthorizationController = nil;
+ MacOSAuthenticatorRequestDelegate* mRequestDelegate = nil;
+ MacOSAuthenticatorPresentationContextProvider* mPresentationContextProvider =
+ nil;
+};
+#pragma clang diagnostic pop
+
+} // namespace mozilla::dom
+
+nsTArray<uint8_t> NSDataToArray(NSData* data) {
+ nsTArray<uint8_t> array(reinterpret_cast<const uint8_t*>(data.bytes),
+ data.length);
+ return array;
+}
+
+@implementation MacOSAuthenticatorRequestDelegate {
+ RefPtr<mozilla::dom::MacOSWebAuthnService> mCallback;
+}
+
+- (void)setCallback:(mozilla::dom::MacOSWebAuthnService*)callback {
+ mCallback = callback;
+}
+
+- (void)authorizationController:(ASAuthorizationController*)controller
+ didCompleteWithAuthorization:(ASAuthorization*)authorization {
+ if ([authorization.credential
+ conformsToProtocol:
+ @protocol(ASAuthorizationPublicKeyCredentialRegistration)]) {
+ MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
+ ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
+ "got registration"));
+ id<ASAuthorizationPublicKeyCredentialRegistration> credential =
+ (id<ASAuthorizationPublicKeyCredentialRegistration>)
+ authorization.credential;
+ nsTArray<uint8_t> rawAttestationObject(
+ NSDataToArray(credential.rawAttestationObject));
+ nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
+ nsTArray<nsString> transports;
+ mozilla::Maybe<nsString> authenticatorAttachment;
+ if ([credential isKindOfClass:
+ [ASAuthorizationPlatformPublicKeyCredentialRegistration
+ class]]) {
+ transports.AppendElement(u"internal"_ns);
+#if defined(MAC_OS_VERSION_13_5) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
+ if (__builtin_available(macos 13.5, *)) {
+ ASAuthorizationPlatformPublicKeyCredentialRegistration*
+ platformCredential =
+ (ASAuthorizationPlatformPublicKeyCredentialRegistration*)
+ credential;
+ switch (platformCredential.attachment) {
+ case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
+ authenticatorAttachment.emplace(u"cross-platform"_ns);
+ break;
+ case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
+ authenticatorAttachment.emplace(u"platform"_ns);
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ } else {
+ authenticatorAttachment.emplace(u"cross-platform"_ns);
+ }
+ mCallback->FinishMakeCredential(rawAttestationObject, credentialId,
+ transports, authenticatorAttachment);
+ } else if ([authorization.credential
+ conformsToProtocol:
+ @protocol(ASAuthorizationPublicKeyCredentialAssertion)]) {
+ MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Debug,
+ ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
+ "got assertion"));
+ id<ASAuthorizationPublicKeyCredentialAssertion> credential =
+ (id<ASAuthorizationPublicKeyCredentialAssertion>)
+ authorization.credential;
+ nsTArray<uint8_t> credentialId(NSDataToArray(credential.credentialID));
+ nsTArray<uint8_t> signature(NSDataToArray(credential.signature));
+ nsTArray<uint8_t> rawAuthenticatorData(
+ NSDataToArray(credential.rawAuthenticatorData));
+ nsTArray<uint8_t> userHandle(NSDataToArray(credential.userID));
+ mozilla::Maybe<nsString> authenticatorAttachment;
+ if ([credential
+ isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion
+ class]]) {
+#if defined(MAC_OS_VERSION_13_5) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_5
+ if (__builtin_available(macos 13.5, *)) {
+ ASAuthorizationPlatformPublicKeyCredentialAssertion*
+ platformCredential =
+ (ASAuthorizationPlatformPublicKeyCredentialAssertion*)
+ credential;
+ switch (platformCredential.attachment) {
+ case ASAuthorizationPublicKeyCredentialAttachmentCrossPlatform:
+ authenticatorAttachment.emplace(u"cross-platform"_ns);
+ break;
+ case ASAuthorizationPublicKeyCredentialAttachmentPlatform:
+ authenticatorAttachment.emplace(u"platform"_ns);
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ } else {
+ authenticatorAttachment.emplace(u"cross-platform"_ns);
+ }
+ mCallback->FinishGetAssertion(credentialId, signature, rawAuthenticatorData,
+ userHandle, authenticatorAttachment);
+ } else {
+ MOZ_LOG(
+ gMacOSWebAuthnServiceLog, mozilla::LogLevel::Error,
+ ("MacOSAuthenticatorRequestDelegate::didCompleteWithAuthorization: "
+ "authorization.credential is neither registration nor assertion!"));
+ MOZ_ASSERT_UNREACHABLE(
+ "should have ASAuthorizationPublicKeyCredentialRegistration or "
+ "ASAuthorizationPublicKeyCredentialAssertion");
+ }
+ mCallback->ReleasePlatformResources();
+ mCallback = nullptr;
+}
+
+- (void)authorizationController:(ASAuthorizationController*)controller
+ didCompleteWithError:(NSError*)error {
+ nsAutoString errorDescription;
+ nsCocoaUtils::GetStringForNSString(error.localizedDescription,
+ errorDescription);
+ nsAutoString errorDomain;
+ nsCocoaUtils::GetStringForNSString(error.domain, errorDomain);
+ MOZ_LOG(gMacOSWebAuthnServiceLog, mozilla::LogLevel::Warning,
+ ("MacOSAuthenticatorRequestDelegate::didCompleteWithError: domain "
+ "'%s' code %ld (%s)",
+ NS_ConvertUTF16toUTF8(errorDomain).get(), error.code,
+ NS_ConvertUTF16toUTF8(errorDescription).get()));
+ nsresult rv = NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ // For some reason, the error for "the credential used in a registration was
+ // on the exclude list" is in the "WKErrorDomain" domain with code 8, which
+ // is presumably WKErrorDuplicateCredential.
+ const NSInteger WKErrorDuplicateCredential = 8;
+ if (errorDomain.EqualsLiteral("WKErrorDomain") &&
+ error.code == WKErrorDuplicateCredential) {
+ rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (error.domain == ASAuthorizationErrorDomain) {
+ switch (error.code) {
+ case ASAuthorizationErrorCanceled:
+ rv = NS_ERROR_DOM_ABORT_ERR;
+ break;
+ case ASAuthorizationErrorFailed:
+ // The message is right, but it's not about indexeddb.
+ // See https://webidl.spec.whatwg.org/#constrainterror
+ rv = NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+ break;
+ case ASAuthorizationErrorUnknown:
+ rv = NS_ERROR_DOM_UNKNOWN_ERR;
+ break;
+ default:
+ // rv already has a default value
+ break;
+ }
+ }
+ mCallback->AbortTransaction(rv);
+ mCallback = nullptr;
+}
+@end
+
+@implementation MacOSAuthenticatorPresentationContextProvider
+@synthesize window = window;
+
+- (ASPresentationAnchor)presentationAnchorForAuthorizationController:
+ (ASAuthorizationController*)controller {
+ return window;
+}
+@end
+
+namespace mozilla::dom {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+// Given a browsingContextId, attempts to determine the NSWindow associated
+// with that browser.
+nsresult BrowsingContextIdToNSWindow(uint64_t browsingContextId,
+ NSWindow** window) {
+ *window = nullptr;
+ RefPtr<BrowsingContext> browsingContext(
+ BrowsingContext::Get(browsingContextId));
+ if (!browsingContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ CanonicalBrowsingContext* canonicalBrowsingContext =
+ browsingContext->Canonical();
+ if (!canonicalBrowsingContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<nsIWidget> widget(
+ canonicalBrowsingContext->GetParentProcessWidgetContaining());
+ if (!widget) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *window = static_cast<NSWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ return NS_OK;
+}
+#pragma clang diagnostic pop
+
+already_AddRefed<nsIWebAuthnService> NewMacOSWebAuthnServiceIfAvailable() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!StaticPrefs::security_webauthn_enable_macos_passkeys()) {
+ MOZ_LOG(
+ gMacOSWebAuthnServiceLog, LogLevel::Debug,
+ ("macOS platform support for webauthn (passkeys) disabled by pref"));
+ return nullptr;
+ }
+ // This code checks for the entitlement
+ // 'com.apple.developer.web-browser.public-key-credential', which must be
+ // true to be able to use the platform APIs.
+ CFTypeRefPtr<SecTaskRef> entitlementTask(
+ CFTypeRefPtr<SecTaskRef>::WrapUnderCreateRule(
+ SecTaskCreateFromSelf(nullptr)));
+ CFTypeRefPtr<CFBooleanRef> entitlementValue(
+ CFTypeRefPtr<CFBooleanRef>::WrapUnderCreateRule(
+ reinterpret_cast<CFBooleanRef>(SecTaskCopyValueForEntitlement(
+ entitlementTask.get(),
+ CFSTR("com.apple.developer.web-browser.public-key-credential"),
+ nullptr))));
+ if (!entitlementValue || !CFBooleanGetValue(entitlementValue.get())) {
+ MOZ_LOG(
+ gMacOSWebAuthnServiceLog, LogLevel::Warning,
+ ("entitlement com.apple.developer.web-browser.public-key-credential "
+ "not present: platform passkey APIs will not be available"));
+ return nullptr;
+ }
+ nsCOMPtr<nsIWebAuthnService> service(new MacOSWebAuthnService());
+ return service.forget();
+}
+
+void MacOSWebAuthnService::AbortTransaction(nsresult aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mRegisterPromise) {
+ Unused << mRegisterPromise->Reject(aError);
+ mRegisterPromise = nullptr;
+ }
+ if (mSignPromise) {
+ Unused << mSignPromise->Reject(aError);
+ mSignPromise = nullptr;
+ }
+ ReleasePlatformResources();
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+NS_IMPL_ISUPPORTS(MacOSWebAuthnService, nsIWebAuthnService)
+#pragma clang diagnostic pop
+
+NS_IMETHODIMP
+MacOSWebAuthnService::MakeCredential(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnRegisterArgs* aArgs,
+ nsIWebAuthnRegisterPromise* aPromise) {
+ Reset();
+ auto guard = mTransactionState.Lock();
+ *guard = Some(TransactionState{
+ aTransactionId,
+ aBrowsingContextId,
+ Nothing(),
+ Nothing(),
+ Nothing(),
+ });
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MacOSWebAuthnService::MakeCredential",
+ [self = RefPtr{this}, browsingContextId(aBrowsingContextId),
+ aArgs = nsCOMPtr{aArgs}, aPromise = nsCOMPtr{aPromise}]() {
+ self->mRegisterPromise = aPromise;
+
+ nsAutoString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
+
+ nsTArray<uint8_t> challenge;
+ Unused << aArgs->GetChallenge(challenge);
+ NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
+ length:challenge.Length()];
+
+ nsTArray<uint8_t> userId;
+ Unused << aArgs->GetUserId(userId);
+ NSData* userIdNS = [NSData dataWithBytes:userId.Elements()
+ length:userId.Length()];
+
+ nsAutoString userName;
+ Unused << aArgs->GetUserName(userName);
+ NSString* userNameNS = nsCocoaUtils::ToNSString(userName);
+
+ nsAutoString userDisplayName;
+ Unused << aArgs->GetUserName(userDisplayName);
+ NSString* userDisplayNameNS = nsCocoaUtils::ToNSString(userDisplayName);
+
+ nsTArray<int32_t> coseAlgs;
+ Unused << aArgs->GetCoseAlgs(coseAlgs);
+ NSMutableArray* credentialParameters = [[NSMutableArray alloc] init];
+ for (const auto& coseAlg : coseAlgs) {
+ ASAuthorizationPublicKeyCredentialParameters* credentialParameter =
+ [[ASAuthorizationPublicKeyCredentialParameters alloc]
+ initWithAlgorithm:coseAlg];
+ [credentialParameters addObject:credentialParameter];
+ }
+
+ nsTArray<nsTArray<uint8_t>> excludeList;
+ Unused << aArgs->GetExcludeList(excludeList);
+ nsTArray<uint8_t> excludeListTransports;
+ Unused << aArgs->GetExcludeListTransports(excludeListTransports);
+ if (excludeList.Length() != excludeListTransports.Length()) {
+ self->mRegisterPromise->Reject(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
+ userVerificationPreference = Nothing();
+ nsAutoString userVerification;
+ Unused << aArgs->GetUserVerification(userVerification);
+ if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
+ } else if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
+ } else if (
+ userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
+ }
+
+ // The API doesn't support attestation for platform passkeys and shows
+ // no consent UI for non-none attestation for cross-platform devices,
+ // so this must always be none.
+ ASAuthorizationPublicKeyCredentialAttestationKind
+ attestationPreference =
+ ASAuthorizationPublicKeyCredentialAttestationKindNone;
+
+ // Initialize the platform provider with the rpId.
+ ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
+ [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
+ initWithRelyingPartyIdentifier:rpIdNS];
+ // Make a credential registration request with the challenge, userName,
+ // and userId.
+ ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest*
+ platformRegistrationRequest = [platformProvider
+ createCredentialRegistrationRequestWithChallenge:challengeNS
+ name:userNameNS
+ userID:userIdNS];
+ [platformProvider release];
+ platformRegistrationRequest.attestationPreference =
+ attestationPreference;
+ if (userVerificationPreference.isSome()) {
+ platformRegistrationRequest.userVerificationPreference =
+ *userVerificationPreference;
+ }
+
+ // Initialize the cross-platform provider with the rpId.
+ ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
+ crossPlatformProvider =
+ [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
+ initWithRelyingPartyIdentifier:rpIdNS];
+ // Make a credential registration request with the challenge,
+ // userDisplayName, userName, and userId.
+ ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest*
+ crossPlatformRegistrationRequest = [crossPlatformProvider
+ createCredentialRegistrationRequestWithChallenge:challengeNS
+ displayName:
+ userDisplayNameNS
+ name:userNameNS
+ userID:userIdNS];
+ [crossPlatformProvider release];
+ crossPlatformRegistrationRequest.attestationPreference =
+ attestationPreference;
+ crossPlatformRegistrationRequest.credentialParameters =
+ credentialParameters;
+ if (userVerificationPreference.isSome()) {
+ crossPlatformRegistrationRequest.userVerificationPreference =
+ *userVerificationPreference;
+ }
+ nsTArray<uint8_t> clientDataHash;
+ nsresult rv = aArgs->GetClientDataHash(clientDataHash);
+ if (NS_FAILED(rv)) {
+ self->mRegisterPromise->Reject(rv);
+ return;
+ }
+ nsAutoString authenticatorAttachment;
+ rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
+ self->mRegisterPromise->Reject(rv);
+ return;
+ }
+ NSMutableArray* requests = [[NSMutableArray alloc] init];
+ if (authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
+ [requests addObject:platformRegistrationRequest];
+ } else if (authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
+ [requests addObject:crossPlatformRegistrationRequest];
+ } else {
+ // Regarding the value of authenticator attachment, according to the
+ // specification, "client platforms MUST ignore unknown values,
+ // treating an unknown value as if the member does not exist".
+ [requests addObject:platformRegistrationRequest];
+ [requests addObject:crossPlatformRegistrationRequest];
+ }
+ self->PerformRequests(
+ requests, std::move(clientDataHash), std::move(excludeList),
+ std::move(excludeListTransports), browsingContextId);
+ }));
+ return NS_OK;
+}
+
+void MacOSWebAuthnService::PerformRequests(
+ NSArray<ASAuthorizationRequest*>* aRequests,
+ nsTArray<uint8_t>&& aClientDataHash,
+ nsTArray<nsTArray<uint8_t>>&& aCredentialList,
+ nsTArray<uint8_t>&& aCredentialListTransports,
+ uint64_t aBrowsingContextId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Create a MacOSAuthorizationController and initialize it with the requests.
+ MOZ_ASSERT(!mAuthorizationController);
+ mAuthorizationController = [[MacOSAuthorizationController alloc]
+ initWithAuthorizationRequests:aRequests];
+ [mAuthorizationController
+ stashClientDataHash:std::move(aClientDataHash)
+ andCredentialList:std::move(aCredentialList)
+ andCredentialListTransports:std::move(aCredentialListTransports)];
+
+ // Set up the delegate to run when the operation completes.
+ MOZ_ASSERT(!mRequestDelegate);
+ mRequestDelegate = [[MacOSAuthenticatorRequestDelegate alloc] init];
+ [mRequestDelegate setCallback:this];
+ mAuthorizationController.delegate = mRequestDelegate;
+
+ // Create a presentation context provider so the API knows which window
+ // made the request.
+ NSWindow* window = nullptr;
+ nsresult rv = BrowsingContextIdToNSWindow(aBrowsingContextId, &window);
+ if (NS_FAILED(rv)) {
+ AbortTransaction(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ MOZ_ASSERT(!mPresentationContextProvider);
+ mPresentationContextProvider =
+ [[MacOSAuthenticatorPresentationContextProvider alloc] init];
+ mPresentationContextProvider.window = window;
+ mAuthorizationController.presentationContextProvider =
+ mPresentationContextProvider;
+
+ // Finally, perform the request.
+ [mAuthorizationController performRequests];
+}
+
+void MacOSWebAuthnService::FinishMakeCredential(
+ const nsTArray<uint8_t>& aRawAttestationObject,
+ const nsTArray<uint8_t>& aCredentialId,
+ const nsTArray<nsString>& aTransports,
+ const Maybe<nsString>& aAuthenticatorAttachment) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mRegisterPromise) {
+ return;
+ }
+
+ RefPtr<WebAuthnRegisterResult> result(new WebAuthnRegisterResult(
+ aRawAttestationObject, Nothing(), aCredentialId, aTransports,
+ aAuthenticatorAttachment));
+ Unused << mRegisterPromise->Resolve(result);
+ mRegisterPromise = nullptr;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::GetAssertion(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnSignArgs* aArgs,
+ nsIWebAuthnSignPromise* aPromise) {
+ Reset();
+
+ auto guard = mTransactionState.Lock();
+ *guard = Some(TransactionState{
+ aTransactionId,
+ aBrowsingContextId,
+ Some(RefPtr{aArgs}),
+ Some(RefPtr{aPromise}),
+ Nothing(),
+ });
+
+ bool conditionallyMediated;
+ Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
+ if (!conditionallyMediated) {
+ DoGetAssertion(Nothing(), guard);
+ return NS_OK;
+ }
+
+ // Using conditional mediation, so dispatch a task to collect any available
+ // passkeys.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "platformCredentialsForRelyingParty",
+ [self = RefPtr{this}, aTransactionId, aArgs = nsCOMPtr{aArgs}]() {
+ // This handler is called when platformCredentialsForRelyingParty
+ // completes.
+ auto credentialsCompletionHandler = ^(
+ NSArray<ASAuthorizationWebBrowserPlatformPublicKeyCredential*>*
+ credentials) {
+ nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>> autoFillEntries;
+ for (NSUInteger i = 0; i < credentials.count; i++) {
+ const auto& credential = credentials[i];
+ nsAutoString userName;
+ nsCocoaUtils::GetStringForNSString(credential.name, userName);
+ nsAutoString rpId;
+ nsCocoaUtils::GetStringForNSString(credential.relyingParty, rpId);
+ autoFillEntries.AppendElement(new WebAuthnAutoFillEntry(
+ nsIWebAuthnAutoFillEntry::PROVIDER_PLATFORM_MACOS, userName,
+ rpId, NSDataToArray(credential.credentialID)));
+ }
+ auto guard = self->mTransactionState.Lock();
+ if (guard->isSome() && guard->ref().transactionId == aTransactionId) {
+ guard->ref().autoFillEntries.emplace(std::move(autoFillEntries));
+ }
+ };
+ // This handler is called when
+ // requestAuthorizationForPublicKeyCredentials completes.
+ auto authorizationHandler = ^(
+ ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState
+ authorizationState) {
+ // If authorized, list any available passkeys.
+ if (authorizationState ==
+ ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationStateAuthorized) {
+ nsAutoString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ [self->mCredentialManager
+ platformCredentialsForRelyingParty:nsCocoaUtils::ToNSString(
+ rpId)
+ completionHandler:
+ credentialsCompletionHandler];
+ }
+ };
+ if (!self->mCredentialManager) {
+ self->mCredentialManager =
+ [[ASAuthorizationWebBrowserPublicKeyCredentialManager alloc]
+ init];
+ }
+ // Request authorization to examine any available passkeys. This will
+ // cause a permission prompt to appear once.
+ [self->mCredentialManager
+ requestAuthorizationForPublicKeyCredentials:authorizationHandler];
+ }));
+
+ return NS_OK;
+}
+
+void MacOSWebAuthnService::DoGetAssertion(
+ Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
+ const TransactionStateMutex::AutoLock& aGuard) {
+ if (aGuard->isNothing() || aGuard->ref().pendingSignArgs.isNothing() ||
+ aGuard->ref().pendingSignPromise.isNothing()) {
+ return;
+ }
+
+ // Take the pending Args and Promise to prevent repeated calls to
+ // DoGetAssertion for this transaction.
+ RefPtr<nsIWebAuthnSignArgs> aArgs = aGuard->ref().pendingSignArgs.extract();
+ RefPtr<nsIWebAuthnSignPromise> aPromise =
+ aGuard->ref().pendingSignPromise.extract();
+ uint64_t aBrowsingContextId = aGuard->ref().browsingContextId;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MacOSWebAuthnService::MakeCredential",
+ [self = RefPtr{this}, browsingContextId(aBrowsingContextId), aArgs,
+ aPromise,
+ aSelectedCredentialId = std::move(aSelectedCredentialId)]() mutable {
+ self->mSignPromise = aPromise;
+
+ nsAutoString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ NSString* rpIdNS = nsCocoaUtils::ToNSString(rpId);
+
+ nsTArray<uint8_t> challenge;
+ Unused << aArgs->GetChallenge(challenge);
+ NSData* challengeNS = [NSData dataWithBytes:challenge.Elements()
+ length:challenge.Length()];
+
+ nsTArray<nsTArray<uint8_t>> allowList;
+ nsTArray<uint8_t> allowListTransports;
+ if (aSelectedCredentialId.isSome()) {
+ allowList.AppendElement(aSelectedCredentialId.extract());
+ allowListTransports.AppendElement(
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL);
+ } else {
+ Unused << aArgs->GetAllowList(allowList);
+ Unused << aArgs->GetAllowListTransports(allowListTransports);
+ }
+ NSMutableArray* platformAllowedCredentials =
+ [[NSMutableArray alloc] init];
+ for (const auto& allowedCredentialId : allowList) {
+ NSData* allowedCredentialIdNS =
+ [NSData dataWithBytes:allowedCredentialId.Elements()
+ length:allowedCredentialId.Length()];
+ ASAuthorizationPlatformPublicKeyCredentialDescriptor*
+ allowedCredential =
+ [[ASAuthorizationPlatformPublicKeyCredentialDescriptor alloc]
+ initWithCredentialID:allowedCredentialIdNS];
+ [platformAllowedCredentials addObject:allowedCredential];
+ }
+ const Class securityKeyPublicKeyCredentialDescriptorClass =
+ NSClassFromString(
+ @"ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor");
+ NSArray<ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor*>*
+ crossPlatformAllowedCredentials =
+ CredentialListsToCredentialDescriptorArray(
+ allowList, allowListTransports,
+ securityKeyPublicKeyCredentialDescriptorClass);
+
+ Maybe<ASAuthorizationPublicKeyCredentialUserVerificationPreference>
+ userVerificationPreference = Nothing();
+ nsAutoString userVerification;
+ Unused << aArgs->GetUserVerification(userVerification);
+ if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired);
+ } else if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferencePreferred);
+ } else if (
+ userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
+ userVerificationPreference.emplace(
+ ASAuthorizationPublicKeyCredentialUserVerificationPreferenceDiscouraged);
+ }
+
+ // Initialize the platform provider with the rpId.
+ ASAuthorizationPlatformPublicKeyCredentialProvider* platformProvider =
+ [[ASAuthorizationPlatformPublicKeyCredentialProvider alloc]
+ initWithRelyingPartyIdentifier:rpIdNS];
+ // Make a credential assertion request with the challenge.
+ ASAuthorizationPlatformPublicKeyCredentialAssertionRequest*
+ platformAssertionRequest = [platformProvider
+ createCredentialAssertionRequestWithChallenge:challengeNS];
+ [platformProvider release];
+ platformAssertionRequest.allowedCredentials =
+ platformAllowedCredentials;
+ if (userVerificationPreference.isSome()) {
+ platformAssertionRequest.userVerificationPreference =
+ *userVerificationPreference;
+ }
+
+ // Initialize the cross-platform provider with the rpId.
+ ASAuthorizationSecurityKeyPublicKeyCredentialProvider*
+ crossPlatformProvider =
+ [[ASAuthorizationSecurityKeyPublicKeyCredentialProvider alloc]
+ initWithRelyingPartyIdentifier:rpIdNS];
+ // Make a credential assertion request with the challenge.
+ ASAuthorizationSecurityKeyPublicKeyCredentialAssertionRequest*
+ crossPlatformAssertionRequest = [crossPlatformProvider
+ createCredentialAssertionRequestWithChallenge:challengeNS];
+ [crossPlatformProvider release];
+ crossPlatformAssertionRequest.allowedCredentials =
+ crossPlatformAllowedCredentials;
+ if (userVerificationPreference.isSome()) {
+ crossPlatformAssertionRequest.userVerificationPreference =
+ *userVerificationPreference;
+ }
+ nsTArray<uint8_t> clientDataHash;
+ nsresult rv = aArgs->GetClientDataHash(clientDataHash);
+ if (NS_FAILED(rv)) {
+ self->mSignPromise->Reject(rv);
+ return;
+ }
+ nsTArray<nsTArray<uint8_t>> unusedCredentialIds;
+ nsTArray<uint8_t> unusedTransports;
+ // allowList and allowListTransports won't actually get used.
+ self->PerformRequests(
+ @[ platformAssertionRequest, crossPlatformAssertionRequest ],
+ std::move(clientDataHash), std::move(allowList),
+ std::move(allowListTransports), browsingContextId);
+ }));
+}
+
+void MacOSWebAuthnService::FinishGetAssertion(
+ const nsTArray<uint8_t>& aCredentialId, const nsTArray<uint8_t>& aSignature,
+ const nsTArray<uint8_t>& aAuthenticatorData,
+ const nsTArray<uint8_t>& aUserHandle,
+ const Maybe<nsString>& aAuthenticatorAttachment) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mSignPromise) {
+ return;
+ }
+
+ RefPtr<WebAuthnSignResult> result(new WebAuthnSignResult(
+ aAuthenticatorData, Nothing(), aCredentialId, aSignature, aUserHandle,
+ aAuthenticatorAttachment));
+ Unused << mSignPromise->Resolve(result);
+ mSignPromise = nullptr;
+}
+
+void MacOSWebAuthnService::ReleasePlatformResources() {
+ MOZ_ASSERT(NS_IsMainThread());
+ [mCredentialManager release];
+ mCredentialManager = nil;
+ [mAuthorizationController release];
+ mAuthorizationController = nil;
+ [mRequestDelegate release];
+ mRequestDelegate = nil;
+ [mPresentationContextProvider release];
+ mPresentationContextProvider = nil;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::Reset() {
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ if (guard->ref().pendingSignPromise.isSome()) {
+ // This request was never dispatched to the platform API, so
+ // we need to reject the promise ourselves.
+ guard->ref().pendingSignPromise.ref()->Reject(
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+ guard->reset();
+ }
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MacOSWebAuthnService::Cancel", [self = RefPtr{this}] {
+ // cancel results in the delegate's didCompleteWithError method being
+ // called, which will release all platform resources.
+ [self->mAuthorizationController cancel];
+ }));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::GetIsUVPAA(bool* aAvailable) {
+ *aAvailable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::Cancel(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
+ const nsAString& aOrigin,
+ uint64_t* aRv) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() ||
+ guard->ref().browsingContextId != aBrowsingContextId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ *aRv = 0;
+ return NS_OK;
+ }
+
+ nsString origin;
+ Unused << guard->ref().pendingSignArgs.ref()->GetOrigin(origin);
+ if (origin != aOrigin) {
+ *aRv = 0;
+ return NS_OK;
+ }
+
+ *aRv = guard->ref().transactionId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::GetAutoFillEntries(
+ uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing() ||
+ guard->ref().autoFillEntries.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aRv.Assign(guard->ref().autoFillEntries.ref());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::SelectAutoFillEntry(
+ uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsTArray<uint8_t>> allowList;
+ Unused << guard->ref().pendingSignArgs.ref()->GetAllowList(allowList);
+ if (!allowList.IsEmpty() && !allowList.Contains(aCredentialId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Maybe<nsTArray<uint8_t>> id;
+ id.emplace();
+ id.ref().Assign(aCredentialId);
+ DoGetAssertion(std::move(id), guard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ DoGetAssertion(Nothing(), guard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::PinCallback(uint64_t aTransactionId,
+ const nsACString& aPin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
+ bool aForceNoneAttestation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::SelectionCallback(uint64_t aTransactionId,
+ uint64_t aIndex) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::AddVirtualAuthenticator(
+ const nsACString& protocol, const nsACString& transport,
+ bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
+ bool isUserVerified, uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::AddCredential(uint64_t authenticatorId,
+ const nsACString& credentialId,
+ bool isResidentCredential,
+ const nsACString& rpId,
+ const nsACString& privateKey,
+ const nsACString& userHandle,
+ uint32_t signCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::GetCredentials(
+ uint64_t authenticatorId,
+ nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::RemoveCredential(uint64_t authenticatorId,
+ const nsACString& credentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::SetUserVerified(uint64_t authenticatorId,
+ bool isUserVerified) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MacOSWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+MacOSWebAuthnService::RunCommand(const nsACString& cmd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla::dom
+
+NS_ASSUME_NONNULL_END
diff --git a/dom/webauthn/PWebAuthnTransaction.ipdl b/dom/webauthn/PWebAuthnTransaction.ipdl
new file mode 100644
index 0000000000..229fb7899d
--- /dev/null
+++ b/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -0,0 +1,157 @@
+/* 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/. */
+
+/*
+ * IPC Transaction protocol for the WebAuthn DOM API. This IPC protocol allows
+ * the content process to call to the parent to access hardware for
+ * authentication registration and challenges. All transactions start in the
+ * child process, and the parent replies with a "Confirm*" message, or a
+ * "Cancel" message if there was an error (no hardware available, no registered
+ * keys, etc) or interruption (another transaction was started in another
+ * content process). Similarly, the content process can also request a cancel,
+ * either triggered explicitly by the user/script or due to UI events like
+ * selecting a different tab.
+ */
+
+include protocol PBackground;
+
+using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h";
+
+namespace mozilla {
+namespace dom {
+
+struct WebAuthnAuthenticatorSelection {
+ nsString residentKey;
+ nsString userVerificationRequirement;
+ nsString? authenticatorAttachment;
+};
+
+struct WebAuthnScopedCredential {
+ uint8_t[] id;
+ uint8_t transports;
+};
+
+struct WebAuthnExtensionAppId {
+ nsString appIdentifier;
+};
+
+struct WebAuthnExtensionCredProps {
+ bool credProps;
+};
+
+struct WebAuthnExtensionHmacSecret {
+ bool hmacCreateSecret;
+};
+
+struct WebAuthnExtensionMinPinLength {
+ bool minPinLength;
+};
+
+union WebAuthnExtension {
+ WebAuthnExtensionAppId;
+ WebAuthnExtensionCredProps;
+ WebAuthnExtensionHmacSecret;
+ WebAuthnExtensionMinPinLength;
+};
+
+struct WebAuthnExtensionResultAppId {
+ bool AppId;
+};
+
+struct WebAuthnExtensionResultCredProps {
+ bool rk;
+};
+
+struct WebAuthnExtensionResultHmacSecret {
+ bool hmacCreateSecret;
+};
+
+union WebAuthnExtensionResult {
+ WebAuthnExtensionResultAppId;
+ WebAuthnExtensionResultCredProps;
+ WebAuthnExtensionResultHmacSecret;
+};
+
+struct WebAuthnMakeCredentialRpInfo {
+ nsString Name;
+};
+
+struct WebAuthnMakeCredentialUserInfo {
+ uint8_t[] Id;
+ nsString Name;
+ nsString DisplayName;
+};
+
+struct CoseAlg {
+ long alg;
+};
+
+struct WebAuthnMakeCredentialInfo {
+ nsString Origin;
+ nsString RpId;
+ uint8_t[] Challenge;
+ nsCString ClientDataJSON;
+ uint32_t TimeoutMS;
+ WebAuthnScopedCredential[] ExcludeList;
+ WebAuthnMakeCredentialRpInfo Rp;
+ WebAuthnMakeCredentialUserInfo User;
+ CoseAlg[] coseAlgs;
+ WebAuthnExtension[] Extensions;
+ WebAuthnAuthenticatorSelection AuthenticatorSelection;
+ nsString attestationConveyancePreference;
+ uint64_t BrowsingContextId;
+};
+
+struct WebAuthnMakeCredentialResult {
+ nsCString ClientDataJSON;
+ uint8_t[] AttestationObject;
+ uint8_t[] KeyHandle;
+ nsString[] Transports;
+ WebAuthnExtensionResult[] Extensions;
+ nsString? AuthenticatorAttachment;
+};
+
+struct WebAuthnGetAssertionInfo {
+ nsString Origin;
+ nsString RpId;
+ uint8_t[] Challenge;
+ nsCString ClientDataJSON;
+ uint32_t TimeoutMS;
+ WebAuthnScopedCredential[] AllowList;
+ WebAuthnExtension[] Extensions;
+ nsString userVerificationRequirement;
+ bool ConditionallyMediated;
+ uint64_t BrowsingContextId;
+};
+
+struct WebAuthnGetAssertionResult {
+ nsCString ClientDataJSON;
+ uint8_t[] KeyHandle;
+ uint8_t[] Signature;
+ uint8_t[] AuthenticatorData;
+ WebAuthnExtensionResult[] Extensions;
+ uint8_t[] UserHandle;
+ nsString? AuthenticatorAttachment;
+};
+
+[ManualDealloc]
+async protocol PWebAuthnTransaction {
+ manager PBackground;
+
+ parent:
+ async RequestRegister(uint64_t aTransactionId, WebAuthnMakeCredentialInfo aTransactionInfo);
+ async RequestSign(uint64_t aTransactionId, WebAuthnGetAssertionInfo aTransactionInfo);
+ async RequestIsUVPAA() returns (bool available);
+ [Tainted] async RequestCancel(uint64_t aTransactionId);
+ async DestroyMe();
+
+ child:
+ async __delete__();
+ async ConfirmRegister(uint64_t aTransactionId, WebAuthnMakeCredentialResult aResult);
+ async ConfirmSign(uint64_t aTransactionId, WebAuthnGetAssertionResult aResult);
+ async Abort(uint64_t aTransactionId, nsresult Error);
+};
+
+}
+}
diff --git a/dom/webauthn/PublicKeyCredential.cpp b/dom/webauthn/PublicKeyCredential.cpp
new file mode 100644
index 0000000000..444d9742b3
--- /dev/null
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -0,0 +1,377 @@
+/* -*- 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 "mozilla/Base64.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
+#include "mozilla/dom/CredentialsContainer.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnManager.h"
+#include "nsCycleCollectionParticipant.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/MozPromise.h"
+# include "mozilla/java/GeckoResultNatives.h"
+# include "mozilla/java/WebAuthnTokenManagerWrappers.h"
+#endif
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PublicKeyCredential)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PublicKeyCredential, Credential)
+ tmp->mRawIdCachedObj = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PublicKeyCredential, Credential)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRawIdCachedObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PublicKeyCredential,
+ Credential)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttestationResponse)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAssertionResponse)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
+NS_IMPL_RELEASE_INHERITED(PublicKeyCredential, Credential)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PublicKeyCredential)
+NS_INTERFACE_MAP_END_INHERITING(Credential)
+
+PublicKeyCredential::PublicKeyCredential(nsPIDOMWindowInner* aParent)
+ : Credential(aParent), mRawIdCachedObj(nullptr) {
+ mozilla::HoldJSObjects(this);
+}
+
+PublicKeyCredential::~PublicKeyCredential() { mozilla::DropJSObjects(this); }
+
+JSObject* PublicKeyCredential::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PublicKeyCredential_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void PublicKeyCredential::GetRawId(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv) {
+ if (!mRawIdCachedObj) {
+ mRawIdCachedObj = ArrayBuffer::Create(aCx, mRawId, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ aValue.set(mRawIdCachedObj);
+}
+
+void PublicKeyCredential::GetAuthenticatorAttachment(
+ DOMString& aAuthenticatorAttachment) {
+ if (mAuthenticatorAttachment.isSome()) {
+ aAuthenticatorAttachment.SetKnownLiveString(mAuthenticatorAttachment.ref());
+ } else {
+ aAuthenticatorAttachment.SetNull();
+ }
+}
+
+already_AddRefed<AuthenticatorResponse> PublicKeyCredential::Response() const {
+ if (mAttestationResponse) {
+ return do_AddRef(mAttestationResponse);
+ }
+ if (mAssertionResponse) {
+ return do_AddRef(mAssertionResponse);
+ }
+ return nullptr;
+}
+
+void PublicKeyCredential::SetRawId(const nsTArray<uint8_t>& aBuffer) {
+ mRawId.Assign(aBuffer);
+}
+
+void PublicKeyCredential::SetAuthenticatorAttachment(
+ const Maybe<nsString>& aAuthenticatorAttachment) {
+ mAuthenticatorAttachment = aAuthenticatorAttachment;
+}
+
+void PublicKeyCredential::SetAttestationResponse(
+ const RefPtr<AuthenticatorAttestationResponse>& aAttestationResponse) {
+ mAttestationResponse = aAttestationResponse;
+}
+
+void PublicKeyCredential::SetAssertionResponse(
+ const RefPtr<AuthenticatorAssertionResponse>& aAssertionResponse) {
+ mAssertionResponse = aAssertionResponse;
+}
+
+/* static */
+already_AddRefed<Promise>
+PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(
+ GlobalObject& aGlobal, ErrorResult& aError) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<WebAuthnManager> manager =
+ window->Navigator()->Credentials()->GetWebAuthnManager();
+ return manager->IsUVPAA(aGlobal, aError);
+}
+
+/* static */
+already_AddRefed<Promise> PublicKeyCredential::IsConditionalMediationAvailable(
+ GlobalObject& aGlobal, ErrorResult& aError) {
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aGlobal.Context()), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+#if defined(MOZ_WIDGET_ANDROID)
+ promise->MaybeResolve(false);
+#else
+ promise->MaybeResolve(
+ StaticPrefs::security_webauthn_enable_conditional_mediation());
+#endif
+ return promise.forget();
+}
+
+void PublicKeyCredential::GetClientExtensionResults(
+ AuthenticationExtensionsClientOutputs& aResult) {
+ 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;
+ }
+ if (mAuthenticatorAttachment.isSome()) {
+ json.mAuthenticatorAttachment.Construct();
+ json.mAuthenticatorAttachment.Value() = mAuthenticatorAttachment.ref();
+ }
+ if (mClientExtensionOutputs.mCredProps.WasPassed()) {
+ json.mClientExtensionResults.mCredProps.Construct(
+ mClientExtensionOutputs.mCredProps.Value());
+ }
+ 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;
+ }
+ if (mAuthenticatorAttachment.isSome()) {
+ json.mAuthenticatorAttachment.Construct();
+ json.mAuthenticatorAttachment.Value() = mAuthenticatorAttachment.ref();
+ }
+ if (mClientExtensionOutputs.mAppid.WasPassed()) {
+ json.mClientExtensionResults.mAppid.Construct(
+ mClientExtensionOutputs.mAppid.Value());
+ }
+ json.mType.Assign(u"public-key"_ns);
+ if (!ToJSValue(aCx, json, &value)) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "either mAttestationResponse or mAssertionResponse should be set");
+ }
+ JS::Rooted<JSObject*> result(aCx, &value.toObject());
+ aRetval.set(result);
+}
+
+void PublicKeyCredential::SetClientExtensionResultAppId(bool aResult) {
+ mClientExtensionOutputs.mAppid.Construct();
+ mClientExtensionOutputs.mAppid.Value() = aResult;
+}
+
+void PublicKeyCredential::SetClientExtensionResultCredPropsRk(bool aResult) {
+ mClientExtensionOutputs.mCredProps.Construct();
+ mClientExtensionOutputs.mCredProps.Value().mRk.Construct();
+ mClientExtensionOutputs.mCredProps.Value().mRk.Value() = aResult;
+}
+
+void PublicKeyCredential::SetClientExtensionResultHmacSecret(
+ bool aHmacCreateSecret) {
+ mClientExtensionOutputs.mHmacCreateSecret.Construct();
+ 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().mCredProps.WasPassed()) {
+ aResult.mExtensions.mCredProps.Construct(
+ aOptions.mExtensions.Value().mCredProps.Value());
+ }
+ if (aOptions.mExtensions.Value().mHmacCreateSecret.WasPassed()) {
+ aResult.mExtensions.mHmacCreateSecret.Construct(
+ aOptions.mExtensions.Value().mHmacCreateSecret.Value());
+ }
+ if (aOptions.mExtensions.Value().mMinPinLength.WasPassed()) {
+ aResult.mExtensions.mMinPinLength.Construct(
+ aOptions.mExtensions.Value().mMinPinLength.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().mCredProps.WasPassed()) {
+ aResult.mExtensions.mCredProps.Construct(
+ aOptions.mExtensions.Value().mCredProps.Value());
+ }
+ if (aOptions.mExtensions.Value().mHmacCreateSecret.WasPassed()) {
+ aResult.mExtensions.mHmacCreateSecret.Construct(
+ aOptions.mExtensions.Value().mHmacCreateSecret.Value());
+ }
+ if (aOptions.mExtensions.Value().mMinPinLength.WasPassed()) {
+ aResult.mExtensions.mMinPinLength.Construct(
+ aOptions.mExtensions.Value().mMinPinLength.Value());
+ }
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/PublicKeyCredential.h b/dom/webauthn/PublicKeyCredential.h
new file mode 100644
index 0000000000..67163553c8
--- /dev/null
+++ b/dom/webauthn/PublicKeyCredential.h
@@ -0,0 +1,93 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PublicKeyCredential_h
+#define mozilla_dom_PublicKeyCredential_h
+
+#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"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class PublicKeyCredential final : public Credential {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PublicKeyCredential,
+ Credential)
+
+ explicit PublicKeyCredential(nsPIDOMWindowInner* aParent);
+
+ protected:
+ ~PublicKeyCredential() override;
+
+ public:
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetRawId(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ void GetAuthenticatorAttachment(DOMString& aAuthenticatorAttachment);
+
+ already_AddRefed<AuthenticatorResponse> Response() const;
+
+ void SetRawId(const nsTArray<uint8_t>& aBuffer);
+
+ void SetAuthenticatorAttachment(
+ const Maybe<nsString>& aAuthenticatorAttachment);
+
+ void SetAttestationResponse(
+ const RefPtr<AuthenticatorAttestationResponse>& aAttestationResponse);
+ void SetAssertionResponse(
+ const RefPtr<AuthenticatorAssertionResponse>& aAssertionResponse);
+
+ static already_AddRefed<Promise>
+ IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal,
+ ErrorResult& aError);
+
+ static already_AddRefed<Promise> IsConditionalMediationAvailable(
+ GlobalObject& aGlobal, ErrorResult& aError);
+
+ void GetClientExtensionResults(
+ AuthenticationExtensionsClientOutputs& aResult);
+
+ void ToJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError);
+
+ void SetClientExtensionResultAppId(bool aResult);
+
+ void SetClientExtensionResultCredPropsRk(bool aResult);
+
+ void SetClientExtensionResultHmacSecret(bool aHmacCreateSecret);
+
+ static void ParseCreationOptionsFromJSON(
+ GlobalObject& aGlobal,
+ const PublicKeyCredentialCreationOptionsJSON& aOptions,
+ PublicKeyCredentialCreationOptions& aResult, ErrorResult& aRv);
+
+ static void ParseRequestOptionsFromJSON(
+ GlobalObject& aGlobal,
+ const PublicKeyCredentialRequestOptionsJSON& aOptions,
+ PublicKeyCredentialRequestOptions& aResult, ErrorResult& aRv);
+
+ private:
+ nsTArray<uint8_t> mRawId;
+ JS::Heap<JSObject*> mRawIdCachedObj;
+ Maybe<nsString> mAuthenticatorAttachment;
+ RefPtr<AuthenticatorAttestationResponse> mAttestationResponse;
+ RefPtr<AuthenticatorAssertionResponse> mAssertionResponse;
+ AuthenticationExtensionsClientOutputs mClientExtensionOutputs;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_PublicKeyCredential_h
diff --git a/dom/webauthn/WebAuthnArgs.cpp b/dom/webauthn/WebAuthnArgs.cpp
new file mode 100644
index 0000000000..7c78d39bef
--- /dev/null
+++ b/dom/webauthn/WebAuthnArgs.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "WebAuthnArgs.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnUtil.h"
+#include "mozilla/dom/PWebAuthnTransactionParent.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(WebAuthnRegisterArgs, nsIWebAuthnRegisterArgs)
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetOrigin(nsAString& aOrigin) {
+ aOrigin = mInfo.Origin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetChallenge(nsTArray<uint8_t>& aChallenge) {
+ aChallenge.Assign(mInfo.Challenge());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetClientDataJSON(nsACString& aClientDataJSON) {
+ aClientDataJSON = mInfo.ClientDataJSON();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetClientDataHash(nsTArray<uint8_t>& aClientDataHash) {
+ nsresult rv = HashCString(mInfo.ClientDataJSON(), aClientDataHash);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetRpId(nsAString& aRpId) {
+ aRpId = mInfo.RpId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetRpName(nsAString& aRpName) {
+ aRpName = mInfo.Rp().Name();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetUserId(nsTArray<uint8_t>& aUserId) {
+ aUserId.Assign(mInfo.User().Id());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetUserName(nsAString& aUserName) {
+ aUserName = mInfo.User().Name();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetUserDisplayName(nsAString& aUserDisplayName) {
+ aUserDisplayName = mInfo.User().DisplayName();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetCoseAlgs(nsTArray<int32_t>& aCoseAlgs) {
+ aCoseAlgs.Clear();
+ for (const CoseAlg& coseAlg : mInfo.coseAlgs()) {
+ aCoseAlgs.AppendElement(coseAlg.alg());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetExcludeList(
+ nsTArray<nsTArray<uint8_t> >& aExcludeList) {
+ aExcludeList.Clear();
+ for (const WebAuthnScopedCredential& cred : mInfo.ExcludeList()) {
+ aExcludeList.AppendElement(cred.id().Clone());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetExcludeListTransports(
+ nsTArray<uint8_t>& aExcludeListTransports) {
+ aExcludeListTransports.Clear();
+ for (const WebAuthnScopedCredential& cred : mInfo.ExcludeList()) {
+ aExcludeListTransports.AppendElement(cred.transports());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetCredProps(bool* aCredProps) {
+ *aCredProps = mCredProps;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetHmacCreateSecret(bool* aHmacCreateSecret) {
+ *aHmacCreateSecret = mHmacCreateSecret;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetMinPinLength(bool* aMinPinLength) {
+ *aMinPinLength = mMinPinLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetResidentKey(nsAString& aResidentKey) {
+ aResidentKey = mInfo.AuthenticatorSelection().residentKey();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetUserVerification(
+ nsAString& aUserVerificationRequirement) {
+ aUserVerificationRequirement =
+ mInfo.AuthenticatorSelection().userVerificationRequirement();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetAuthenticatorAttachment(
+ nsAString& aAuthenticatorAttachment) {
+ if (mInfo.AuthenticatorSelection().authenticatorAttachment().isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aAuthenticatorAttachment =
+ *mInfo.AuthenticatorSelection().authenticatorAttachment();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetTimeoutMS(uint32_t* aTimeoutMS) {
+ *aTimeoutMS = mInfo.TimeoutMS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetAttestationConveyancePreference(
+ nsAString& aAttestationConveyancePreference) {
+ aAttestationConveyancePreference = mInfo.attestationConveyancePreference();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(WebAuthnSignArgs, nsIWebAuthnSignArgs)
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetOrigin(nsAString& aOrigin) {
+ aOrigin = mInfo.Origin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetRpId(nsAString& aRpId) {
+ aRpId = mInfo.RpId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetChallenge(nsTArray<uint8_t>& aChallenge) {
+ aChallenge.Assign(mInfo.Challenge());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetClientDataJSON(nsACString& aClientDataJSON) {
+ aClientDataJSON = mInfo.ClientDataJSON();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetClientDataHash(nsTArray<uint8_t>& aClientDataHash) {
+ nsresult rv = HashCString(mInfo.ClientDataJSON(), aClientDataHash);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetAllowList(nsTArray<nsTArray<uint8_t> >& aAllowList) {
+ aAllowList.Clear();
+ for (const WebAuthnScopedCredential& cred : mInfo.AllowList()) {
+ aAllowList.AppendElement(cred.id().Clone());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetAllowListTransports(
+ nsTArray<uint8_t>& aAllowListTransports) {
+ aAllowListTransports.Clear();
+ for (const WebAuthnScopedCredential& cred : mInfo.AllowList()) {
+ aAllowListTransports.AppendElement(cred.transports());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetHmacCreateSecret(bool* aHmacCreateSecret) {
+ for (const WebAuthnExtension& ext : mInfo.Extensions()) {
+ if (ext.type() == WebAuthnExtension::TWebAuthnExtensionHmacSecret) {
+ *aHmacCreateSecret =
+ ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetAppId(nsAString& aAppId) {
+ if (mAppId.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aAppId = mAppId.ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetUserVerification(nsAString& aUserVerificationRequirement) {
+ aUserVerificationRequirement = mInfo.userVerificationRequirement();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetTimeoutMS(uint32_t* aTimeoutMS) {
+ *aTimeoutMS = mInfo.TimeoutMS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignArgs::GetConditionallyMediated(bool* aConditionallyMediated) {
+ *aConditionallyMediated = mInfo.ConditionallyMediated();
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnArgs.h b/dom/webauthn/WebAuthnArgs.h
new file mode 100644
index 0000000000..1b02646e62
--- /dev/null
+++ b/dom/webauthn/WebAuthnArgs.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. */
+
+#ifndef mozilla_dom_WebAuthnArgs_H_
+#define mozilla_dom_WebAuthnArgs_H_
+
+#include "mozilla/dom/WebAuthnTransactionChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsIWebAuthnArgs.h"
+
+namespace mozilla::dom {
+
+class WebAuthnRegisterArgs final : public nsIWebAuthnRegisterArgs {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNREGISTERARGS
+
+ explicit WebAuthnRegisterArgs(const WebAuthnMakeCredentialInfo& aInfo)
+ : mInfo(aInfo),
+ mCredProps(false),
+ mHmacCreateSecret(false),
+ mMinPinLength(false) {
+ for (const WebAuthnExtension& ext : mInfo.Extensions()) {
+ switch (ext.type()) {
+ case WebAuthnExtension::TWebAuthnExtensionCredProps:
+ mCredProps = ext.get_WebAuthnExtensionCredProps().credProps();
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionHmacSecret:
+ mHmacCreateSecret =
+ ext.get_WebAuthnExtensionHmacSecret().hmacCreateSecret();
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
+ mMinPinLength =
+ ext.get_WebAuthnExtensionMinPinLength().minPinLength();
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionAppId:
+ break;
+ case WebAuthnExtension::T__None:
+ break;
+ }
+ }
+ }
+
+ private:
+ ~WebAuthnRegisterArgs() = default;
+
+ const WebAuthnMakeCredentialInfo mInfo;
+
+ // Flags to indicate whether an extension is being requested.
+ bool mCredProps;
+ bool mHmacCreateSecret;
+ bool mMinPinLength;
+};
+
+class WebAuthnSignArgs final : public nsIWebAuthnSignArgs {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSIGNARGS
+
+ explicit WebAuthnSignArgs(const WebAuthnGetAssertionInfo& aInfo)
+ : mInfo(aInfo) {
+ for (const WebAuthnExtension& ext : mInfo.Extensions()) {
+ switch (ext.type()) {
+ case WebAuthnExtension::TWebAuthnExtensionAppId:
+ mAppId = Some(ext.get_WebAuthnExtensionAppId().appIdentifier());
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionCredProps:
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionHmacSecret:
+ break;
+ case WebAuthnExtension::TWebAuthnExtensionMinPinLength:
+ break;
+ case WebAuthnExtension::T__None:
+ break;
+ }
+ }
+ }
+
+ private:
+ ~WebAuthnSignArgs() = default;
+
+ const WebAuthnGetAssertionInfo mInfo;
+ Maybe<nsString> mAppId;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnArgs_H_
diff --git a/dom/webauthn/WebAuthnAutoFillEntry.cpp b/dom/webauthn/WebAuthnAutoFillEntry.cpp
new file mode 100644
index 0000000000..78423d1df2
--- /dev/null
+++ b/dom/webauthn/WebAuthnAutoFillEntry.cpp
@@ -0,0 +1,35 @@
+/* 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 "WebAuthnAutoFillEntry.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(WebAuthnAutoFillEntry, nsIWebAuthnAutoFillEntry)
+
+NS_IMETHODIMP
+WebAuthnAutoFillEntry::GetProvider(uint8_t* aProvider) {
+ *aProvider = mProvider;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnAutoFillEntry::GetUserName(nsAString& aUserName) {
+ aUserName.Assign(mUserName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnAutoFillEntry::GetRpId(nsAString& aRpId) {
+ aRpId.Assign(mRpId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnAutoFillEntry::GetCredentialId(nsTArray<uint8_t>& aCredentialId) {
+ aCredentialId.Assign(mCredentialId);
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnAutoFillEntry.h b/dom/webauthn/WebAuthnAutoFillEntry.h
new file mode 100644
index 0000000000..53fb040562
--- /dev/null
+++ b/dom/webauthn/WebAuthnAutoFillEntry.h
@@ -0,0 +1,51 @@
+/* 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/. */
+
+#ifndef mozilla_dom_WebAuthnAutoFillEntry_h_
+#define mozilla_dom_WebAuthnAutoFillEntry_h_
+
+#include "nsIWebAuthnService.h"
+#include "nsString.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+# include "winwebauthn/webauthn.h"
+#endif
+
+namespace mozilla::dom {
+
+class WebAuthnAutoFillEntry final : public nsIWebAuthnAutoFillEntry {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNAUTOFILLENTRY
+
+ WebAuthnAutoFillEntry(uint8_t aProvider, const nsAString& aUserName,
+ const nsAString& aRpId,
+ const nsTArray<uint8_t>& aCredentialId)
+ : mProvider(aProvider), mUserName(aUserName), mRpId(aRpId) {
+ mCredentialId.Assign(aCredentialId);
+ }
+
+#ifdef XP_WIN
+ explicit WebAuthnAutoFillEntry(
+ PCWEBAUTHN_CREDENTIAL_DETAILS aCredentialDetails) {
+ mProvider = nsIWebAuthnAutoFillEntry::PROVIDER_PLATFORM_WINDOWS;
+ mUserName.Assign(aCredentialDetails->pUserInformation->pwszName);
+ mRpId.Assign(aCredentialDetails->pRpInformation->pwszId);
+ mCredentialId.AppendElements(aCredentialDetails->pbCredentialID,
+ aCredentialDetails->cbCredentialID);
+ }
+#endif
+
+ private:
+ ~WebAuthnAutoFillEntry() = default;
+
+ uint8_t mProvider;
+ nsString mUserName;
+ nsString mRpId;
+ nsTArray<uint8_t> mCredentialId;
+};
+
+} // namespace mozilla::dom
+#endif // mozilla_dom_WebAuthnAutoFillEntry_h_
diff --git a/dom/webauthn/WebAuthnCoseIdentifiers.h b/dom/webauthn/WebAuthnCoseIdentifiers.h
new file mode 100644
index 0000000000..9e98b5623d
--- /dev/null
+++ b/dom/webauthn/WebAuthnCoseIdentifiers.h
@@ -0,0 +1,22 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnCoseIdentifiers_h
+#define mozilla_dom_WebAuthnCoseIdentifiers_h
+
+#include "mozilla/dom/WebCryptoCommon.h"
+
+namespace mozilla::dom {
+
+// From https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+enum class CoseAlgorithmIdentifier : int32_t {
+ ES256 = -7,
+ RS256 = -257,
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnCoseIdentifiers_h
diff --git a/dom/webauthn/WebAuthnEnumStrings.h b/dom/webauthn/WebAuthnEnumStrings.h
new file mode 100644
index 0000000000..35cca2ed0c
--- /dev/null
+++ b/dom/webauthn/WebAuthnEnumStrings.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnEnumStrings_h
+#define mozilla_dom_WebAuthnEnumStrings_h
+
+// WARNING: This version number must match the WebAuthn level where the strings
+// below are defined.
+#define MOZ_WEBAUTHN_ENUM_STRINGS_VERSION 3
+
+// https://www.w3.org/TR/webauthn-2/#enum-attestation-convey
+#define MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE "none"
+#define MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT "indirect"
+#define MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT "direct"
+#define MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE "enterprise"
+// WARNING: Change version number when adding new values!
+
+// https://www.w3.org/TR/webauthn-2/#enum-attachment
+#define MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM "platform"
+#define MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM "cross-platform"
+// WARNING: Change version number when adding new values!
+
+// https://www.w3.org/TR/webauthn-2/#enum-credentialType
+#define MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY "public-key"
+// WARNING: Change version number when adding new values!
+
+// https://www.w3.org/TR/webauthn-2/#enum-residentKeyRequirement
+#define MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED "required"
+#define MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED "preferred"
+#define MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED "discouraged"
+// WARNING: Change version number when adding new values!
+
+// https://www.w3.org/TR/webauthn-2/#enum-userVerificationRequirement
+#define MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED "required"
+#define MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED "preferred"
+#define MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED "discouraged"
+// WARNING: Change version number when adding new values!
+
+// https://www.w3.org/TR/webauthn-2/#enum-transport
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_USB "usb"
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_NFC "nfc"
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_BLE "ble"
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_INTERNAL "internal"
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_HYBRID "hybrid"
+// WARNING: Change version number when adding new values!
+
+#endif // mozilla_dom_WebAuthnEnumStrings_h
diff --git a/dom/webauthn/WebAuthnManager.cpp b/dom/webauthn/WebAuthnManager.cpp
new file mode 100644
index 0000000000..e4d18ebc45
--- /dev/null
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -0,0 +1,838 @@
+/* -*- 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 "hasht.h"
+#include "nsHTMLDocument.h"
+#include "nsIURIMutator.h"
+#include "nsThreadUtils.h"
+#include "WebAuthnCoseIdentifiers.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnTransportIdentifiers.h"
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/AuthenticatorAssertionResponse.h"
+#include "mozilla/dom/AuthenticatorAttestationResponse.h"
+#include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "mozilla/dom/PWebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnManager.h"
+#include "mozilla/dom/WebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnUtil.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+#ifdef XP_WIN
+# include "WinWebAuthnService.h"
+#endif
+
+using namespace mozilla::ipc;
+
+namespace mozilla::dom {
+
+/***********************************************************************
+ * Statics
+ **********************************************************************/
+
+namespace {
+static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WebAuthnManager,
+ WebAuthnManagerBase)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager,
+ WebAuthnManagerBase)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
+ tmp->mTransaction.reset();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebAuthnManager,
+ WebAuthnManagerBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+/***********************************************************************
+ * Utility Functions
+ **********************************************************************/
+
+static nsresult AssembleClientData(
+ const nsAString& aOrigin, const CryptoBuffer& aChallenge,
+ const nsAString& aType,
+ const AuthenticationExtensionsClientInputs& aExtensions,
+ /* out */ nsACString& aJsonOut) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsString challengeBase64;
+ nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CollectedClientData clientDataObject;
+ clientDataObject.mType.Assign(aType);
+ clientDataObject.mChallenge.Assign(challengeBase64);
+ clientDataObject.mOrigin.Assign(aOrigin);
+
+ nsAutoString temp;
+ if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
+ return NS_OK;
+}
+
+static uint8_t SerializeTransports(
+ const mozilla::dom::Sequence<nsString>& aTransports) {
+ uint8_t transports = 0;
+
+ // We ignore unknown transports for forward-compatibility, but this
+ // needs to be reviewed if values are added to the
+ // AuthenticatorTransport enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ for (const nsAString& str : aTransports) {
+ if (str.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_USB)) {
+ transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB;
+ } else if (str.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_NFC)) {
+ transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC;
+ } else if (str.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_BLE)) {
+ transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE;
+ } else if (str.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_INTERNAL)) {
+ transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL;
+ } else if (str.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_HYBRID)) {
+ transports |= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID;
+ }
+ }
+ return transports;
+}
+
+nsresult GetOrigin(nsPIDOMWindowInner* aParent,
+ /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost) {
+ MOZ_ASSERT(aParent);
+ nsCOMPtr<Document> doc = aParent->GetDoc();
+ MOZ_ASSERT(doc);
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ nsresult rv =
+ nsContentUtils::GetWebExposedOriginSerialization(principal, aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(aOrigin.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (principal->GetIsIpAddress()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ if (aOrigin.EqualsLiteral("null")) {
+ // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
+ // DOMException whose name is "NotAllowedError", and terminate this
+ // algorithm
+ MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug,
+ ("Rejecting due to opaque origin"));
+ return NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ }
+
+ nsCOMPtr<nsIURI> originUri;
+ auto* basePrin = BasePrincipal::Cast(principal);
+ if (NS_FAILED(basePrin->GetURI(getter_AddRefs(originUri)))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(originUri->GetAsciiHost(aHost))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult RelaxSameOrigin(nsPIDOMWindowInner* aParent,
+ const nsAString& aInputRpId,
+ /* out */ nsACString& aRelaxedRpId) {
+ MOZ_ASSERT(aParent);
+ nsCOMPtr<Document> doc = aParent->GetDoc();
+ MOZ_ASSERT(doc);
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ auto* basePrin = BasePrincipal::Cast(principal);
+ nsCOMPtr<nsIURI> uri;
+
+ if (NS_FAILED(basePrin->GetURI(getter_AddRefs(uri)))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoCString originHost;
+ if (NS_FAILED(uri->GetAsciiHost(originHost))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<Document> document = aParent->GetDoc();
+ if (!document || !document->IsHTMLDocument()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsHTMLDocument* html = document->AsHTMLDocument();
+ // See if the given RP ID is a valid domain string.
+ // (We use the document's URI here as a template so we don't have to come up
+ // with our own scheme, etc. If we can successfully set the host as the given
+ // RP ID, then it should be a valid domain string.)
+ nsCOMPtr<nsIURI> inputRpIdURI;
+ nsresult rv = NS_MutateURI(uri)
+ .SetHost(NS_ConvertUTF16toUTF8(aInputRpId))
+ .Finalize(inputRpIdURI);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsAutoCString inputRpId;
+ if (NS_FAILED(inputRpIdURI->GetAsciiHost(inputRpId))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!html->IsRegistrableDomainSuffixOfOrEqualTo(
+ NS_ConvertUTF8toUTF16(inputRpId), originHost)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ aRelaxedRpId.Assign(inputRpId);
+ return NS_OK;
+}
+
+/***********************************************************************
+ * WebAuthnManager Implementation
+ **********************************************************************/
+
+void WebAuthnManager::ClearTransaction() {
+ mTransaction.reset();
+ Unfollow();
+}
+
+void WebAuthnManager::CancelParent() {
+ if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
+ mChild->SendRequestCancel(mTransaction.ref().mId);
+ }
+}
+
+WebAuthnManager::~WebAuthnManager() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mTransaction.isSome()) {
+ ClearTransaction();
+ }
+
+ if (mChild) {
+ RefPtr<WebAuthnTransactionChild> c;
+ mChild.swap(c);
+ c->Disconnect();
+ }
+}
+
+already_AddRefed<Promise> WebAuthnManager::MakeCredential(
+ const PublicKeyCredentialCreationOptions& aOptions,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (mTransaction.isSome()) {
+ // abort the old transaction and take over control from here.
+ CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ nsString origin;
+ nsCString rpId;
+ nsresult rv = GetOrigin(mParent, origin, rpId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ // Enforce 5.4.3 User Account Parameters for Credential Generation
+ // When we add UX, we'll want to do more with this value, but for now
+ // we just have to verify its correctness.
+
+ CryptoBuffer userId;
+ userId.Assign(aOptions.mUser.mId);
+ if (userId.Length() > 64) {
+ promise->MaybeRejectWithTypeError("user.id is too long");
+ return promise.forget();
+ }
+
+ // 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);
+ }
+
+ if (aOptions.mRp.mId.WasPassed()) {
+ // If rpId is specified, then invoke the procedure used for relaxing the
+ // same-origin restriction by setting the document.domain attribute, using
+ // rpId as the given value but without changing the current document’s
+ // domain. If no errors are thrown, set rpId to the value of host as
+ // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRp.mId.Value(), rpId))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+ }
+
+ // <https://w3c.github.io/webauthn/#sctn-appid-extension>
+ if (aOptions.mExtensions.mAppid.WasPassed()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+ }
+
+ // Process each element of mPubKeyCredParams using the following steps, to
+ // produce a new sequence of coseAlgos.
+ nsTArray<CoseAlg> coseAlgos;
+ // If pubKeyCredParams is empty, append ES256 and RS256
+ if (aOptions.mPubKeyCredParams.IsEmpty()) {
+ coseAlgos.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::ES256));
+ coseAlgos.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::RS256));
+ } else {
+ for (size_t a = 0; a < aOptions.mPubKeyCredParams.Length(); ++a) {
+ // If current.type does not contain a PublicKeyCredentialType
+ // supported by this implementation, then stop processing current and move
+ // on to the next element in mPubKeyCredParams.
+ if (!aOptions.mPubKeyCredParams[a].mType.EqualsLiteral(
+ MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY)) {
+ continue;
+ }
+
+ coseAlgos.AppendElement(aOptions.mPubKeyCredParams[a].mAlg);
+ }
+ }
+
+ // If there are algorithms specified, but none are Public_key algorithms,
+ // reject the promise.
+ if (coseAlgos.IsEmpty() && !aOptions.mPubKeyCredParams.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+ }
+
+ // If excludeList is undefined, set it to the empty list.
+ //
+ // If extensions was specified, process any extensions supported by this
+ // client platform, to produce the extension data that needs to be sent to the
+ // authenticator. If an error is encountered while processing an extension,
+ // skip that extension and do not produce any extension data for it. Call the
+ // result of this processing clientExtensions.
+ //
+ // Currently no extensions are supported
+ //
+ // Use attestationChallenge, callerOrigin and rpId, along with the token
+ // binding key associated with callerOrigin (if any), to create a ClientData
+ // structure representing this request. Choose a hash algorithm for hashAlg
+ // and compute the clientDataJSON and clientDataHash.
+
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aOptions.mChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ nsresult srv = AssembleClientData(origin, challenge, u"webauthn.create"_ns,
+ aOptions.mExtensions, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsTArray<WebAuthnScopedCredential> excludeList;
+ for (const auto& s : aOptions.mExcludeCredentials) {
+ WebAuthnScopedCredential c;
+ CryptoBuffer cb;
+ cb.Assign(s.mId);
+ c.id() = cb;
+ if (s.mTransports.WasPassed()) {
+ c.transports() = SerializeTransports(s.mTransports.Value());
+ }
+ excludeList.AppendElement(c);
+ }
+
+ if (!MaybeCreateBackgroundActor()) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ // TODO: Add extension list building
+ nsTArray<WebAuthnExtension> extensions;
+
+ // <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension>
+ if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
+ bool hmacCreateSecret = aOptions.mExtensions.mHmacCreateSecret.Value();
+ if (hmacCreateSecret) {
+ extensions.AppendElement(WebAuthnExtensionHmacSecret(hmacCreateSecret));
+ }
+ }
+
+ if (aOptions.mExtensions.mCredProps.WasPassed()) {
+ bool credProps = aOptions.mExtensions.mCredProps.Value();
+ if (credProps) {
+ extensions.AppendElement(WebAuthnExtensionCredProps(credProps));
+ }
+ }
+
+ if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+ bool minPinLength = aOptions.mExtensions.mMinPinLength.Value();
+ if (minPinLength) {
+ extensions.AppendElement(WebAuthnExtensionMinPinLength(minPinLength));
+ }
+ }
+
+ const auto& selection = aOptions.mAuthenticatorSelection;
+ const auto& attachment = selection.mAuthenticatorAttachment;
+ const nsString& attestation = aOptions.mAttestation;
+
+ // Attachment
+ Maybe<nsString> authenticatorAttachment;
+ if (attachment.WasPassed()) {
+ authenticatorAttachment.emplace(attachment.Value());
+ }
+
+ // The residentKey field was added in WebAuthn level 2. It takes precedent
+ // over the requireResidentKey field if and only if it is present and it is a
+ // member of the ResidentKeyRequirement enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ bool useResidentKeyValue =
+ selection.mResidentKey.WasPassed() &&
+ (selection.mResidentKey.Value().EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED) ||
+ selection.mResidentKey.Value().EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED) ||
+ selection.mResidentKey.Value().EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED));
+
+ nsString residentKey;
+ if (useResidentKeyValue) {
+ residentKey = selection.mResidentKey.Value();
+ } else {
+ // "If no value is given then the effective value is required if
+ // requireResidentKey is true or discouraged if it is false or absent."
+ if (selection.mRequireResidentKey) {
+ residentKey.AssignLiteral(MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED);
+ } else {
+ residentKey.AssignLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED);
+ }
+ }
+
+ // Create and forward authenticator selection criteria.
+ WebAuthnAuthenticatorSelection authSelection(
+ residentKey, selection.mUserVerification, authenticatorAttachment);
+
+ WebAuthnMakeCredentialRpInfo rpInfo(aOptions.mRp.mName);
+
+ WebAuthnMakeCredentialUserInfo userInfo(userId, aOptions.mUser.mName,
+ aOptions.mUser.mDisplayName);
+
+ BrowsingContext* context = mParent->GetBrowsingContext();
+ if (!context) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ // 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);
+ promise->MaybeReject(reason);
+ return promise.forget();
+ }
+
+ WebAuthnMakeCredentialInfo info(
+ origin, NS_ConvertUTF8toUTF16(rpId), challenge, clientDataJSON,
+ adjustedTimeout, excludeList, rpInfo, userInfo, coseAlgos, extensions,
+ authSelection, attestation, context->Top()->Id());
+
+ // Set up the transaction state. Fallible operations should not be performed
+ // below this line, as we must not leave the transaction state partially
+ // initialized. Once the transaction state is initialized the only valid ways
+ // to end the transaction are CancelTransaction, RejectTransaction, and
+ // FinishMakeCredential.
+ AbortSignal* signal = nullptr;
+ if (aSignal.WasPassed()) {
+ signal = &aSignal.Value();
+ Follow(signal);
+ }
+
+ MOZ_ASSERT(mTransaction.isNothing());
+ mTransaction = Some(WebAuthnTransaction(promise));
+ mChild->SendRequestRegister(mTransaction.ref().mId, info);
+
+ return promise.forget();
+}
+
+const size_t MAX_ALLOWED_CREDENTIALS = 20;
+
+already_AddRefed<Promise> WebAuthnManager::GetAssertion(
+ const PublicKeyCredentialRequestOptions& aOptions,
+ const bool aConditionallyMediated,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (mTransaction.isSome()) {
+ // abort the old transaction and take over control from here.
+ CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ nsString origin;
+ nsCString rpId;
+ nsresult rv = GetOrigin(mParent, origin, rpId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ // 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);
+ }
+
+ if (aOptions.mRpId.WasPassed()) {
+ // If rpId is specified, then invoke the procedure used for relaxing the
+ // same-origin restriction by setting the document.domain attribute, using
+ // rpId as the given value but without changing the current document’s
+ // domain. If no errors are thrown, set rpId to the value of host as
+ // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRpId.Value(), rpId))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+ }
+
+ // Abort the request if the allowCredentials set is too large
+ if (aOptions.mAllowCredentials.Length() > MAX_ALLOWED_CREDENTIALS) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // Use assertionChallenge, callerOrigin and rpId, along with the token binding
+ // key associated with callerOrigin (if any), to create a ClientData structure
+ // representing this request. Choose a hash algorithm for hashAlg and compute
+ // the clientDataJSON and clientDataHash.
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aOptions.mChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ rv = AssembleClientData(origin, challenge, u"webauthn.get"_ns,
+ aOptions.mExtensions, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsTArray<WebAuthnScopedCredential> allowList;
+ for (const auto& s : aOptions.mAllowCredentials) {
+ if (s.mType.EqualsLiteral(
+ MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY)) {
+ WebAuthnScopedCredential c;
+ CryptoBuffer cb;
+ cb.Assign(s.mId);
+ c.id() = cb;
+ if (s.mTransports.WasPassed()) {
+ c.transports() = SerializeTransports(s.mTransports.Value());
+ }
+ allowList.AppendElement(c);
+ }
+ }
+ if (allowList.Length() == 0 && aOptions.mAllowCredentials.Length() != 0) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ if (!MaybeCreateBackgroundActor()) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ // If extensions were specified, process any extensions supported by this
+ // client platform, to produce the extension data that needs to be sent to the
+ // authenticator. If an error is encountered while processing an extension,
+ // skip that extension and do not produce any extension data for it. Call the
+ // result of this processing clientExtensions.
+ nsTArray<WebAuthnExtension> extensions;
+
+ // credProps is only supported in MakeCredentials
+ if (aOptions.mExtensions.mCredProps.WasPassed()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+ }
+
+ // minPinLength is only supported in MakeCredentials
+ if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+ }
+
+ // <https://w3c.github.io/webauthn/#sctn-appid-extension>
+ if (aOptions.mExtensions.mAppid.WasPassed()) {
+ nsString appId(aOptions.mExtensions.mAppid.Value());
+
+ // Check that the appId value is allowed.
+ if (!EvaluateAppID(mParent, origin, appId)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // Append the hash and send it to the backend.
+ extensions.AppendElement(WebAuthnExtensionAppId(appId));
+ }
+
+ BrowsingContext* context = mParent->GetBrowsingContext();
+ if (!context) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ // 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);
+ promise->MaybeReject(reason);
+ return promise.forget();
+ }
+
+ WebAuthnGetAssertionInfo info(origin, NS_ConvertUTF8toUTF16(rpId), challenge,
+ clientDataJSON, adjustedTimeout, allowList,
+ extensions, aOptions.mUserVerification,
+ aConditionallyMediated, context->Top()->Id());
+
+ // Set up the transaction state. Fallible operations should not be performed
+ // below this line, as we must not leave the transaction state partially
+ // initialized. Once the transaction state is initialized the only valid ways
+ // to end the transaction are CancelTransaction, RejectTransaction, and
+ // FinishGetAssertion.
+ AbortSignal* signal = nullptr;
+ if (aSignal.WasPassed()) {
+ signal = &aSignal.Value();
+ Follow(signal);
+ }
+
+ MOZ_ASSERT(mTransaction.isNothing());
+ mTransaction = Some(WebAuthnTransaction(promise));
+ mChild->SendRequestSign(mTransaction.ref().mId, info);
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> WebAuthnManager::Store(const Credential& aCredential,
+ ErrorResult& aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (mTransaction.isSome()) {
+ // abort the old transaction and take over control from here.
+ CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
+ }
+
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+}
+
+already_AddRefed<Promise> WebAuthnManager::IsUVPAA(GlobalObject& aGlobal,
+ ErrorResult& aError) {
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aGlobal.Context()), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (!MaybeCreateBackgroundActor()) {
+ promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return promise.forget();
+ }
+
+ mChild->SendRequestIsUVPAA()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const PWebAuthnTransactionChild::RequestIsUVPAAPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ promise->MaybeResolve(aValue.ResolveValue());
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+ });
+ return promise.forget();
+}
+
+void WebAuthnManager::FinishMakeCredential(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialResult& aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Check for a valid transaction.
+ if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
+ return;
+ }
+
+ nsAutoCString keyHandleBase64Url;
+ nsresult rv = Base64URLEncode(
+ aResult.KeyHandle().Length(), aResult.KeyHandle().Elements(),
+ Base64URLEncodePaddingPolicy::Omit, keyHandleBase64Url);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectTransaction(rv);
+ return;
+ }
+
+ // Create a new PublicKeyCredential object and populate its fields with the
+ // values returned from the authenticator as well as the clientDataJSON
+ // computed earlier.
+ RefPtr<AuthenticatorAttestationResponse> attestation =
+ new AuthenticatorAttestationResponse(mParent);
+ attestation->SetClientDataJSON(aResult.ClientDataJSON());
+ attestation->SetAttestationObject(aResult.AttestationObject());
+ attestation->SetTransports(aResult.Transports());
+
+ RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
+ credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
+ credential->SetType(u"public-key"_ns);
+ credential->SetRawId(aResult.KeyHandle());
+ credential->SetAttestationResponse(attestation);
+ credential->SetAuthenticatorAttachment(aResult.AuthenticatorAttachment());
+
+ // Forward client extension results.
+ for (const auto& ext : aResult.Extensions()) {
+ if (ext.type() ==
+ WebAuthnExtensionResult::TWebAuthnExtensionResultCredProps) {
+ bool credPropsRk = ext.get_WebAuthnExtensionResultCredProps().rk();
+ credential->SetClientExtensionResultCredPropsRk(credPropsRk);
+ }
+ if (ext.type() ==
+ WebAuthnExtensionResult::TWebAuthnExtensionResultHmacSecret) {
+ bool hmacCreateSecret =
+ ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
+ credential->SetClientExtensionResultHmacSecret(hmacCreateSecret);
+ }
+ }
+
+ mTransaction.ref().mPromise->MaybeResolve(credential);
+ ClearTransaction();
+}
+
+void WebAuthnManager::FinishGetAssertion(
+ const uint64_t& aTransactionId, const WebAuthnGetAssertionResult& aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Check for a valid transaction.
+ if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
+ return;
+ }
+
+ nsAutoCString keyHandleBase64Url;
+ nsresult rv = Base64URLEncode(
+ aResult.KeyHandle().Length(), aResult.KeyHandle().Elements(),
+ Base64URLEncodePaddingPolicy::Omit, keyHandleBase64Url);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RejectTransaction(rv);
+ return;
+ }
+
+ // Create a new PublicKeyCredential object named value and populate its fields
+ // with the values returned from the authenticator as well as the
+ // clientDataJSON computed earlier.
+ RefPtr<AuthenticatorAssertionResponse> assertion =
+ new AuthenticatorAssertionResponse(mParent);
+ assertion->SetClientDataJSON(aResult.ClientDataJSON());
+ assertion->SetAuthenticatorData(aResult.AuthenticatorData());
+ assertion->SetSignature(aResult.Signature());
+ assertion->SetUserHandle(aResult.UserHandle()); // may be empty
+
+ RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
+ credential->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url));
+ credential->SetType(u"public-key"_ns);
+ credential->SetRawId(aResult.KeyHandle());
+ credential->SetAssertionResponse(assertion);
+ credential->SetAuthenticatorAttachment(aResult.AuthenticatorAttachment());
+
+ // Forward client extension results.
+ for (const auto& ext : aResult.Extensions()) {
+ if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId) {
+ bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
+ credential->SetClientExtensionResultAppId(appid);
+ }
+ }
+
+ mTransaction.ref().mPromise->MaybeResolve(credential);
+ ClearTransaction();
+}
+
+void WebAuthnManager::RequestAborted(const uint64_t& aTransactionId,
+ const nsresult& aError) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
+ RejectTransaction(aError);
+ }
+}
+
+void WebAuthnManager::RunAbortAlgorithm() {
+ if (NS_WARN_IF(mTransaction.isNothing())) {
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> reason(cx);
+ Signal()->GetReason(cx, &reason);
+ CancelTransaction(reason);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnManager.h b/dom/webauthn/WebAuthnManager.h
new file mode 100644
index 0000000000..f60635ec88
--- /dev/null
+++ b/dom/webauthn/WebAuthnManager.h
@@ -0,0 +1,160 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnManager_h
+#define mozilla_dom_WebAuthnManager_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "mozilla/dom/WebAuthnManagerBase.h"
+
+/*
+ * Content process manager for the WebAuthn protocol. Created on calls to the
+ * WebAuthentication DOM object, this manager handles establishing IPC channels
+ * for WebAuthn transactions, as well as keeping track of JS Promise objects
+ * representing transactions in flight.
+ *
+ * The WebAuthn spec (https://www.w3.org/TR/webauthn/) allows for two different
+ * types of transactions: registration and signing. When either of these is
+ * requested via the DOM API, the following steps are executed in the
+ * WebAuthnManager:
+ *
+ * - Validation of the request. Return a failed promise to js if request does
+ * not have correct parameters.
+ *
+ * - If request is valid, open a new IPC channel for running the transaction. If
+ * another transaction is already running in this content process, cancel it.
+ * Return a pending promise to js.
+ *
+ * - Send transaction information to parent process (by running the Start*
+ * functions of WebAuthnManager). Assuming another transaction is currently in
+ * flight in another content process, parent will handle canceling it.
+ *
+ * - On return of successful transaction information from parent process, turn
+ * information into DOM object format required by spec, and resolve promise
+ * (by running the Finish* functions of WebAuthnManager). On cancellation
+ * request from parent, reject promise with corresponding error code. Either
+ * outcome will also close the IPC channel.
+ *
+ */
+
+namespace mozilla::dom {
+
+class Credential;
+
+class WebAuthnTransaction {
+ public:
+ explicit WebAuthnTransaction(const RefPtr<Promise>& aPromise)
+ : mPromise(aPromise), mId(NextId()) {
+ MOZ_ASSERT(mId > 0);
+ }
+
+ // JS Promise representing the transaction status.
+ RefPtr<Promise> mPromise;
+
+ // Unique transaction id.
+ uint64_t mId;
+
+ private:
+ // Generates a probabilistically unique ID for the new transaction. IDs are 53
+ // bits, as they are used in javascript. We use a random value if possible,
+ // otherwise a counter.
+ static uint64_t NextId() {
+ static uint64_t counter = 0;
+ Maybe<uint64_t> rand = mozilla::RandomUint64();
+ uint64_t id =
+ rand.valueOr(++counter) & UINT64_C(0x1fffffffffffff); // 2^53 - 1
+ // The transaction ID 0 is reserved.
+ return id ? id : 1;
+ }
+};
+
+class WebAuthnManager final : public WebAuthnManagerBase, public AbortFollower {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebAuthnManager, WebAuthnManagerBase)
+
+ explicit WebAuthnManager(nsPIDOMWindowInner* aParent)
+ : WebAuthnManagerBase(aParent) {}
+
+ already_AddRefed<Promise> MakeCredential(
+ const PublicKeyCredentialCreationOptions& aOptions,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
+
+ already_AddRefed<Promise> GetAssertion(
+ const PublicKeyCredentialRequestOptions& aOptions,
+ const bool aConditionallyMediated,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
+
+ already_AddRefed<Promise> Store(const Credential& aCredential,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> IsUVPAA(GlobalObject& aGlobal, ErrorResult& aError);
+
+ // WebAuthnManagerBase
+
+ void FinishMakeCredential(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialResult& aResult) override;
+
+ void FinishGetAssertion(const uint64_t& aTransactionId,
+ const WebAuthnGetAssertionResult& aResult) override;
+
+ void RequestAborted(const uint64_t& aTransactionId,
+ const nsresult& aError) override;
+
+ // AbortFollower
+
+ void RunAbortAlgorithm() override;
+
+ private:
+ virtual ~WebAuthnManager();
+
+ // Send a Cancel message to the parent, reject the promise with the given
+ // reason (an nsresult or JS::Handle<JS::Value>), and clear the transaction.
+ template <typename T>
+ void CancelTransaction(const T& aReason) {
+ CancelParent();
+ RejectTransaction(aReason);
+ }
+
+ // Reject the promise with the given reason (an nsresult or JS::Value), and
+ // clear the transaction.
+ template <typename T>
+ void RejectTransaction(const T& aReason) {
+ if (!NS_WARN_IF(mTransaction.isNothing())) {
+ mTransaction.ref().mPromise->MaybeReject(aReason);
+ }
+
+ ClearTransaction();
+ }
+
+ // Send a Cancel message to the parent.
+ void CancelParent();
+
+ // Clears all information we have about the current transaction.
+ void ClearTransaction();
+
+ // The current transaction, if any.
+ Maybe<WebAuthnTransaction> mTransaction;
+};
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ WebAuthnTransaction& aTransaction, const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aTransaction.mPromise, aName, aFlags);
+}
+
+inline void ImplCycleCollectionUnlink(WebAuthnTransaction& aTransaction) {
+ ImplCycleCollectionUnlink(aTransaction.mPromise);
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnManager_h
diff --git a/dom/webauthn/WebAuthnManagerBase.cpp b/dom/webauthn/WebAuthnManagerBase.cpp
new file mode 100644
index 0000000000..392be93076
--- /dev/null
+++ b/dom/webauthn/WebAuthnManagerBase.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "mozilla/dom/WebAuthnManagerBase.h"
+#include "mozilla/dom/WebAuthnTransactionChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+namespace mozilla::dom {
+
+WebAuthnManagerBase::WebAuthnManagerBase(nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aParent);
+}
+
+WebAuthnManagerBase::~WebAuthnManagerBase() { MOZ_ASSERT(NS_IsMainThread()); }
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnManagerBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(WebAuthnManagerBase, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnManagerBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnManagerBase)
+
+/***********************************************************************
+ * IPC Protocol Implementation
+ **********************************************************************/
+
+bool WebAuthnManagerBase::MaybeCreateBackgroundActor() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mChild) {
+ return true;
+ }
+
+ ::mozilla::ipc::PBackgroundChild* actorChild =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return false;
+ }
+
+ RefPtr<WebAuthnTransactionChild> mgr(new WebAuthnTransactionChild(this));
+ PWebAuthnTransactionChild* constructedMgr =
+ actorChild->SendPWebAuthnTransactionConstructor(mgr);
+
+ if (NS_WARN_IF(!constructedMgr)) {
+ return false;
+ }
+
+ MOZ_ASSERT(constructedMgr == mgr);
+ mChild = std::move(mgr);
+
+ return true;
+}
+
+void WebAuthnManagerBase::ActorDestroyed() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mChild = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnManagerBase.h b/dom/webauthn/WebAuthnManagerBase.h
new file mode 100644
index 0000000000..0958fb59b3
--- /dev/null
+++ b/dom/webauthn/WebAuthnManagerBase.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnManagerBase_h
+#define mozilla_dom_WebAuthnManagerBase_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+/*
+ * A base class used by WebAuthn and U2F implementations, providing shared
+ * functionality and requiring an interface used by the IPC child actors.
+ */
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class WebAuthnTransactionChild;
+class WebAuthnMakeCredentialResult;
+class WebAuthnGetAssertionResult;
+
+class WebAuthnManagerBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(WebAuthnManagerBase)
+
+ explicit WebAuthnManagerBase(nsPIDOMWindowInner* aParent);
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void FinishMakeCredential(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialResult& aResult) = 0;
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void FinishGetAssertion(
+ const uint64_t& aTransactionId,
+ const WebAuthnGetAssertionResult& aResult) = 0;
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void RequestAborted(const uint64_t& aTransactionId,
+ const nsresult& aError) = 0;
+
+ void ActorDestroyed();
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT virtual ~WebAuthnManagerBase();
+
+ bool MaybeCreateBackgroundActor();
+
+ // The parent window.
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+
+ // IPC Channel to the parent process.
+ RefPtr<WebAuthnTransactionChild> mChild;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnManagerBase_h
diff --git a/dom/webauthn/WebAuthnPromiseHolder.cpp b/dom/webauthn/WebAuthnPromiseHolder.cpp
new file mode 100644
index 0000000000..60dff7f1c2
--- /dev/null
+++ b/dom/webauthn/WebAuthnPromiseHolder.cpp
@@ -0,0 +1,88 @@
+/* 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 "mozilla/AppShutdown.h"
+#include "WebAuthnPromiseHolder.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(WebAuthnRegisterPromiseHolder, nsIWebAuthnRegisterPromise);
+
+already_AddRefed<WebAuthnRegisterPromise>
+WebAuthnRegisterPromiseHolder::Ensure() {
+ return mRegisterPromise.Ensure(__func__);
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterPromiseHolder::Resolve(nsIWebAuthnRegisterResult* aResult) {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // Resolve the promise on its owning thread if Disconnect() has not been
+ // called.
+ RefPtr<nsIWebAuthnRegisterResult> result(aResult);
+ mEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebAuthnRegisterPromiseHolder::Resolve",
+ [self = RefPtr{this}, result]() {
+ self->mRegisterPromise.ResolveIfExists(result, __func__);
+ }));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterPromiseHolder::Reject(nsresult aResult) {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // Reject the promise on its owning thread if Disconnect() has not been
+ // called.
+ mEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebAuthnRegisterPromiseHolder::Reject",
+ [self = RefPtr{this}, aResult]() {
+ self->mRegisterPromise.RejectIfExists(aResult, __func__);
+ }));
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(WebAuthnSignPromiseHolder, nsIWebAuthnSignPromise);
+
+already_AddRefed<WebAuthnSignPromise> WebAuthnSignPromiseHolder::Ensure() {
+ return mSignPromise.Ensure(__func__);
+}
+
+NS_IMETHODIMP
+WebAuthnSignPromiseHolder::Resolve(nsIWebAuthnSignResult* aResult) {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // Resolve the promise on its owning thread if Disconnect() has not been
+ // called.
+ RefPtr<nsIWebAuthnSignResult> result(aResult);
+ mEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebAuthnSignPromiseHolder::Resolve", [self = RefPtr{this}, result]() {
+ self->mSignPromise.ResolveIfExists(result, __func__);
+ }));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignPromiseHolder::Reject(nsresult aResult) {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ // Reject the promise on its owning thread if Disconnect() has not been
+ // called.
+ mEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebAuthnSignPromiseHolder::Reject", [self = RefPtr{this}, aResult]() {
+ self->mSignPromise.RejectIfExists(aResult, __func__);
+ }));
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnPromiseHolder.h b/dom/webauthn/WebAuthnPromiseHolder.h
new file mode 100644
index 0000000000..ca70fedb14
--- /dev/null
+++ b/dom/webauthn/WebAuthnPromiseHolder.h
@@ -0,0 +1,69 @@
+/* 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/. */
+
+#ifndef mozilla_dom_WebAuthnPromiseHolder_h
+#define mozilla_dom_WebAuthnPromiseHolder_h
+
+#include "mozilla/MozPromise.h"
+#include "nsIWebAuthnResult.h"
+#include "nsIWebAuthnPromise.h"
+#include "nsIThread.h"
+
+namespace mozilla::dom {
+
+/*
+ * WebAuthnRegisterPromiseHolder and WebAuthnSignPromiseHolder wrap a
+ * MozPromiseHolder with an XPCOM interface that allows the contained promise
+ * to be resolved, rejected, or disconnected safely from any thread.
+ *
+ * Calls to Resolve(), Reject(), and Disconnect() are dispatched to the serial
+ * event target with wich the promise holder was initialized. At most one of
+ * these calls will be processed; the first call to reach the event target
+ * wins. Once the promise is initialized with Ensure() the program MUST call
+ * at least one of Resolve(), Reject(), or Disconnect().
+ */
+
+using WebAuthnRegisterPromise =
+ MozPromise<RefPtr<nsIWebAuthnRegisterResult>, nsresult, true>;
+
+using WebAuthnSignPromise =
+ MozPromise<RefPtr<nsIWebAuthnSignResult>, nsresult, true>;
+
+class WebAuthnRegisterPromiseHolder final : public nsIWebAuthnRegisterPromise {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNREGISTERPROMISE
+
+ explicit WebAuthnRegisterPromiseHolder(nsISerialEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget) {}
+
+ already_AddRefed<WebAuthnRegisterPromise> Ensure();
+
+ private:
+ ~WebAuthnRegisterPromiseHolder() = default;
+
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ MozPromiseHolder<WebAuthnRegisterPromise> mRegisterPromise;
+};
+
+class WebAuthnSignPromiseHolder final : public nsIWebAuthnSignPromise {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSIGNPROMISE
+
+ explicit WebAuthnSignPromiseHolder(nsISerialEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget) {}
+
+ already_AddRefed<WebAuthnSignPromise> Ensure();
+
+ private:
+ ~WebAuthnSignPromiseHolder() = default;
+
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ MozPromiseHolder<WebAuthnSignPromise> mSignPromise;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnPromiseHolder_h
diff --git a/dom/webauthn/WebAuthnResult.cpp b/dom/webauthn/WebAuthnResult.cpp
new file mode 100644
index 0000000000..268dd62f20
--- /dev/null
+++ b/dom/webauthn/WebAuthnResult.cpp
@@ -0,0 +1,192 @@
+/* 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 "AuthrsBridge_ffi.h"
+#include "WebAuthnResult.h"
+#include "nsIWebAuthnAttObj.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+namespace mozilla::jni {
+
+template <>
+RefPtr<dom::WebAuthnRegisterResult> Java2Native(
+ mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(
+ aData.IsInstanceOf<java::WebAuthnTokenManager::MakeCredentialResponse>());
+ java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef response(aData);
+ RefPtr<dom::WebAuthnRegisterResult> result =
+ new dom::WebAuthnRegisterResult(response);
+ return result;
+}
+
+template <>
+RefPtr<dom::WebAuthnSignResult> Java2Native(mozilla::jni::Object::Param aData,
+ JNIEnv* aEnv) {
+ MOZ_ASSERT(
+ aData.IsInstanceOf<java::WebAuthnTokenManager::GetAssertionResponse>());
+ java::WebAuthnTokenManager::GetAssertionResponse::LocalRef response(aData);
+ RefPtr<dom::WebAuthnSignResult> result =
+ new dom::WebAuthnSignResult(response);
+ return result;
+}
+
+} // namespace mozilla::jni
+#endif
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(WebAuthnRegisterResult, nsIWebAuthnRegisterResult)
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetClientDataJSON(nsACString& aClientDataJSON) {
+ if (mClientDataJSON.isSome()) {
+ aClientDataJSON = *mClientDataJSON;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetAttestationObject(
+ nsTArray<uint8_t>& aAttestationObject) {
+ aAttestationObject.Assign(mAttestationObject);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetCredentialId(nsTArray<uint8_t>& aCredentialId) {
+ aCredentialId.Assign(mCredentialId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetTransports(nsTArray<nsString>& aTransports) {
+ aTransports.Assign(mTransports);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetHmacCreateSecret(bool* aHmacCreateSecret) {
+ if (mHmacCreateSecret.isSome()) {
+ *aHmacCreateSecret = mHmacCreateSecret.ref();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetCredPropsRk(bool* aCredPropsRk) {
+ if (mCredPropsRk.isSome()) {
+ *aCredPropsRk = mCredPropsRk.ref();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::SetCredPropsRk(bool aCredPropsRk) {
+ mCredPropsRk = Some(aCredPropsRk);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnRegisterResult::GetAuthenticatorAttachment(
+ nsAString& aAuthenticatorAttachment) {
+ if (mAuthenticatorAttachment.isSome()) {
+ aAuthenticatorAttachment = mAuthenticatorAttachment.ref();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult WebAuthnRegisterResult::Anonymize() {
+ // The anonymize flag in the nsIWebAuthnAttObj constructor causes the
+ // attestation statement to be removed during deserialization. It also
+ // causes the AAGUID to be zeroed out. If we can't deserialize the
+ // existing attestation, then we can't ensure that it is anonymized, so we
+ // act as though the user denied consent and we return NotAllowed.
+ nsCOMPtr<nsIWebAuthnAttObj> anonymizedAttObj;
+ nsresult rv = authrs_webauthn_att_obj_constructor(
+ mAttestationObject,
+ /* anonymize */ true, getter_AddRefs(anonymizedAttObj));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mAttestationObject.Clear();
+ rv = anonymizedAttObj->GetAttestationObject(mAttestationObject);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(WebAuthnSignResult, nsIWebAuthnSignResult)
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetClientDataJSON(nsACString& aClientDataJSON) {
+ if (mClientDataJSON.isSome()) {
+ aClientDataJSON = *mClientDataJSON;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetAuthenticatorData(
+ nsTArray<uint8_t>& aAuthenticatorData) {
+ aAuthenticatorData.Assign(mAuthenticatorData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetCredentialId(nsTArray<uint8_t>& aCredentialId) {
+ aCredentialId.Assign(mCredentialId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetSignature(nsTArray<uint8_t>& aSignature) {
+ aSignature.Assign(mSignature);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetUserHandle(nsTArray<uint8_t>& aUserHandle) {
+ aUserHandle.Assign(mUserHandle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetUserName(nsACString& aUserName) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetUsedAppId(bool* aUsedAppId) {
+ if (mUsedAppId.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aUsedAppId = mUsedAppId.ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::SetUsedAppId(bool aUsedAppId) {
+ mUsedAppId = Some(aUsedAppId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnSignResult::GetAuthenticatorAttachment(
+ nsAString& aAuthenticatorAttachment) {
+ if (mAuthenticatorAttachment.isSome()) {
+ aAuthenticatorAttachment = mAuthenticatorAttachment.ref();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnResult.h b/dom/webauthn/WebAuthnResult.h
new file mode 100644
index 0000000000..f7653fd4b0
--- /dev/null
+++ b/dom/webauthn/WebAuthnResult.h
@@ -0,0 +1,229 @@
+/* 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/. */
+
+#ifndef mozilla_dom_WebAuthnResult_h_
+#define mozilla_dom_WebAuthnResult_h_
+
+#include "nsIWebAuthnResult.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/Maybe.h"
+#include "nsString.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/java/WebAuthnTokenManagerNatives.h"
+#endif
+
+#ifdef XP_WIN
+# include <windows.h>
+# include "mozilla/dom/PWebAuthnTransactionParent.h"
+# include "winwebauthn/webauthn.h"
+#endif
+
+namespace mozilla::dom {
+
+class WebAuthnRegisterResult final : public nsIWebAuthnRegisterResult {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNREGISTERRESULT
+
+ WebAuthnRegisterResult(const nsTArray<uint8_t>& aAttestationObject,
+ const Maybe<nsCString>& aClientDataJSON,
+ const nsTArray<uint8_t>& aCredentialId,
+ const nsTArray<nsString>& aTransports,
+ const Maybe<nsString>& aAuthenticatorAttachment)
+ : mClientDataJSON(aClientDataJSON),
+ mCredPropsRk(Nothing()),
+ mAuthenticatorAttachment(aAuthenticatorAttachment) {
+ mAttestationObject.AppendElements(aAttestationObject);
+ mCredentialId.AppendElements(aCredentialId);
+ mTransports.AppendElements(aTransports);
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ explicit WebAuthnRegisterResult(
+ const java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef&
+ aResponse) {
+ mAttestationObject.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->AttestationObject()->GetElements().Elements()),
+ aResponse->AttestationObject()->Length());
+ mClientDataJSON = Some(nsAutoCString(
+ reinterpret_cast<const char*>(
+ aResponse->ClientDataJson()->GetElements().Elements()),
+ aResponse->ClientDataJson()->Length()));
+ mCredentialId.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->KeyHandle()->GetElements().Elements()),
+ aResponse->KeyHandle()->Length());
+ auto transports = aResponse->Transports();
+ for (size_t i = 0; i < transports->Length(); i++) {
+ mTransports.AppendElement(
+ jni::String::LocalRef(transports->GetElement(i))->ToString());
+ }
+ // authenticator attachment is not available on Android
+ mAuthenticatorAttachment = Nothing();
+ }
+#endif
+
+#ifdef XP_WIN
+ WebAuthnRegisterResult(nsCString& aClientDataJSON,
+ PCWEBAUTHN_CREDENTIAL_ATTESTATION aResponse)
+ : mClientDataJSON(Some(aClientDataJSON)) {
+ mCredentialId.AppendElements(aResponse->pbCredentialId,
+ aResponse->cbCredentialId);
+
+ mAttestationObject.AppendElements(aResponse->pbAttestationObject,
+ aResponse->cbAttestationObject);
+
+ nsTArray<WebAuthnExtensionResult> extensions;
+ if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2) {
+ PCWEBAUTHN_EXTENSIONS pExtensionList = &aResponse->Extensions;
+ if (pExtensionList->cExtensions != 0 &&
+ pExtensionList->pExtensions != NULL) {
+ for (DWORD dwIndex = 0; dwIndex < pExtensionList->cExtensions;
+ dwIndex++) {
+ PWEBAUTHN_EXTENSION pExtension =
+ &pExtensionList->pExtensions[dwIndex];
+ if (pExtension->pwszExtensionIdentifier &&
+ (0 == _wcsicmp(pExtension->pwszExtensionIdentifier,
+ WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET)) &&
+ pExtension->cbExtension == sizeof(BOOL)) {
+ BOOL* pCredentialCreatedWithHmacSecret =
+ (BOOL*)pExtension->pvExtension;
+ if (*pCredentialCreatedWithHmacSecret) {
+ mHmacCreateSecret = Some(true);
+ }
+ }
+ }
+ }
+ }
+
+ if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_USB) {
+ mTransports.AppendElement(u"usb"_ns);
+ }
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_NFC) {
+ mTransports.AppendElement(u"nfc"_ns);
+ }
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_BLE) {
+ mTransports.AppendElement(u"ble"_ns);
+ }
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_INTERNAL) {
+ mTransports.AppendElement(u"internal"_ns);
+ }
+ }
+ // WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 corresponds to
+ // WEBAUTHN_API_VERSION_6 which is where WEBAUTHN_CTAP_TRANSPORT_HYBRID was
+ // defined.
+ if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) {
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_HYBRID) {
+ mTransports.AppendElement(u"hybrid"_ns);
+ }
+ }
+
+ if (aResponse->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
+ if (aResponse->dwUsedTransport & WEBAUTHN_CTAP_TRANSPORT_INTERNAL) {
+ mAuthenticatorAttachment = Some(u"platform"_ns);
+ } else {
+ mAuthenticatorAttachment = Some(u"cross-platform"_ns);
+ }
+ }
+ }
+#endif
+
+ nsresult Anonymize();
+
+ private:
+ ~WebAuthnRegisterResult() = default;
+
+ nsTArray<uint8_t> mAttestationObject;
+ nsTArray<uint8_t> mCredentialId;
+ nsTArray<nsString> mTransports;
+ Maybe<nsCString> mClientDataJSON;
+ Maybe<bool> mCredPropsRk;
+ Maybe<bool> mHmacCreateSecret;
+ Maybe<nsString> mAuthenticatorAttachment;
+};
+
+class WebAuthnSignResult final : public nsIWebAuthnSignResult {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSIGNRESULT
+
+ WebAuthnSignResult(const nsTArray<uint8_t>& aAuthenticatorData,
+ const Maybe<nsCString>& aClientDataJSON,
+ const nsTArray<uint8_t>& aCredentialId,
+ const nsTArray<uint8_t>& aSignature,
+ const nsTArray<uint8_t>& aUserHandle,
+ const Maybe<nsString>& aAuthenticatorAttachment)
+ : mClientDataJSON(aClientDataJSON),
+ mAuthenticatorAttachment(aAuthenticatorAttachment) {
+ mAuthenticatorData.AppendElements(aAuthenticatorData);
+ mCredentialId.AppendElements(aCredentialId);
+ mSignature.AppendElements(aSignature);
+ mUserHandle.AppendElements(aUserHandle);
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ explicit WebAuthnSignResult(
+ const java::WebAuthnTokenManager::GetAssertionResponse::LocalRef&
+ aResponse) {
+ mAuthenticatorData.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->AuthData()->GetElements().Elements()),
+ aResponse->AuthData()->Length());
+ mClientDataJSON = Some(nsAutoCString(
+ reinterpret_cast<const char*>(
+ aResponse->ClientDataJson()->GetElements().Elements()),
+ aResponse->ClientDataJson()->Length()));
+ mCredentialId.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->KeyHandle()->GetElements().Elements()),
+ aResponse->KeyHandle()->Length());
+ mSignature.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->Signature()->GetElements().Elements()),
+ aResponse->Signature()->Length());
+ mUserHandle.AppendElements(
+ reinterpret_cast<uint8_t*>(
+ aResponse->UserHandle()->GetElements().Elements()),
+ aResponse->UserHandle()->Length());
+ // authenticator attachment is not available on Android
+ mAuthenticatorAttachment = Nothing();
+ }
+#endif
+
+#ifdef XP_WIN
+ WebAuthnSignResult(nsCString& aClientDataJSON, PCWEBAUTHN_ASSERTION aResponse)
+ : mClientDataJSON(Some(aClientDataJSON)) {
+ mSignature.AppendElements(aResponse->pbSignature, aResponse->cbSignature);
+
+ mCredentialId.AppendElements(aResponse->Credential.pbId,
+ aResponse->Credential.cbId);
+
+ mUserHandle.AppendElements(aResponse->pbUserId, aResponse->cbUserId);
+
+ mAuthenticatorData.AppendElements(aResponse->pbAuthenticatorData,
+ aResponse->cbAuthenticatorData);
+
+ mAuthenticatorAttachment = Nothing(); // not available
+ }
+#endif
+
+ private:
+ ~WebAuthnSignResult() = default;
+
+ nsTArray<uint8_t> mAuthenticatorData;
+ Maybe<nsCString> mClientDataJSON;
+ nsTArray<uint8_t> mCredentialId;
+ nsTArray<uint8_t> mSignature;
+ nsTArray<uint8_t> mUserHandle;
+ Maybe<nsString> mAuthenticatorAttachment;
+ Maybe<bool> mUsedAppId;
+};
+
+} // namespace mozilla::dom
+#endif // mozilla_dom_WebAuthnResult_h
diff --git a/dom/webauthn/WebAuthnService.cpp b/dom/webauthn/WebAuthnService.cpp
new file mode 100644
index 0000000000..3e1557edbc
--- /dev/null
+++ b/dom/webauthn/WebAuthnService.cpp
@@ -0,0 +1,220 @@
+/* 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 "mozilla/Services.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "WebAuthnService.h"
+#include "WebAuthnTransportIdentifiers.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<nsIWebAuthnService> NewWebAuthnService() {
+ nsCOMPtr<nsIWebAuthnService> webauthnService(new WebAuthnService());
+ return webauthnService.forget();
+}
+
+NS_IMPL_ISUPPORTS(WebAuthnService, nsIWebAuthnService)
+
+NS_IMETHODIMP
+WebAuthnService::MakeCredential(uint64_t aTransactionId,
+ uint64_t browsingContextId,
+ nsIWebAuthnRegisterArgs* aArgs,
+ nsIWebAuthnRegisterPromise* aPromise) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ guard->ref().service->Reset();
+ *guard = Nothing();
+ }
+ *guard = Some(TransactionState{DefaultService()});
+ return guard->ref().service->MakeCredential(aTransactionId, browsingContextId,
+ aArgs, aPromise);
+}
+
+NS_IMETHODIMP
+WebAuthnService::GetAssertion(uint64_t aTransactionId,
+ uint64_t browsingContextId,
+ nsIWebAuthnSignArgs* aArgs,
+ nsIWebAuthnSignPromise* aPromise) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ guard->ref().service->Reset();
+ *guard = Nothing();
+ }
+ *guard = Some(TransactionState{DefaultService()});
+ nsresult rv;
+
+#if defined(XP_MACOSX)
+ // The macOS security key API doesn't handle the AppID extension. So we'll
+ // use authenticator-rs if it's likely that the request requires AppID. We
+ // consider it likely if 1) the AppID extension is present, 2) the allow list
+ // is non-empty, and 3) none of the allowed credentials use the
+ // "internal" or "hybrid" transport.
+ nsString appId;
+ rv = aArgs->GetAppId(appId);
+ if (rv == NS_OK) { // AppID is set
+ uint8_t transportSet = 0;
+ nsTArray<uint8_t> allowListTransports;
+ Unused << aArgs->GetAllowListTransports(allowListTransports);
+ for (const uint8_t& transport : allowListTransports) {
+ transportSet |= transport;
+ }
+ uint8_t passkeyTransportMask =
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL |
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID;
+ if (allowListTransports.Length() > 0 &&
+ (transportSet & passkeyTransportMask) == 0) {
+ guard->ref().service = AuthrsService();
+ }
+ }
+#endif
+
+ rv = guard->ref().service->GetAssertion(aTransactionId, browsingContextId,
+ aArgs, aPromise);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If this is a conditionally mediated request, notify observers that there
+ // is a pending transaction. This is mainly useful in tests.
+ bool conditionallyMediated;
+ Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
+ if (conditionallyMediated) {
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(__func__, []() {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "webauthn:conditional-get-pending",
+ nullptr);
+ }
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnService::GetIsUVPAA(bool* aAvailable) {
+ return DefaultService()->GetIsUVPAA(aAvailable);
+}
+
+NS_IMETHODIMP
+WebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
+ const nsAString& aOrigin,
+ uint64_t* aRv) {
+ return SelectedService()->HasPendingConditionalGet(aBrowsingContextId,
+ aOrigin, aRv);
+}
+
+NS_IMETHODIMP
+WebAuthnService::GetAutoFillEntries(
+ uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
+ return SelectedService()->GetAutoFillEntries(aTransactionId, aRv);
+}
+
+NS_IMETHODIMP
+WebAuthnService::SelectAutoFillEntry(uint64_t aTransactionId,
+ const nsTArray<uint8_t>& aCredentialId) {
+ return SelectedService()->SelectAutoFillEntry(aTransactionId, aCredentialId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
+ return SelectedService()->ResumeConditionalGet(aTransactionId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::Reset() {
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ guard->ref().service->Reset();
+ }
+ *guard = Nothing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebAuthnService::Cancel(uint64_t aTransactionId) {
+ return SelectedService()->Cancel(aTransactionId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::PinCallback(uint64_t aTransactionId, const nsACString& aPin) {
+ return SelectedService()->PinCallback(aTransactionId, aPin);
+}
+
+NS_IMETHODIMP
+WebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
+ bool aForceNoneAttestation) {
+ return SelectedService()->ResumeMakeCredential(aTransactionId,
+ aForceNoneAttestation);
+}
+
+NS_IMETHODIMP
+WebAuthnService::SelectionCallback(uint64_t aTransactionId, uint64_t aIndex) {
+ return SelectedService()->SelectionCallback(aTransactionId, aIndex);
+}
+
+NS_IMETHODIMP
+WebAuthnService::AddVirtualAuthenticator(
+ const nsACString& protocol, const nsACString& transport,
+ bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
+ bool isUserVerified, uint64_t* retval) {
+ return SelectedService()->AddVirtualAuthenticator(
+ protocol, transport, hasResidentKey, hasUserVerification,
+ isUserConsenting, isUserVerified, retval);
+}
+
+NS_IMETHODIMP
+WebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
+ return SelectedService()->RemoveVirtualAuthenticator(authenticatorId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::AddCredential(uint64_t authenticatorId,
+ const nsACString& credentialId,
+ bool isResidentCredential,
+ const nsACString& rpId,
+ const nsACString& privateKey,
+ const nsACString& userHandle,
+ uint32_t signCount) {
+ return SelectedService()->AddCredential(authenticatorId, credentialId,
+ isResidentCredential, rpId,
+ privateKey, userHandle, signCount);
+}
+
+NS_IMETHODIMP
+WebAuthnService::GetCredentials(
+ uint64_t authenticatorId,
+ nsTArray<RefPtr<nsICredentialParameters>>& retval) {
+ return SelectedService()->GetCredentials(authenticatorId, retval);
+}
+
+NS_IMETHODIMP
+WebAuthnService::RemoveCredential(uint64_t authenticatorId,
+ const nsACString& credentialId) {
+ return SelectedService()->RemoveCredential(authenticatorId, credentialId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
+ return SelectedService()->RemoveAllCredentials(authenticatorId);
+}
+
+NS_IMETHODIMP
+WebAuthnService::SetUserVerified(uint64_t authenticatorId,
+ bool isUserVerified) {
+ return SelectedService()->SetUserVerified(authenticatorId, isUserVerified);
+}
+
+NS_IMETHODIMP
+WebAuthnService::Listen() { return SelectedService()->Listen(); }
+
+NS_IMETHODIMP
+WebAuthnService::RunCommand(const nsACString& cmd) {
+ return SelectedService()->RunCommand(cmd);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnService.h b/dom/webauthn/WebAuthnService.h
new file mode 100644
index 0000000000..254b75d251
--- /dev/null
+++ b/dom/webauthn/WebAuthnService.h
@@ -0,0 +1,87 @@
+/* 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/. */
+
+#ifndef mozilla_dom_WebAuthnService_h_
+#define mozilla_dom_WebAuthnService_h_
+
+#include "nsIWebAuthnService.h"
+#include "AuthrsBridge_ffi.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidWebAuthnService.h"
+#endif
+
+#ifdef XP_MACOSX
+# include "MacOSWebAuthnService.h"
+#endif
+
+#ifdef XP_WIN
+# include "WinWebAuthnService.h"
+#endif
+
+namespace mozilla::dom {
+
+already_AddRefed<nsIWebAuthnService> NewWebAuthnService();
+
+class WebAuthnService final : public nsIWebAuthnService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSERVICE
+
+ WebAuthnService()
+ : mTransactionState(Nothing(), "WebAuthnService::mTransactionState") {
+ Unused << authrs_service_constructor(getter_AddRefs(mAuthrsService));
+#if defined(XP_WIN)
+ if (WinWebAuthnService::AreWebAuthNApisAvailable()) {
+ mPlatformService = new WinWebAuthnService();
+ } else {
+ mPlatformService = mAuthrsService;
+ }
+#elif defined(MOZ_WIDGET_ANDROID)
+ mPlatformService = new AndroidWebAuthnService();
+#elif defined(XP_MACOSX)
+ if (__builtin_available(macos 13.3, *)) {
+ mPlatformService = NewMacOSWebAuthnServiceIfAvailable();
+ }
+ if (!mPlatformService) {
+ mPlatformService = mAuthrsService;
+ }
+#else
+ mPlatformService = mAuthrsService;
+#endif
+ }
+
+ private:
+ ~WebAuthnService() = default;
+
+ nsIWebAuthnService* DefaultService() {
+ if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
+ return mAuthrsService;
+ }
+ return mPlatformService;
+ }
+
+ nsIWebAuthnService* AuthrsService() { return mAuthrsService; }
+
+ nsIWebAuthnService* SelectedService() {
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ return guard->ref().service;
+ }
+ return DefaultService();
+ }
+
+ struct TransactionState {
+ nsCOMPtr<nsIWebAuthnService> service;
+ };
+ using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
+ TransactionStateMutex mTransactionState;
+
+ nsCOMPtr<nsIWebAuthnService> mAuthrsService;
+ nsCOMPtr<nsIWebAuthnService> mPlatformService;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnService_h_
diff --git a/dom/webauthn/WebAuthnTransactionChild.cpp b/dom/webauthn/WebAuthnTransactionChild.cpp
new file mode 100644
index 0000000000..15022ff981
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionChild.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "mozilla/dom/WebAuthnTransactionChild.h"
+
+namespace mozilla::dom {
+
+WebAuthnTransactionChild::WebAuthnTransactionChild(
+ WebAuthnManagerBase* aManager)
+ : mManager(aManager) {
+ MOZ_ASSERT(aManager);
+
+ // Retain a reference so the task object isn't deleted without IPDL's
+ // knowledge. The reference will be released by
+ // mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild.
+ NS_ADDREF_THIS();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionChild::RecvConfirmRegister(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialResult& aResult) {
+ if (NS_WARN_IF(!mManager)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // We don't own the reference to mManager. We need to prevent its refcount
+ // going to 0 while we call anything that can reach the call to
+ // StopListeningForVisibilityEvents in WebAuthnManager::ClearTransaction
+ // (often via WebAuthnManager::RejectTransaction).
+ RefPtr<WebAuthnManagerBase> kungFuDeathGrip(mManager);
+ kungFuDeathGrip->FinishMakeCredential(aTransactionId, aResult);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionChild::RecvConfirmSign(
+ const uint64_t& aTransactionId, const WebAuthnGetAssertionResult& aResult) {
+ if (NS_WARN_IF(!mManager)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // We don't own the reference to mManager. We need to prevent its refcount
+ // going to 0 while we call anything that can reach the call to
+ // StopListeningForVisibilityEvents in WebAuthnManager::ClearTransaction
+ // (often via WebAuthnManager::RejectTransaction).
+ RefPtr<WebAuthnManagerBase> kungFuDeathGrip(mManager);
+ kungFuDeathGrip->FinishGetAssertion(aTransactionId, aResult);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionChild::RecvAbort(
+ const uint64_t& aTransactionId, const nsresult& aError) {
+ if (NS_WARN_IF(!mManager)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // We don't own the reference to mManager. We need to prevent its refcount
+ // going to 0 while we call anything that can reach the call to
+ // StopListeningForVisibilityEvents in WebAuthnManager::ClearTransaction
+ // (often via WebAuthnManager::RejectTransaction).
+ RefPtr<WebAuthnManagerBase> kungFuDeathGrip(mManager);
+ kungFuDeathGrip->RequestAborted(aTransactionId, aError);
+ return IPC_OK();
+}
+
+void WebAuthnTransactionChild::ActorDestroy(ActorDestroyReason why) {
+ // Called by either a __delete__ message from the parent, or when the
+ // channel disconnects. Clear out the child actor reference to be sure.
+ if (mManager) {
+ mManager->ActorDestroyed();
+ mManager = nullptr;
+ }
+}
+
+void WebAuthnTransactionChild::Disconnect() {
+ mManager = nullptr;
+
+ // The WebAuthnManager released us, but we're going to be held alive by the
+ // IPC layer. The parent will explicitly destroy us via Send__delete__(),
+ // after receiving the DestroyMe message.
+
+ SendDestroyMe();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnTransactionChild.h b/dom/webauthn/WebAuthnTransactionChild.h
new file mode 100644
index 0000000000..fd2d4db90b
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionChild.h
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnTransactionChild_h
+#define mozilla_dom_WebAuthnTransactionChild_h
+
+#include "mozilla/dom/PWebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnManagerBase.h"
+
+/*
+ * Child process IPC implementation for WebAuthn API. Receives results of
+ * WebAuthn transactions from the parent process, and sends them to the
+ * WebAuthnManager either cancel the transaction, or be formatted and relayed to
+ * content.
+ */
+
+namespace mozilla::dom {
+
+class WebAuthnTransactionChild final : public PWebAuthnTransactionChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionChild);
+ explicit WebAuthnTransactionChild(WebAuthnManagerBase* aManager);
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can do MOZ_CAN_RUN_SCRIPT in
+ // IPDL-generated things.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ mozilla::ipc::IPCResult RecvConfirmRegister(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialResult& aResult);
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can do MOZ_CAN_RUN_SCRIPT in
+ // IPDL-generated things.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ mozilla::ipc::IPCResult RecvConfirmSign(
+ const uint64_t& aTransactionId,
+ const WebAuthnGetAssertionResult& aResult);
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can do MOZ_CAN_RUN_SCRIPT in
+ // IPDL-generated things.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ mozilla::ipc::IPCResult RecvAbort(const uint64_t& aTransactionId,
+ const nsresult& aError);
+
+ void ActorDestroy(ActorDestroyReason why) override;
+
+ void Disconnect();
+
+ private:
+ ~WebAuthnTransactionChild() = default;
+
+ // Nulled by ~WebAuthnManager() when disconnecting.
+ WebAuthnManagerBase* mManager;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnTransactionChild_h
diff --git a/dom/webauthn/WebAuthnTransactionParent.cpp b/dom/webauthn/WebAuthnTransactionParent.cpp
new file mode 100644
index 0000000000..697e8dc970
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionParent.cpp
@@ -0,0 +1,409 @@
+/* -*- 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 "mozilla/dom/WebAuthnTransactionParent.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/StaticPrefs_security.h"
+
+#include "nsThreadUtils.h"
+#include "WebAuthnArgs.h"
+
+namespace mozilla::dom {
+
+void WebAuthnTransactionParent::CompleteTransaction() {
+ if (mTransactionId.isSome()) {
+ if (mRegisterPromiseRequest.Exists()) {
+ mRegisterPromiseRequest.Complete();
+ }
+ if (mSignPromiseRequest.Exists()) {
+ mSignPromiseRequest.Complete();
+ }
+ if (mWebAuthnService) {
+ // We have to do this to work around Bug 1864526.
+ mWebAuthnService->Cancel(mTransactionId.ref());
+ }
+ mTransactionId.reset();
+ }
+}
+
+void WebAuthnTransactionParent::DisconnectTransaction() {
+ mTransactionId.reset();
+ mRegisterPromiseRequest.DisconnectIfExists();
+ mSignPromiseRequest.DisconnectIfExists();
+ if (mWebAuthnService) {
+ mWebAuthnService->Reset();
+ }
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialInfo& aTransactionInfo) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!mWebAuthnService) {
+ mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
+ if (!mWebAuthnService) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ }
+
+ // If there's an ongoing transaction, abort it.
+ if (mTransactionId.isSome()) {
+ DisconnectTransaction();
+ Unused << SendAbort(mTransactionId.ref(), NS_ERROR_DOM_ABORT_ERR);
+ }
+ mTransactionId = Some(aTransactionId);
+
+ RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
+ new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
+
+ PWebAuthnTransactionParent* parent = this;
+ RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
+ promise
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, parent, aTransactionId,
+ inputClientData = aTransactionInfo.ClientDataJSON()](
+ const WebAuthnRegisterPromise::ResolveValueType& aValue) {
+ CompleteTransaction();
+
+ nsCString clientData;
+ nsresult rv = aValue->GetClientDataJSON(clientData);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ clientData = inputClientData;
+ } else if (NS_FAILED(rv)) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> attObj;
+ rv = aValue->GetAttestationObject(attObj);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> credentialId;
+ rv = aValue->GetCredentialId(credentialId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<nsString> transports;
+ rv = aValue->GetTransports(transports);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ Maybe<nsString> authenticatorAttachment;
+ nsString maybeAuthenticatorAttachment;
+ rv = aValue->GetAuthenticatorAttachment(
+ maybeAuthenticatorAttachment);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ authenticatorAttachment = Some(maybeAuthenticatorAttachment);
+ }
+
+ nsTArray<WebAuthnExtensionResult> extensions;
+ bool credPropsRk;
+ rv = aValue->GetCredPropsRk(&credPropsRk);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ extensions.AppendElement(
+ WebAuthnExtensionResultCredProps(credPropsRk));
+ }
+
+ bool hmacCreateSecret;
+ rv = aValue->GetHmacCreateSecret(&hmacCreateSecret);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ extensions.AppendElement(
+ WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
+ }
+
+ WebAuthnMakeCredentialResult result(
+ clientData, attObj, credentialId, transports, extensions,
+ authenticatorAttachment);
+
+ Unused << parent->SendConfirmRegister(aTransactionId, result);
+ },
+ [this, parent, aTransactionId](
+ const WebAuthnRegisterPromise::RejectValueType aValue) {
+ CompleteTransaction();
+ Unused << parent->SendAbort(aTransactionId, aValue);
+ })
+ ->Track(mRegisterPromiseRequest);
+
+ uint64_t browsingContextId = aTransactionInfo.BrowsingContextId();
+ RefPtr<WebAuthnRegisterArgs> args(new WebAuthnRegisterArgs(aTransactionInfo));
+
+ nsresult rv = mWebAuthnService->MakeCredential(
+ aTransactionId, browsingContextId, args, promiseHolder);
+ if (NS_FAILED(rv)) {
+ promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign(
+ const uint64_t& aTransactionId,
+ const WebAuthnGetAssertionInfo& aTransactionInfo) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!mWebAuthnService) {
+ mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1");
+ if (!mWebAuthnService) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ }
+
+ if (mTransactionId.isSome()) {
+ DisconnectTransaction();
+ Unused << SendAbort(mTransactionId.ref(), NS_ERROR_DOM_ABORT_ERR);
+ }
+ mTransactionId = Some(aTransactionId);
+
+ RefPtr<WebAuthnSignPromiseHolder> promiseHolder =
+ new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
+
+ PWebAuthnTransactionParent* parent = this;
+ RefPtr<WebAuthnSignPromise> promise = promiseHolder->Ensure();
+ promise
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, parent, aTransactionId,
+ inputClientData = aTransactionInfo.ClientDataJSON()](
+ const WebAuthnSignPromise::ResolveValueType& aValue) {
+ CompleteTransaction();
+
+ nsCString clientData;
+ nsresult rv = aValue->GetClientDataJSON(clientData);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ clientData = inputClientData;
+ } else if (NS_FAILED(rv)) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> credentialId;
+ rv = aValue->GetCredentialId(credentialId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> signature;
+ rv = aValue->GetSignature(signature);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> authenticatorData;
+ rv = aValue->GetAuthenticatorData(authenticatorData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+
+ nsTArray<uint8_t> userHandle;
+ Unused << aValue->GetUserHandle(userHandle); // optional
+
+ Maybe<nsString> authenticatorAttachment;
+ nsString maybeAuthenticatorAttachment;
+ rv = aValue->GetAuthenticatorAttachment(
+ maybeAuthenticatorAttachment);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ authenticatorAttachment = Some(maybeAuthenticatorAttachment);
+ }
+
+ nsTArray<WebAuthnExtensionResult> extensions;
+ bool usedAppId;
+ rv = aValue->GetUsedAppId(&usedAppId);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ Unused << parent->SendAbort(aTransactionId,
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
+ }
+
+ WebAuthnGetAssertionResult result(
+ clientData, credentialId, signature, authenticatorData,
+ extensions, userHandle, authenticatorAttachment);
+
+ Unused << parent->SendConfirmSign(aTransactionId, result);
+ },
+ [this, parent,
+ aTransactionId](const WebAuthnSignPromise::RejectValueType aValue) {
+ CompleteTransaction();
+ Unused << parent->SendAbort(aTransactionId, aValue);
+ })
+ ->Track(mSignPromiseRequest);
+
+ RefPtr<WebAuthnSignArgs> args(new WebAuthnSignArgs(aTransactionInfo));
+
+ nsresult rv = mWebAuthnService->GetAssertion(
+ aTransactionId, aTransactionInfo.BrowsingContextId(), args,
+ promiseHolder);
+ if (NS_FAILED(rv)) {
+ promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel(
+ const Tainted<uint64_t>& aTransactionId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mTransactionId.isNothing() ||
+ !MOZ_IS_VALID(aTransactionId, mTransactionId.ref() == aTransactionId)) {
+ return IPC_OK();
+ }
+
+ DisconnectTransaction();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA(
+ RequestIsUVPAAResolver&& aResolver) {
+#ifdef MOZ_WIDGET_ANDROID
+ // Try the nsIWebAuthnService. If we're configured for tests we
+ // will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
+ nsCOMPtr<nsIWebAuthnService> service(
+ do_GetService("@mozilla.org/webauthn/service;1"));
+ bool available;
+ nsresult rv = service->GetIsUVPAA(&available);
+ if (NS_SUCCEEDED(rv)) {
+ aResolver(available);
+ return IPC_OK();
+ }
+
+ // Don't consult the platform API if resident key support is disabled.
+ if (!StaticPrefs::
+ security_webauthn_webauthn_enable_android_fido2_residentkey()) {
+ aResolver(false);
+ return IPC_OK();
+ }
+
+ // The GeckoView implementation of
+ // isUserVerifiyingPlatformAuthenticatorAvailable does not block, but we must
+ // call it on the main thread. It returns a MozPromise which we can ->Then to
+ // call aResolver on the IPDL background thread.
+ //
+ // Bug 1550788: there is an unnecessary layer of dispatching here: ipdl ->
+ // main -> a background thread. Other platforms just do ipdl -> a background
+ // thread.
+ nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ __func__, [target, resolver = std::move(aResolver)]() {
+ auto result = java::WebAuthnTokenManager::
+ WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+ MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
+ ->Then(
+ target, __func__,
+ [resolver](
+ const MozPromise<bool, bool, false>::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsResolve()) {
+ resolver(aValue.ResolveValue());
+ } else {
+ resolver(false);
+ }
+ });
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+ return IPC_OK();
+
+#else
+
+ nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ __func__, [target, resolver = std::move(aResolver)]() {
+ bool available;
+ nsCOMPtr<nsIWebAuthnService> service(
+ do_GetService("@mozilla.org/webauthn/service;1"));
+ nsresult rv = service->GetIsUVPAA(&available);
+ if (NS_FAILED(rv)) {
+ available = false;
+ }
+ BoolPromise::CreateAndResolve(available, __func__)
+ ->Then(target, __func__,
+ [resolver](const BoolPromise::ResolveOrRejectValue& value) {
+ if (value.IsResolve()) {
+ resolver(value.ResolveValue());
+ } else {
+ resolver(false);
+ }
+ });
+ }));
+ NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
+ return IPC_OK();
+#endif
+}
+
+mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvDestroyMe() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // The child was disconnected from the WebAuthnManager instance and will send
+ // no further messages. It is kept alive until we delete it explicitly.
+
+ // The child should have cancelled any active transaction. This means
+ // we expect no more messages to the child. We'll crash otherwise.
+
+ // The IPC roundtrip is complete. No more messages, hopefully.
+ IProtocol* mgr = Manager();
+ if (!Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+
+ return IPC_OK();
+}
+
+void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // Called either by Send__delete__() in RecvDestroyMe() above, or when
+ // the channel disconnects. Ensure the token manager forgets about us.
+
+ if (mTransactionId.isSome()) {
+ DisconnectTransaction();
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnTransactionParent.h b/dom/webauthn/WebAuthnTransactionParent.h
new file mode 100644
index 0000000000..c393971dc4
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionParent.h
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnTransactionParent_h
+#define mozilla_dom_WebAuthnTransactionParent_h
+
+#include "mozilla/dom/PWebAuthnTransactionParent.h"
+#include "mozilla/dom/WebAuthnPromiseHolder.h"
+#include "nsIWebAuthnService.h"
+
+/*
+ * Parent process IPC implementation for WebAuthn.
+ */
+
+namespace mozilla::dom {
+
+class WebAuthnRegisterPromiseHolder;
+class WebAuthnSignPromiseHolder;
+
+class WebAuthnTransactionParent final : public PWebAuthnTransactionParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionParent);
+ WebAuthnTransactionParent() = default;
+
+ mozilla::ipc::IPCResult RecvRequestRegister(
+ const uint64_t& aTransactionId,
+ const WebAuthnMakeCredentialInfo& aTransactionInfo);
+
+ mozilla::ipc::IPCResult RecvRequestSign(
+ const uint64_t& aTransactionId,
+ const WebAuthnGetAssertionInfo& aTransactionInfo);
+
+ mozilla::ipc::IPCResult RecvRequestCancel(
+ const Tainted<uint64_t>& aTransactionId);
+
+ mozilla::ipc::IPCResult RecvRequestIsUVPAA(
+ RequestIsUVPAAResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvDestroyMe();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ ~WebAuthnTransactionParent() = default;
+
+ void CompleteTransaction();
+ void DisconnectTransaction();
+
+ nsCOMPtr<nsIWebAuthnService> mWebAuthnService;
+ Maybe<uint64_t> mTransactionId;
+ MozPromiseRequestHolder<WebAuthnRegisterPromise> mRegisterPromiseRequest;
+ MozPromiseRequestHolder<WebAuthnSignPromise> mSignPromiseRequest;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnTransactionParent_h
diff --git a/dom/webauthn/WebAuthnTransportIdentifiers.h b/dom/webauthn/WebAuthnTransportIdentifiers.h
new file mode 100644
index 0000000000..e6a348a377
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransportIdentifiers.h
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnTransportIdentifiers_h
+#define mozilla_dom_WebAuthnTransportIdentifiers_h
+
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB 1
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC 2
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE 4
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL 8
+#define MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID 16
+
+#endif // mozilla_dom_WebAuthnTransportIdentifiers_h
diff --git a/dom/webauthn/WebAuthnUtil.cpp b/dom/webauthn/WebAuthnUtil.cpp
new file mode 100644
index 0000000000..941ff0a85b
--- /dev/null
+++ b/dom/webauthn/WebAuthnUtil.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "mozilla/dom/WebAuthnUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsNetUtil.h"
+#include "mozpkix/pkixutil.h"
+#include "nsHTMLDocument.h"
+#include "hasht.h"
+
+namespace mozilla::dom {
+
+// Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
+constexpr auto kGoogleAccountsAppId1 =
+ u"https://www.gstatic.com/securitykey/origins.json"_ns;
+constexpr auto kGoogleAccountsAppId2 =
+ u"https://www.gstatic.com/securitykey/a/google.com/origins.json"_ns;
+
+bool EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
+ /* in/out */ nsString& aAppId) {
+ // Facet is the specification's way of referring to the web origin.
+ nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
+ nsCOMPtr<nsIURI> facetUri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
+ return false;
+ }
+
+ // If the facetId (origin) is not HTTPS, reject
+ if (!facetUri->SchemeIs("https")) {
+ return false;
+ }
+
+ // If the appId is empty or null, overwrite it with the facetId and accept
+ if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
+ aAppId.Assign(aOrigin);
+ return true;
+ }
+
+ // AppID is user-supplied. It's quite possible for this parse to fail.
+ nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
+ nsCOMPtr<nsIURI> appIdUri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
+ return false;
+ }
+
+ // if the appId URL is not HTTPS, reject.
+ if (!appIdUri->SchemeIs("https")) {
+ return false;
+ }
+
+ nsAutoCString appIdHost;
+ if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
+ return false;
+ }
+
+ // Allow localhost.
+ if (appIdHost.EqualsLiteral("localhost")) {
+ nsAutoCString facetHost;
+ if (NS_FAILED(facetUri->GetAsciiHost(facetHost))) {
+ return false;
+ }
+
+ if (facetHost.EqualsLiteral("localhost")) {
+ return true;
+ }
+ }
+
+ // Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
+ // Web Authentication. See Bug 1244959 comment #8 for context on why we are
+ // doing this instead of implementing the external-fetch FacetID logic.
+ nsCOMPtr<Document> document = aParent->GetDoc();
+ if (!document || !document->IsHTMLDocument()) {
+ return false;
+ }
+
+ nsHTMLDocument* html = document->AsHTMLDocument();
+ // Use the base domain as the facet for evaluation. This lets this algorithm
+ // relax the whole eTLD+1.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return false;
+ }
+
+ nsAutoCString lowestFacetHost;
+ if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
+ return false;
+ }
+
+ if (html->IsRegistrableDomainSuffixOfOrEqualTo(
+ NS_ConvertUTF8toUTF16(lowestFacetHost), appIdHost)) {
+ return true;
+ }
+
+ // Bug #1436078 - Permit Google Accounts. Remove in Bug #1436085 in Jan 2023.
+ if (lowestFacetHost.EqualsLiteral("google.com") &&
+ (aAppId.Equals(kGoogleAccountsAppId1) ||
+ aAppId.Equals(kGoogleAccountsAppId2))) {
+ return true;
+ }
+
+ return false;
+}
+
+static nsresult HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
+ /* out */ nsTArray<uint8_t>& aOut) {
+ MOZ_ASSERT(aHashService);
+
+ nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aHashService->Update(
+ reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString fullHash;
+ // Passing false below means we will get a binary result rather than a
+ // base64-encoded string.
+ rv = aHashService->Finish(false, fullHash);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aOut.Clear();
+ aOut.AppendElements(reinterpret_cast<uint8_t const*>(fullHash.BeginReading()),
+ fullHash.Length());
+
+ return NS_OK;
+}
+
+nsresult HashCString(const nsACString& aIn, /* out */ nsTArray<uint8_t>& aOut) {
+ nsresult srv;
+ nsCOMPtr<nsICryptoHash> hashService =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+ if (NS_FAILED(srv)) {
+ return srv;
+ }
+
+ srv = HashCString(hashService, aIn, aOut);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WebAuthnUtil.h b/dom/webauthn/WebAuthnUtil.h
new file mode 100644
index 0000000000..4c5841b347
--- /dev/null
+++ b/dom/webauthn/WebAuthnUtil.h
@@ -0,0 +1,26 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WebAuthnUtil_h
+#define mozilla_dom_WebAuthnUtil_h
+
+/*
+ * Utility functions used by both WebAuthnManager and U2FTokenManager.
+ */
+
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla::dom {
+
+bool EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
+ /* in/out */ nsString& aAppId);
+
+nsresult HashCString(const nsACString& aIn, /* out */ nsTArray<uint8_t>& aOut);
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebAuthnUtil_h
diff --git a/dom/webauthn/WinWebAuthnService.cpp b/dom/webauthn/WinWebAuthnService.cpp
new file mode 100644
index 0000000000..8667cf5615
--- /dev/null
+++ b/dom/webauthn/WinWebAuthnService.cpp
@@ -0,0 +1,1047 @@
+/* -*- 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 "mozilla/dom/PWebAuthnTransactionParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Unused.h"
+
+#include "nsTextFormatter.h"
+#include "nsWindowsHelpers.h"
+#include "WebAuthnAutoFillEntry.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnResult.h"
+#include "WebAuthnTransportIdentifiers.h"
+#include "winwebauthn/webauthn.h"
+#include "WinWebAuthnService.h"
+
+namespace mozilla::dom {
+
+namespace {
+StaticRWLock gWinWebAuthnModuleLock;
+
+static bool gWinWebAuthnModuleUnusable = false;
+static HMODULE gWinWebAuthnModule = 0;
+
+static decltype(WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)*
+ gWinWebauthnIsUVPAA = nullptr;
+static decltype(WebAuthNAuthenticatorMakeCredential)*
+ gWinWebauthnMakeCredential = nullptr;
+static decltype(WebAuthNFreeCredentialAttestation)*
+ gWinWebauthnFreeCredentialAttestation = nullptr;
+static decltype(WebAuthNAuthenticatorGetAssertion)* gWinWebauthnGetAssertion =
+ nullptr;
+static decltype(WebAuthNFreeAssertion)* gWinWebauthnFreeAssertion = nullptr;
+static decltype(WebAuthNGetCancellationId)* gWinWebauthnGetCancellationId =
+ nullptr;
+static decltype(WebAuthNCancelCurrentOperation)*
+ gWinWebauthnCancelCurrentOperation = nullptr;
+static decltype(WebAuthNGetErrorName)* gWinWebauthnGetErrorName = nullptr;
+static decltype(WebAuthNGetApiVersionNumber)* gWinWebauthnGetApiVersionNumber =
+ nullptr;
+static decltype(WebAuthNGetPlatformCredentialList)*
+ gWinWebauthnGetPlatformCredentialList = nullptr;
+static decltype(WebAuthNFreePlatformCredentialList)*
+ gWinWebauthnFreePlatformCredentialList = nullptr;
+
+} // namespace
+
+/***********************************************************************
+ * WinWebAuthnService Implementation
+ **********************************************************************/
+
+constexpr uint32_t kMinWinWebAuthNApiVersion = WEBAUTHN_API_VERSION_1;
+
+NS_IMPL_ISUPPORTS(WinWebAuthnService, nsIWebAuthnService)
+
+/* static */
+nsresult WinWebAuthnService::EnsureWinWebAuthnModuleLoaded() {
+ {
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (gWinWebAuthnModule) {
+ // The module is already loaded.
+ return NS_OK;
+ }
+ if (gWinWebAuthnModuleUnusable) {
+ // A previous attempt to load the module failed.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ StaticAutoWriteLock lock(gWinWebAuthnModuleLock);
+ if (gWinWebAuthnModule) {
+ // Another thread successfully loaded the module while we were waiting.
+ return NS_OK;
+ }
+ if (gWinWebAuthnModuleUnusable) {
+ // Another thread failed to load the module while we were waiting.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ gWinWebAuthnModule = LoadLibrarySystem32(L"webauthn.dll");
+ auto markModuleUnusable = MakeScopeExit([]() {
+ if (gWinWebAuthnModule) {
+ FreeLibrary(gWinWebAuthnModule);
+ gWinWebAuthnModule = 0;
+ }
+ gWinWebAuthnModuleUnusable = true;
+ });
+
+ if (!gWinWebAuthnModule) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ gWinWebauthnIsUVPAA = reinterpret_cast<
+ decltype(WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable"));
+ gWinWebauthnMakeCredential =
+ reinterpret_cast<decltype(WebAuthNAuthenticatorMakeCredential)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNAuthenticatorMakeCredential"));
+ gWinWebauthnFreeCredentialAttestation =
+ reinterpret_cast<decltype(WebAuthNFreeCredentialAttestation)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNFreeCredentialAttestation"));
+ gWinWebauthnGetAssertion =
+ reinterpret_cast<decltype(WebAuthNAuthenticatorGetAssertion)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNAuthenticatorGetAssertion"));
+ gWinWebauthnFreeAssertion =
+ reinterpret_cast<decltype(WebAuthNFreeAssertion)*>(
+ GetProcAddress(gWinWebAuthnModule, "WebAuthNFreeAssertion"));
+ gWinWebauthnGetCancellationId =
+ reinterpret_cast<decltype(WebAuthNGetCancellationId)*>(
+ GetProcAddress(gWinWebAuthnModule, "WebAuthNGetCancellationId"));
+ gWinWebauthnCancelCurrentOperation =
+ reinterpret_cast<decltype(WebAuthNCancelCurrentOperation)*>(
+ GetProcAddress(gWinWebAuthnModule, "WebAuthNCancelCurrentOperation"));
+ gWinWebauthnGetErrorName = reinterpret_cast<decltype(WebAuthNGetErrorName)*>(
+ GetProcAddress(gWinWebAuthnModule, "WebAuthNGetErrorName"));
+ gWinWebauthnGetApiVersionNumber =
+ reinterpret_cast<decltype(WebAuthNGetApiVersionNumber)*>(
+ GetProcAddress(gWinWebAuthnModule, "WebAuthNGetApiVersionNumber"));
+
+ if (!(gWinWebauthnIsUVPAA && gWinWebauthnMakeCredential &&
+ gWinWebauthnFreeCredentialAttestation && gWinWebauthnGetAssertion &&
+ gWinWebauthnFreeAssertion && gWinWebauthnGetCancellationId &&
+ gWinWebauthnCancelCurrentOperation && gWinWebauthnGetErrorName &&
+ gWinWebauthnGetApiVersionNumber)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ DWORD version = gWinWebauthnGetApiVersionNumber();
+
+ if (version >= WEBAUTHN_API_VERSION_4) {
+ gWinWebauthnGetPlatformCredentialList =
+ reinterpret_cast<decltype(WebAuthNGetPlatformCredentialList)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNGetPlatformCredentialList"));
+ gWinWebauthnFreePlatformCredentialList =
+ reinterpret_cast<decltype(WebAuthNFreePlatformCredentialList)*>(
+ GetProcAddress(gWinWebAuthnModule,
+ "WebAuthNFreePlatformCredentialList"));
+ if (!(gWinWebauthnGetPlatformCredentialList &&
+ gWinWebauthnFreePlatformCredentialList)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ // Bug 1869584: In some of our tests, a content process can end up here due to
+ // a call to WinWebAuthnService::AreWebAuthNApisAvailable. This causes us to
+ // fail an assertion in Preferences::SetBool, which is parent-process only.
+ if (XRE_IsParentProcess()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(__func__, [version]() {
+ Preferences::SetBool("security.webauthn.show_ms_settings_link",
+ version >= WEBAUTHN_API_VERSION_7);
+ }));
+ }
+
+ markModuleUnusable.release();
+ return NS_OK;
+}
+
+WinWebAuthnService::~WinWebAuthnService() {
+ StaticAutoWriteLock lock(gWinWebAuthnModuleLock);
+ if (gWinWebAuthnModule) {
+ FreeLibrary(gWinWebAuthnModule);
+ }
+ gWinWebAuthnModule = 0;
+}
+
+// static
+bool WinWebAuthnService::AreWebAuthNApisAvailable() {
+ nsresult rv = EnsureWinWebAuthnModuleLoaded();
+ NS_ENSURE_SUCCESS(rv, false);
+
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ return gWinWebAuthnModule &&
+ gWinWebauthnGetApiVersionNumber() >= kMinWinWebAuthNApiVersion;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::GetIsUVPAA(bool* aAvailable) {
+ nsresult rv = EnsureWinWebAuthnModuleLoaded();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WinWebAuthnService::AreWebAuthNApisAvailable()) {
+ BOOL isUVPAA = FALSE;
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ *aAvailable = gWinWebAuthnModule && gWinWebauthnIsUVPAA(&isUVPAA) == S_OK &&
+ isUVPAA == TRUE;
+ } else {
+ *aAvailable = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::Cancel(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::Reset() {
+ // Reset will never be the first function to use gWinWebAuthnModule, so
+ // we shouldn't try to initialize it here.
+ auto guard = mTransactionState.Lock();
+ if (guard->isSome()) {
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (gWinWebAuthnModule) {
+ const GUID cancellationId = guard->ref().cancellationId;
+ gWinWebauthnCancelCurrentOperation(&cancellationId);
+ }
+ if (guard->ref().pendingSignPromise.isSome()) {
+ // This request was never dispatched to the platform API, so
+ // we need to reject the promise ourselves.
+ guard->ref().pendingSignPromise.ref()->Reject(
+ NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+ guard->reset();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::MakeCredential(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnRegisterArgs* aArgs,
+ nsIWebAuthnRegisterPromise* aPromise) {
+ nsresult rv = EnsureWinWebAuthnModuleLoaded();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Reset();
+ auto guard = mTransactionState.Lock();
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ GUID cancellationId;
+ if (gWinWebauthnGetCancellationId(&cancellationId) != S_OK) {
+ // caller will reject promise
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ *guard = Some(TransactionState{
+ aTransactionId,
+ aBrowsingContextId,
+ Nothing(),
+ Nothing(),
+ cancellationId,
+ });
+
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "WinWebAuthnService::MakeCredential",
+ [self = RefPtr{this}, aArgs = RefPtr{aArgs}, aPromise = RefPtr{aPromise},
+ cancellationId]() mutable {
+ // Take a read lock on gWinWebAuthnModuleLock to prevent the module from
+ // being unloaded while the operation is in progress. This does not
+ // prevent the operation from being cancelled, so it does not block a
+ // clean shutdown.
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (!gWinWebAuthnModule) {
+ aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ BOOL HmacCreateSecret = FALSE;
+ BOOL MinPinLength = FALSE;
+
+ // RP Information
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ WEBAUTHN_RP_ENTITY_INFORMATION rpInfo = {
+ WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, rpId.get(), nullptr,
+ nullptr};
+
+ // User Information
+ WEBAUTHN_USER_ENTITY_INFORMATION userInfo = {
+ WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION,
+ 0,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr};
+
+ // Client Data
+ nsCString clientDataJSON;
+ Unused << aArgs->GetClientDataJSON(clientDataJSON);
+ WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
+ WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
+ (DWORD)clientDataJSON.Length(), (BYTE*)(clientDataJSON.get()),
+ WEBAUTHN_HASH_ALGORITHM_SHA_256};
+
+ // User Verification Requirement
+ DWORD winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
+
+ // Resident Key
+ BOOL winRequireResidentKey = FALSE;
+ BOOL winPreferResidentKey = FALSE;
+
+ // AttestationConveyance
+ DWORD winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;
+
+ nsString rpName;
+ Unused << aArgs->GetRpName(rpName);
+ rpInfo.pwszName = rpName.get();
+ rpInfo.pwszIcon = nullptr;
+
+ nsTArray<uint8_t> userId;
+ Unused << aArgs->GetUserId(userId);
+ userInfo.cbId = static_cast<DWORD>(userId.Length());
+ userInfo.pbId = const_cast<unsigned char*>(userId.Elements());
+
+ nsString userName;
+ Unused << aArgs->GetUserName(userName);
+ userInfo.pwszName = userName.get();
+
+ userInfo.pwszIcon = nullptr;
+
+ nsString userDisplayName;
+ Unused << aArgs->GetUserDisplayName(userDisplayName);
+ userInfo.pwszDisplayName = userDisplayName.get();
+
+ // Algorithms
+ nsTArray<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> coseParams;
+ nsTArray<int32_t> coseAlgs;
+ Unused << aArgs->GetCoseAlgs(coseAlgs);
+ for (const int32_t& coseAlg : coseAlgs) {
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETER coseAlgorithm = {
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION,
+ WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, coseAlg};
+ coseParams.AppendElement(coseAlgorithm);
+ }
+
+ nsString userVerificationReq;
+ Unused << aArgs->GetUserVerification(userVerificationReq);
+ // This mapping needs to be reviewed if values are added to the
+ // UserVerificationRequirement enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ } else if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
+ } else if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+ } else {
+ winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
+ }
+
+ // Attachment
+ DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
+ nsString authenticatorAttachment;
+ nsresult rv =
+ aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(rv);
+ return;
+ }
+ // This mapping needs to be reviewed if values are added to the
+ // AuthenticatorAttachement enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ if (authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
+ winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM;
+ } else if (
+ authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
+ winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
+ } else {
+ winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
+ }
+ }
+
+ nsString residentKey;
+ Unused << aArgs->GetResidentKey(residentKey);
+ // This mapping needs to be reviewed if values are added to the
+ // ResidentKeyRequirement enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ if (residentKey.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED)) {
+ winRequireResidentKey = TRUE;
+ winPreferResidentKey = TRUE;
+ } else if (residentKey.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED)) {
+ winRequireResidentKey = FALSE;
+ winPreferResidentKey = TRUE;
+ } else if (residentKey.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
+ winRequireResidentKey = FALSE;
+ winPreferResidentKey = FALSE;
+ } else {
+ // WebAuthnManager::MakeCredential is supposed to assign one of the
+ // above values, so this shouldn't happen.
+ MOZ_ASSERT_UNREACHABLE();
+ aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ // AttestationConveyance
+ nsString attestation;
+ Unused << aArgs->GetAttestationConveyancePreference(attestation);
+ bool anonymize = false;
+ // This mapping needs to be reviewed if values are added to the
+ // AttestationConveyancePreference enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ if (attestation.EqualsLiteral(
+ MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE)) {
+ winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
+ anonymize = true;
+ } else if (
+ attestation.EqualsLiteral(
+ MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT)) {
+ winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT;
+ } else if (attestation.EqualsLiteral(
+ MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT)) {
+ winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
+ } else {
+ winAttestation = WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY;
+ }
+
+ bool requestedCredProps;
+ Unused << aArgs->GetCredProps(&requestedCredProps);
+
+ bool requestedMinPinLength;
+ Unused << aArgs->GetMinPinLength(&requestedMinPinLength);
+
+ bool requestedHmacCreateSecret;
+ Unused << aArgs->GetHmacCreateSecret(&requestedHmacCreateSecret);
+
+ // Extensions that might require an entry: hmac-secret, minPinLength.
+ WEBAUTHN_EXTENSION rgExtension[2] = {};
+ DWORD cExtensions = 0;
+ if (requestedHmacCreateSecret) {
+ HmacCreateSecret = TRUE;
+ rgExtension[cExtensions].pwszExtensionIdentifier =
+ WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
+ rgExtension[cExtensions].cbExtension = sizeof(BOOL);
+ rgExtension[cExtensions].pvExtension = &HmacCreateSecret;
+ cExtensions++;
+ }
+ if (requestedMinPinLength) {
+ MinPinLength = TRUE;
+ rgExtension[cExtensions].pwszExtensionIdentifier =
+ WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH;
+ rgExtension[cExtensions].cbExtension = sizeof(BOOL);
+ rgExtension[cExtensions].pvExtension = &MinPinLength;
+ cExtensions++;
+ }
+
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETERS WebAuthNCredentialParameters = {
+ static_cast<DWORD>(coseParams.Length()), coseParams.Elements()};
+
+ // Exclude Credentials
+ nsTArray<nsTArray<uint8_t>> excludeList;
+ Unused << aArgs->GetExcludeList(excludeList);
+
+ nsTArray<uint8_t> excludeListTransports;
+ Unused << aArgs->GetExcludeListTransports(excludeListTransports);
+
+ if (excludeList.Length() != excludeListTransports.Length()) {
+ aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ nsTArray<WEBAUTHN_CREDENTIAL_EX> excludeCredentials;
+ WEBAUTHN_CREDENTIAL_EX* pExcludeCredentials = nullptr;
+ nsTArray<WEBAUTHN_CREDENTIAL_EX*> excludeCredentialsPtrs;
+ WEBAUTHN_CREDENTIAL_LIST excludeCredentialList = {0};
+ WEBAUTHN_CREDENTIAL_LIST* pExcludeCredentialList = nullptr;
+
+ for (size_t i = 0; i < excludeList.Length(); i++) {
+ nsTArray<uint8_t>& cred = excludeList[i];
+ uint8_t& transports = excludeListTransports[i];
+ DWORD winTransports = 0;
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_HYBRID;
+ }
+
+ WEBAUTHN_CREDENTIAL_EX credential = {
+ WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
+ static_cast<DWORD>(cred.Length()), (PBYTE)(cred.Elements()),
+ WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, winTransports};
+ excludeCredentials.AppendElement(credential);
+ }
+
+ if (!excludeCredentials.IsEmpty()) {
+ pExcludeCredentials = excludeCredentials.Elements();
+ for (DWORD i = 0; i < excludeCredentials.Length(); i++) {
+ excludeCredentialsPtrs.AppendElement(&pExcludeCredentials[i]);
+ }
+ excludeCredentialList.cCredentials = excludeCredentials.Length();
+ excludeCredentialList.ppCredentials =
+ excludeCredentialsPtrs.Elements();
+ pExcludeCredentialList = &excludeCredentialList;
+ }
+
+ uint32_t timeout_u32;
+ Unused << aArgs->GetTimeoutMS(&timeout_u32);
+ DWORD timeout = timeout_u32;
+
+ // MakeCredentialOptions
+ WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS
+ WebAuthNCredentialOptions = {
+ WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7,
+ timeout,
+ {0, NULL},
+ {0, NULL},
+ winAttachment,
+ winRequireResidentKey,
+ winUserVerificationReq,
+ winAttestation,
+ 0, // Flags
+ &cancellationId, // CancellationId
+ pExcludeCredentialList,
+ WEBAUTHN_ENTERPRISE_ATTESTATION_NONE,
+ WEBAUTHN_LARGE_BLOB_SUPPORT_NONE,
+ winPreferResidentKey, // PreferResidentKey
+ FALSE, // BrowserInPrivateMode
+ FALSE, // EnablePrf
+ NULL, // LinkedDevice
+ 0, // size of JsonExt
+ NULL, // JsonExt
+ };
+
+ if (cExtensions != 0) {
+ WebAuthNCredentialOptions.Extensions.cExtensions = cExtensions;
+ WebAuthNCredentialOptions.Extensions.pExtensions = rgExtension;
+ }
+
+ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation =
+ nullptr;
+
+ // Bug 1518876: Get Window Handle from Content process for Windows
+ // WebAuthN APIs
+ HWND hWnd = GetForegroundWindow();
+
+ HRESULT hr = gWinWebauthnMakeCredential(
+ hWnd, &rpInfo, &userInfo, &WebAuthNCredentialParameters,
+ &WebAuthNClientData, &WebAuthNCredentialOptions,
+ &pWebAuthNCredentialAttestation);
+
+ if (hr == S_OK) {
+ RefPtr<WebAuthnRegisterResult> result = new WebAuthnRegisterResult(
+ clientDataJSON, pWebAuthNCredentialAttestation);
+
+ // WEBAUTHN_CREDENTIAL_ATTESTATION structs of version >= 4 always
+ // include a flag to indicate whether a resident key was created. We
+ // copy that flag to the credProps extension output only if the RP
+ // requested the credProps extension.
+ if (requestedCredProps &&
+ pWebAuthNCredentialAttestation->dwVersion >=
+ WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4) {
+ BOOL rk = pWebAuthNCredentialAttestation->bResidentKey;
+ Unused << result->SetCredPropsRk(rk == TRUE);
+ }
+ gWinWebauthnFreeCredentialAttestation(pWebAuthNCredentialAttestation);
+
+ if (anonymize) {
+ nsresult rv = result->Anonymize();
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ }
+ aPromise->Resolve(result);
+ } else {
+ PCWSTR errorName = gWinWebauthnGetErrorName(hr);
+ nsresult aError = NS_ERROR_DOM_ABORT_ERR;
+
+ if (_wcsicmp(errorName, L"InvalidStateError") == 0) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (_wcsicmp(errorName, L"ConstraintError") == 0 ||
+ _wcsicmp(errorName, L"UnknownError") == 0) {
+ aError = NS_ERROR_DOM_UNKNOWN_ERR;
+ } else if (_wcsicmp(errorName, L"NotSupportedError") == 0) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (_wcsicmp(errorName, L"NotAllowedError") == 0) {
+ aError = NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ }
+
+ aPromise->Reject(aError);
+ }
+ }));
+
+ NS_DispatchBackgroundTask(runnable, NS_DISPATCH_EVENT_MAY_BLOCK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::GetAssertion(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnSignArgs* aArgs,
+ nsIWebAuthnSignPromise* aPromise) {
+ nsresult rv = EnsureWinWebAuthnModuleLoaded();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Reset();
+
+ auto guard = mTransactionState.Lock();
+
+ GUID cancellationId;
+ {
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (gWinWebauthnGetCancellationId(&cancellationId) != S_OK) {
+ // caller will reject promise
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ }
+
+ *guard = Some(TransactionState{
+ aTransactionId,
+ aBrowsingContextId,
+ Some(RefPtr{aArgs}),
+ Some(RefPtr{aPromise}),
+ cancellationId,
+ });
+
+ bool conditionallyMediated;
+ Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
+ if (!conditionallyMediated) {
+ DoGetAssertion(Nothing(), guard);
+ }
+ return NS_OK;
+}
+
+void WinWebAuthnService::DoGetAssertion(
+ Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
+ const TransactionStateMutex::AutoLock& aGuard) {
+ if (aGuard->isNothing() || aGuard->ref().pendingSignArgs.isNothing() ||
+ aGuard->ref().pendingSignPromise.isNothing()) {
+ return;
+ }
+
+ // Take the pending Args and Promise to prevent repeated calls to
+ // DoGetAssertion for this transaction.
+ RefPtr<nsIWebAuthnSignArgs> aArgs = aGuard->ref().pendingSignArgs.extract();
+ RefPtr<nsIWebAuthnSignPromise> aPromise =
+ aGuard->ref().pendingSignPromise.extract();
+
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "WinWebAuthnService::MakeCredential",
+ [self = RefPtr{this}, aArgs, aPromise,
+ aSelectedCredentialId = std::move(aSelectedCredentialId),
+ aCancellationId = aGuard->ref().cancellationId]() mutable {
+ // Take a read lock on gWinWebAuthnModuleLock to prevent the module from
+ // being unloaded while the operation is in progress. This does not
+ // prevent the operation from being cancelled, so it does not block a
+ // clean shutdown.
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (!gWinWebAuthnModule) {
+ aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ // Attachment
+ DWORD winAttachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
+
+ // AppId
+ BOOL bAppIdUsed = FALSE;
+ BOOL* pbAppIdUsed = nullptr;
+ PCWSTR winAppIdentifier = nullptr;
+
+ // Client Data
+ nsCString clientDataJSON;
+ Unused << aArgs->GetClientDataJSON(clientDataJSON);
+ WEBAUTHN_CLIENT_DATA WebAuthNClientData = {
+ WEBAUTHN_CLIENT_DATA_CURRENT_VERSION,
+ (DWORD)clientDataJSON.Length(), (BYTE*)(clientDataJSON.get()),
+ WEBAUTHN_HASH_ALGORITHM_SHA_256};
+
+ nsString appId;
+ nsresult rv = aArgs->GetAppId(appId);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(rv);
+ return;
+ }
+ winAppIdentifier = appId.get();
+ pbAppIdUsed = &bAppIdUsed;
+ }
+
+ // RPID
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+
+ // User Verification Requirement
+ nsString userVerificationReq;
+ Unused << aArgs->GetUserVerification(userVerificationReq);
+ DWORD winUserVerificationReq;
+ // This mapping needs to be reviewed if values are added to the
+ // UserVerificationRequirement enum.
+ static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION == 3);
+ if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ } else if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
+ } else if (userVerificationReq.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED)) {
+ winUserVerificationReq =
+ WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+ } else {
+ winUserVerificationReq = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
+ }
+
+ // allow Credentials
+ nsTArray<nsTArray<uint8_t>> allowList;
+ nsTArray<uint8_t> allowListTransports;
+ if (aSelectedCredentialId.isSome()) {
+ allowList.AppendElement(aSelectedCredentialId.extract());
+ allowListTransports.AppendElement(
+ MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL);
+ } else {
+ Unused << aArgs->GetAllowList(allowList);
+ Unused << aArgs->GetAllowListTransports(allowListTransports);
+ }
+
+ if (allowList.Length() != allowListTransports.Length()) {
+ aPromise->Reject(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ nsTArray<WEBAUTHN_CREDENTIAL_EX> allowCredentials;
+ WEBAUTHN_CREDENTIAL_EX* pAllowCredentials = nullptr;
+ nsTArray<WEBAUTHN_CREDENTIAL_EX*> allowCredentialsPtrs;
+ WEBAUTHN_CREDENTIAL_LIST allowCredentialList = {0};
+ WEBAUTHN_CREDENTIAL_LIST* pAllowCredentialList = nullptr;
+
+ for (size_t i = 0; i < allowList.Length(); i++) {
+ nsTArray<uint8_t>& cred = allowList[i];
+ uint8_t& transports = allowListTransports[i];
+ DWORD winTransports = 0;
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_USB;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_NFC;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_BLE;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
+ }
+ if (transports & MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID) {
+ winTransports |= WEBAUTHN_CTAP_TRANSPORT_HYBRID;
+ }
+
+ WEBAUTHN_CREDENTIAL_EX credential = {
+ WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
+ static_cast<DWORD>(cred.Length()), (PBYTE)(cred.Elements()),
+ WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, winTransports};
+ allowCredentials.AppendElement(credential);
+ }
+
+ if (allowCredentials.Length()) {
+ pAllowCredentials = allowCredentials.Elements();
+ for (DWORD i = 0; i < allowCredentials.Length(); i++) {
+ allowCredentialsPtrs.AppendElement(&pAllowCredentials[i]);
+ }
+ allowCredentialList.cCredentials = allowCredentials.Length();
+ allowCredentialList.ppCredentials = allowCredentialsPtrs.Elements();
+ pAllowCredentialList = &allowCredentialList;
+ }
+
+ uint32_t timeout_u32;
+ Unused << aArgs->GetTimeoutMS(&timeout_u32);
+ DWORD timeout = timeout_u32;
+
+ WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS WebAuthNAssertionOptions =
+ {
+ WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7,
+ timeout,
+ {0, NULL},
+ {0, NULL},
+ winAttachment,
+ winUserVerificationReq,
+ 0, // dwFlags
+ winAppIdentifier,
+ pbAppIdUsed,
+ &aCancellationId, // CancellationId
+ pAllowCredentialList,
+ WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE,
+ 0, // Size of CredLargeBlob
+ NULL, // CredLargeBlob
+ NULL, // HmacSecretSaltValues
+ FALSE, // BrowserInPrivateMode
+ NULL, // LinkedDevice
+ FALSE, // AutoFill
+ 0, // Size of JsonExt
+ NULL, // JsonExt
+ };
+
+ PWEBAUTHN_ASSERTION pWebAuthNAssertion = nullptr;
+
+ // Bug 1518876: Get Window Handle from Content process for Windows
+ // WebAuthN APIs
+ HWND hWnd = GetForegroundWindow();
+
+ HRESULT hr = gWinWebauthnGetAssertion(
+ hWnd, rpId.get(), &WebAuthNClientData, &WebAuthNAssertionOptions,
+ &pWebAuthNAssertion);
+
+ if (hr == S_OK) {
+ RefPtr<WebAuthnSignResult> result =
+ new WebAuthnSignResult(clientDataJSON, pWebAuthNAssertion);
+ gWinWebauthnFreeAssertion(pWebAuthNAssertion);
+ if (winAppIdentifier != nullptr) {
+ // The gWinWebauthnGetAssertion call modified bAppIdUsed through
+ // a pointer provided in WebAuthNAssertionOptions.
+ Unused << result->SetUsedAppId(bAppIdUsed == TRUE);
+ }
+ aPromise->Resolve(result);
+ } else {
+ PCWSTR errorName = gWinWebauthnGetErrorName(hr);
+ nsresult aError = NS_ERROR_DOM_ABORT_ERR;
+
+ if (_wcsicmp(errorName, L"InvalidStateError") == 0) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (_wcsicmp(errorName, L"ConstraintError") == 0 ||
+ _wcsicmp(errorName, L"UnknownError") == 0) {
+ aError = NS_ERROR_DOM_UNKNOWN_ERR;
+ } else if (_wcsicmp(errorName, L"NotSupportedError") == 0) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ } else if (_wcsicmp(errorName, L"NotAllowedError") == 0) {
+ aError = NS_ERROR_DOM_NOT_ALLOWED_ERR;
+ }
+
+ aPromise->Reject(aError);
+ }
+ }));
+
+ NS_DispatchBackgroundTask(runnable, NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
+ const nsAString& aOrigin,
+ uint64_t* aRv) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() ||
+ guard->ref().browsingContextId != aBrowsingContextId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ *aRv = 0;
+ return NS_OK;
+ }
+
+ nsString origin;
+ Unused << guard->ref().pendingSignArgs.ref()->GetOrigin(origin);
+ if (origin != aOrigin) {
+ *aRv = 0;
+ return NS_OK;
+ }
+
+ *aRv = guard->ref().transactionId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::GetAutoFillEntries(
+ uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ StaticAutoReadLock moduleLock(gWinWebAuthnModuleLock);
+ if (!gWinWebAuthnModule) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aRv.Clear();
+
+ if (gWinWebauthnGetApiVersionNumber() < WEBAUTHN_API_VERSION_4) {
+ // GetPlatformCredentialList was added in version 4. Earlier versions
+ // can still present a generic "Use a Passkey" autofill entry, so
+ // this isn't an error.
+ return NS_OK;
+ }
+
+ nsString rpId;
+ Unused << guard->ref().pendingSignArgs.ref()->GetRpId(rpId);
+
+ WEBAUTHN_GET_CREDENTIALS_OPTIONS getCredentialsOptions{
+ WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1,
+ rpId.get(), // pwszRpId
+ FALSE, // bBrowserInPrivateMode
+ };
+ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialList = nullptr;
+ HRESULT hr = gWinWebauthnGetPlatformCredentialList(&getCredentialsOptions,
+ &pCredentialList);
+ // WebAuthNGetPlatformCredentialList has an _Outptr_result_maybenull_
+ // annotation and a comment "Returns NTE_NOT_FOUND when credentials are
+ // not found."
+ if (pCredentialList == nullptr) {
+ if (hr != NTE_NOT_FOUND) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+ MOZ_ASSERT(hr == S_OK);
+ for (size_t i = 0; i < pCredentialList->cCredentialDetails; i++) {
+ RefPtr<nsIWebAuthnAutoFillEntry> entry(
+ new WebAuthnAutoFillEntry(pCredentialList->ppCredentialDetails[i]));
+ aRv.AppendElement(entry);
+ }
+ gWinWebauthnFreePlatformCredentialList(pCredentialList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::SelectAutoFillEntry(
+ uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsTArray<uint8_t>> allowList;
+ Unused << guard->ref().pendingSignArgs.ref()->GetAllowList(allowList);
+ if (!allowList.IsEmpty() && !allowList.Contains(aCredentialId)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Maybe<nsTArray<uint8_t>> id;
+ id.emplace();
+ id.ref().Assign(aCredentialId);
+ DoGetAssertion(std::move(id), guard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
+ auto guard = mTransactionState.Lock();
+ if (guard->isNothing() || guard->ref().transactionId != aTransactionId ||
+ guard->ref().pendingSignArgs.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ DoGetAssertion(Nothing(), guard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::PinCallback(uint64_t aTransactionId,
+ const nsACString& aPin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
+ bool aForceNoneAttestation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::SelectionCallback(uint64_t aTransactionId,
+ uint64_t aIndex) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::AddVirtualAuthenticator(
+ const nsACString& protocol, const nsACString& transport,
+ bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
+ bool isUserVerified, uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::AddCredential(uint64_t authenticatorId,
+ const nsACString& credentialId,
+ bool isResidentCredential,
+ const nsACString& rpId,
+ const nsACString& privateKey,
+ const nsACString& userHandle,
+ uint32_t signCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::GetCredentials(
+ uint64_t authenticatorId,
+ nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::RemoveCredential(uint64_t authenticatorId,
+ const nsACString& credentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::SetUserVerified(uint64_t authenticatorId,
+ bool isUserVerified) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WinWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+WinWebAuthnService::RunCommand(const nsACString& cmd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webauthn/WinWebAuthnService.h b/dom/webauthn/WinWebAuthnService.h
new file mode 100644
index 0000000000..8312ae83e1
--- /dev/null
+++ b/dom/webauthn/WinWebAuthnService.h
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_WinWebAuthnService_h
+#define mozilla_dom_WinWebAuthnService_h
+
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "mozilla/Tainting.h"
+#include "nsIWebAuthnService.h"
+
+namespace mozilla::dom {
+
+class WinWebAuthnService final : public nsIWebAuthnService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBAUTHNSERVICE
+
+ static bool IsUserVerifyingPlatformAuthenticatorAvailable();
+ static bool AreWebAuthNApisAvailable();
+ static nsresult EnsureWinWebAuthnModuleLoaded();
+
+ WinWebAuthnService()
+ : mTransactionState(Nothing(), "WinWebAuthnService::mTransactionState"){};
+
+ private:
+ ~WinWebAuthnService();
+
+ uint32_t GetWebAuthNApiVersion();
+
+ struct TransactionState {
+ uint64_t transactionId;
+ uint64_t browsingContextId;
+ Maybe<RefPtr<nsIWebAuthnSignArgs>> pendingSignArgs;
+ Maybe<RefPtr<nsIWebAuthnSignPromise>> pendingSignPromise;
+ GUID cancellationId;
+ };
+
+ using TransactionStateMutex = DataMutex<Maybe<TransactionState>>;
+ TransactionStateMutex mTransactionState;
+ void DoGetAssertion(Maybe<nsTArray<uint8_t>>&& aSelectedCredentialId,
+ const TransactionStateMutex::AutoLock& aGuard);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WinWebAuthnService_h
diff --git a/dom/webauthn/authrs_bridge/Cargo.toml b/dom/webauthn/authrs_bridge/Cargo.toml
new file mode 100644
index 0000000000..44c690a2b1
--- /dev/null
+++ b/dom/webauthn/authrs_bridge/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "authrs_bridge"
+version = "0.1.0"
+edition = "2021"
+authors = ["Martin Sirringhaus", "John Schanck"]
+
+[dependencies]
+authenticator = { version = "0.4.0-alpha.24", features = ["gecko"] }
+base64 = "^0.21"
+cstr = "0.2"
+log = "0.4"
+moz_task = { path = "../../../xpcom/rust/moz_task" }
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+rand = "0.8"
+serde = { version = "1", features = ["derive"] }
+serde_cbor = "0.11"
+serde_json = "1.0"
+static_prefs = { path = "../../../modules/libpref/init/static_prefs" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+
+[features]
+fuzzing = []
diff --git a/dom/webauthn/authrs_bridge/src/about_webauthn_controller.rs b/dom/webauthn/authrs_bridge/src/about_webauthn_controller.rs
new file mode 100644
index 0000000000..8d77a62df4
--- /dev/null
+++ b/dom/webauthn/authrs_bridge/src/about_webauthn_controller.rs
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::*;
+use authenticator::{
+ ctap2::commands::{PinUvAuthResult, StatusCode},
+ errors::{CommandError, HIDError},
+ BioEnrollmentCmd, CredManagementCmd, InteractiveRequest, InteractiveUpdate, PinError,
+};
+use serde::{Deserialize, Serialize};
+
+pub(crate) type InteractiveManagementReceiver = Option<Sender<InteractiveRequest>>;
+pub(crate) fn send_about_prompt(prompt: &BrowserPromptType) -> Result<(), nsresult> {
+ let json = nsString::from(&serde_json::to_string(&prompt).unwrap_or_default());
+ notify_observers(PromptTarget::AboutPage, json)
+}
+
+// A wrapper around InteractiveRequest, that leaves out the PUAT
+// so that we can easily de/serialize it to/from JSON for the JS-side
+// and then add our cached PUAT, if we have one.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum RequestWrapper {
+ Quit,
+ ChangePIN(Pin, Pin),
+ SetPIN(Pin),
+ CredentialManagement(CredManagementCmd),
+ BioEnrollment(BioEnrollmentCmd),
+}
+
+pub(crate) fn authrs_to_prompt<'a>(e: AuthenticatorError) -> BrowserPromptType<'a> {
+ match e {
+ AuthenticatorError::PinError(PinError::PinIsTooShort) => BrowserPromptType::PinIsTooShort,
+ AuthenticatorError::PinError(PinError::PinNotSet) => BrowserPromptType::PinNotSet,
+ AuthenticatorError::PinError(PinError::PinRequired) => BrowserPromptType::PinRequired,
+ AuthenticatorError::PinError(PinError::PinIsTooLong(_)) => BrowserPromptType::PinIsTooLong,
+ AuthenticatorError::PinError(PinError::InvalidPin(r)) => {
+ BrowserPromptType::PinInvalid { retries: r }
+ }
+ AuthenticatorError::PinError(PinError::PinAuthBlocked) => BrowserPromptType::PinAuthBlocked,
+ AuthenticatorError::PinError(PinError::PinBlocked) => BrowserPromptType::DeviceBlocked,
+ AuthenticatorError::PinError(PinError::UvBlocked) => BrowserPromptType::UvBlocked,
+ AuthenticatorError::PinError(PinError::InvalidUv(r)) => {
+ BrowserPromptType::UvInvalid { retries: r }
+ }
+ AuthenticatorError::CancelledByUser
+ | AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::KeepaliveCancel,
+ _,
+ ))) => BrowserPromptType::Cancel,
+ _ => BrowserPromptType::UnknownError,
+ }
+}
+
+pub(crate) fn cache_puat(
+ transaction: Arc<Mutex<Option<TransactionState>>>,
+ puat: Option<PinUvAuthResult>,
+) {
+ let mut guard = transaction.lock().unwrap();
+ if let Some(transaction) = guard.as_mut() {
+ transaction.puat_cache = puat;
+ };
+}
+
+pub(crate) fn interactive_status_callback(
+ status_rx: Receiver<StatusUpdate>,
+ transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsTransport */
+ upcoming_error: Arc<Mutex<Option<AuthenticatorError>>>,
+) -> Result<(), nsresult> {
+ loop {
+ match status_rx.recv() {
+ Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::StartManagement((
+ tx,
+ auth_info,
+ )))) => {
+ let mut guard = transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ warn!("STATUS: received status update after end of transaction.");
+ break;
+ };
+ transaction.interactive_receiver.replace(tx);
+ let prompt = BrowserPromptType::SelectedDevice { auth_info };
+ send_about_prompt(&prompt)?;
+ }
+ Ok(StatusUpdate::InteractiveManagement(
+ InteractiveUpdate::CredentialManagementUpdate((cfg_result, puat_res)),
+ )) => {
+ cache_puat(transaction.clone(), puat_res); // We don't care if we fail here. Worst-case: User has to enter PIN more often.
+ let prompt = BrowserPromptType::CredentialManagementUpdate { result: cfg_result };
+ send_about_prompt(&prompt)?;
+ continue;
+ }
+ Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::BioEnrollmentUpdate((
+ bio_res,
+ puat_res,
+ )))) => {
+ cache_puat(transaction.clone(), puat_res); // We don't care if we fail here. Worst-case: User has to enter PIN more often.
+ let prompt = BrowserPromptType::BioEnrollmentUpdate { result: bio_res };
+ send_about_prompt(&prompt)?;
+ continue;
+ }
+ Ok(StatusUpdate::SelectDeviceNotice) => {
+ info!("STATUS: Please select a device by touching one of them.");
+ let prompt = BrowserPromptType::SelectDevice;
+ send_about_prompt(&prompt)?;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ let mut guard = transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ warn!("STATUS: received status update after end of transaction.");
+ break;
+ };
+ let autherr = match e {
+ StatusPinUv::PinRequired(pin_sender) => {
+ transaction.pin_receiver.replace((0, pin_sender));
+ send_about_prompt(&BrowserPromptType::PinRequired)?;
+ continue;
+ }
+ StatusPinUv::InvalidPin(pin_sender, r) => {
+ transaction.pin_receiver.replace((0, pin_sender));
+ send_about_prompt(&BrowserPromptType::PinInvalid { retries: r })?;
+ continue;
+ }
+ StatusPinUv::PinIsTooShort => {
+ AuthenticatorError::PinError(PinError::PinIsTooShort)
+ }
+ StatusPinUv::PinIsTooLong(s) => {
+ AuthenticatorError::PinError(PinError::PinIsTooLong(s))
+ }
+ StatusPinUv::InvalidUv(r) => {
+ send_about_prompt(&BrowserPromptType::UvInvalid { retries: r })?;
+ continue;
+ }
+ StatusPinUv::PinAuthBlocked => {
+ AuthenticatorError::PinError(PinError::PinAuthBlocked)
+ }
+ StatusPinUv::PinBlocked => AuthenticatorError::PinError(PinError::PinBlocked),
+ StatusPinUv::PinNotSet => AuthenticatorError::PinError(PinError::PinNotSet),
+ StatusPinUv::UvBlocked => AuthenticatorError::PinError(PinError::UvBlocked),
+ };
+ // We will cause auth-rs to return an error, once we leave this block
+ // due to us 'hanging up'. Before we do that, we will safe the actual
+ // error that caused this, so our callback-function can return the true
+ // error to JS, instead of "cancelled by user".
+ let guard = upcoming_error.lock();
+ if let Ok(mut entry) = guard {
+ entry.replace(autherr);
+ } else {
+ return Err(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ warn!("STATUS: Pin Error {:?}", e);
+ break;
+ }
+
+ Ok(_) => {
+ // currently not handled
+ continue;
+ }
+ Err(RecvError) => {
+ info!("STATUS: end");
+ break;
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/dom/webauthn/authrs_bridge/src/lib.rs b/dom/webauthn/authrs_bridge/src/lib.rs
new file mode 100644
index 0000000000..353e1e89a4
--- /dev/null
+++ b/dom/webauthn/authrs_bridge/src/lib.rs
@@ -0,0 +1,1534 @@
+/* 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/. */
+
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+extern crate xpcom;
+
+use authenticator::{
+ authenticatorservice::{RegisterArgs, SignArgs},
+ ctap2::attestation::AttestationObject,
+ ctap2::commands::{get_info::AuthenticatorVersion, PinUvAuthResult},
+ ctap2::server::{
+ AuthenticationExtensionsClientInputs, AuthenticatorAttachment,
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
+ PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement,
+ UserVerificationRequirement,
+ },
+ errors::AuthenticatorError,
+ statecallback::StateCallback,
+ AuthenticatorInfo, BioEnrollmentResult, CredentialManagementResult, InteractiveRequest,
+ ManageResult, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
+};
+use base64::Engine;
+use cstr::cstr;
+use moz_task::{get_main_thread, RunnableBuilder};
+use nserror::{
+ nsresult, NS_ERROR_DOM_ABORT_ERR, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
+ NS_ERROR_DOM_OPERATION_ERR, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE,
+ NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, NS_OK,
+};
+use nsstring::{nsACString, nsAString, nsCString, nsString};
+use serde::Serialize;
+use serde_cbor;
+use serde_json::json;
+use std::fmt::Write;
+use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
+use std::sync::{Arc, Mutex, MutexGuard};
+use thin_vec::{thin_vec, ThinVec};
+use xpcom::interfaces::{
+ nsICredentialParameters, nsIObserverService, nsIWebAuthnAttObj, nsIWebAuthnAutoFillEntry,
+ nsIWebAuthnRegisterArgs, nsIWebAuthnRegisterPromise, nsIWebAuthnRegisterResult,
+ nsIWebAuthnService, nsIWebAuthnSignArgs, nsIWebAuthnSignPromise, nsIWebAuthnSignResult,
+};
+use xpcom::{xpcom_method, RefPtr};
+mod about_webauthn_controller;
+use about_webauthn_controller::*;
+mod test_token;
+use test_token::TestTokenManager;
+
+fn authrs_to_nserror(e: AuthenticatorError) -> nsresult {
+ match e {
+ AuthenticatorError::CredentialExcluded => NS_ERROR_DOM_INVALID_STATE_ERR,
+ _ => NS_ERROR_DOM_NOT_ALLOWED_ERR,
+ }
+}
+
+fn should_cancel_prompts<T>(result: &Result<T, AuthenticatorError>) -> bool {
+ match result {
+ Err(AuthenticatorError::CredentialExcluded) | Err(AuthenticatorError::PinError(_)) => false,
+ _ => true,
+ }
+}
+
+// Using serde(tag="type") makes it so that, for example, BrowserPromptType::Cancel is serialized
+// as '{ type: "cancel" }', and BrowserPromptType::PinInvalid { retries: 5 } is serialized as
+// '{type: "pin-invalid", retries: 5}'.
+#[derive(Serialize)]
+#[serde(tag = "type", rename_all = "kebab-case")]
+enum BrowserPromptType<'a> {
+ AlreadyRegistered,
+ Cancel,
+ DeviceBlocked,
+ PinAuthBlocked,
+ PinNotSet,
+ Presence,
+ SelectDevice,
+ UvBlocked,
+ PinRequired,
+ SelectedDevice {
+ auth_info: Option<AuthenticatorInfo>,
+ },
+ PinInvalid {
+ retries: Option<u8>,
+ },
+ PinIsTooLong,
+ PinIsTooShort,
+ RegisterDirect,
+ UvInvalid {
+ retries: Option<u8>,
+ },
+ SelectSignResult {
+ entities: &'a [PublicKeyCredentialUserEntity],
+ },
+ ListenSuccess,
+ ListenError {
+ error: Box<BrowserPromptType<'a>>,
+ },
+ CredentialManagementUpdate {
+ result: CredentialManagementResult,
+ },
+ BioEnrollmentUpdate {
+ result: BioEnrollmentResult,
+ },
+ UnknownError,
+}
+
+#[derive(Debug)]
+enum PromptTarget {
+ Browser,
+ AboutPage,
+}
+
+#[derive(Serialize)]
+struct BrowserPromptMessage<'a> {
+ prompt: BrowserPromptType<'a>,
+ tid: u64,
+ origin: Option<&'a str>,
+ #[serde(rename = "browsingContextId")]
+ browsing_context_id: Option<u64>,
+}
+
+fn notify_observers(prompt_target: PromptTarget, json: nsString) -> Result<(), nsresult> {
+ let main_thread = get_main_thread()?;
+ let target = match prompt_target {
+ PromptTarget::Browser => cstr!("webauthn-prompt"),
+ PromptTarget::AboutPage => cstr!("about-webauthn-prompt"),
+ };
+
+ RunnableBuilder::new("AuthrsService::send_prompt", move || {
+ if let Ok(obs_svc) = xpcom::components::Observer::service::<nsIObserverService>() {
+ unsafe {
+ obs_svc.NotifyObservers(std::ptr::null(), target.as_ptr(), json.as_ptr());
+ }
+ }
+ })
+ .dispatch(main_thread.coerce())
+}
+
+fn send_prompt(
+ prompt: BrowserPromptType,
+ tid: u64,
+ origin: Option<&str>,
+ browsing_context_id: Option<u64>,
+) -> Result<(), nsresult> {
+ let mut json = nsString::new();
+ write!(
+ json,
+ "{}",
+ json!(&BrowserPromptMessage {
+ prompt,
+ tid,
+ origin,
+ browsing_context_id
+ })
+ )
+ .or(Err(NS_ERROR_FAILURE))?;
+ notify_observers(PromptTarget::Browser, json)
+}
+
+fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
+ send_prompt(BrowserPromptType::Cancel, tid, None, None)?;
+ Ok(())
+}
+
+#[xpcom(implement(nsIWebAuthnRegisterResult), atomic)]
+pub struct WebAuthnRegisterResult {
+ result: RegisterResult,
+}
+
+impl WebAuthnRegisterResult {
+ xpcom_method!(get_client_data_json => GetClientDataJSON() -> nsACString);
+ fn get_client_data_json(&self) -> Result<nsCString, nsresult> {
+ Err(NS_ERROR_NOT_AVAILABLE)
+ }
+
+ xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
+ fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
+ let mut out = ThinVec::new();
+ serde_cbor::to_writer(&mut out, &self.result.att_obj).or(Err(NS_ERROR_FAILURE))?;
+ Ok(out)
+ }
+
+ xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
+ fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
+ let Some(credential_data) = &self.result.att_obj.auth_data.credential_data else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ Ok(credential_data.credential_id.as_slice().into())
+ }
+
+ xpcom_method!(get_transports => GetTransports() -> ThinVec<nsString>);
+ fn get_transports(&self) -> Result<ThinVec<nsString>, nsresult> {
+ // The list that we return here might be included in a future GetAssertion request as a
+ // hint as to which transports to try. In production, we only support the "usb" transport.
+ // In tests, the result is not very important, but we can at least return "internal" if
+ // we're simulating platform attachment.
+ if static_prefs::pref!("security.webauth.webauthn_enable_softtoken")
+ && self.result.attachment == AuthenticatorAttachment::Platform
+ {
+ Ok(thin_vec![nsString::from("internal")])
+ } else {
+ Ok(thin_vec![nsString::from("usb")])
+ }
+ }
+
+ xpcom_method!(get_hmac_create_secret => GetHmacCreateSecret() -> bool);
+ fn get_hmac_create_secret(&self) -> Result<bool, nsresult> {
+ let Some(hmac_create_secret) = self.result.extensions.hmac_create_secret else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ Ok(hmac_create_secret)
+ }
+
+ xpcom_method!(get_cred_props_rk => GetCredPropsRk() -> bool);
+ fn get_cred_props_rk(&self) -> Result<bool, nsresult> {
+ let Some(cred_props) = &self.result.extensions.cred_props else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ Ok(cred_props.rk)
+ }
+
+ xpcom_method!(set_cred_props_rk => SetCredPropsRk(aCredPropsRk: bool));
+ fn set_cred_props_rk(&self, _cred_props_rk: bool) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+
+ xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
+ fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
+ match self.result.attachment {
+ AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
+ AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
+ AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
+ }
+ }
+}
+
+#[xpcom(implement(nsIWebAuthnAttObj), atomic)]
+pub struct WebAuthnAttObj {
+ att_obj: AttestationObject,
+}
+
+impl WebAuthnAttObj {
+ xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
+ fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
+ let mut out = ThinVec::new();
+ serde_cbor::to_writer(&mut out, &self.att_obj).or(Err(NS_ERROR_FAILURE))?;
+ Ok(out)
+ }
+
+ xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
+ fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
+ // TODO(https://github.com/mozilla/authenticator-rs/issues/302) use to_writer
+ Ok(self.att_obj.auth_data.to_vec().into())
+ }
+
+ xpcom_method!(get_public_key => GetPublicKey() -> ThinVec<u8>);
+ fn get_public_key(&self) -> Result<ThinVec<u8>, nsresult> {
+ let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ Ok(credential_data
+ .credential_public_key
+ .der_spki()
+ .or(Err(NS_ERROR_NOT_AVAILABLE))?
+ .into())
+ }
+
+ xpcom_method!(get_public_key_algorithm => GetPublicKeyAlgorithm() -> i32);
+ fn get_public_key_algorithm(&self) -> Result<i32, nsresult> {
+ let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ // safe to cast to i32 by inspection of defined values
+ Ok(credential_data.credential_public_key.alg as i32)
+ }
+}
+
+#[xpcom(implement(nsIWebAuthnSignResult), atomic)]
+pub struct WebAuthnSignResult {
+ result: SignResult,
+}
+
+impl WebAuthnSignResult {
+ xpcom_method!(get_client_data_json => GetClientDataJSON() -> nsACString);
+ fn get_client_data_json(&self) -> Result<nsCString, nsresult> {
+ Err(NS_ERROR_NOT_AVAILABLE)
+ }
+
+ xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
+ fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
+ let Some(cred) = &self.result.assertion.credentials else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ Ok(cred.id.as_slice().into())
+ }
+
+ xpcom_method!(get_signature => GetSignature() -> ThinVec<u8>);
+ fn get_signature(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.result.assertion.signature.as_slice().into())
+ }
+
+ xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
+ fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.result.assertion.auth_data.to_vec().into())
+ }
+
+ xpcom_method!(get_user_handle => GetUserHandle() -> ThinVec<u8>);
+ fn get_user_handle(&self) -> Result<ThinVec<u8>, nsresult> {
+ let Some(user) = &self.result.assertion.user else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ Ok(user.id.as_slice().into())
+ }
+
+ xpcom_method!(get_user_name => GetUserName() -> nsACString);
+ fn get_user_name(&self) -> Result<nsCString, nsresult> {
+ let Some(user) = &self.result.assertion.user else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ let Some(name) = &user.name else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ Ok(nsCString::from(name))
+ }
+
+ xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
+ fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
+ match self.result.attachment {
+ AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
+ AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
+ AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
+ }
+ }
+
+ xpcom_method!(get_used_app_id => GetUsedAppId() -> bool);
+ fn get_used_app_id(&self) -> Result<bool, nsresult> {
+ self.result.extensions.app_id.ok_or(NS_ERROR_NOT_AVAILABLE)
+ }
+
+ xpcom_method!(set_used_app_id => SetUsedAppId(aUsedAppId: bool));
+ fn set_used_app_id(&self, _used_app_id: bool) -> Result<(), nsresult> {
+ Err(NS_ERROR_NOT_IMPLEMENTED)
+ }
+}
+
+// A transaction may create a channel to ask a user for additional input, e.g. a PIN. The Sender
+// component of this channel is sent to an AuthrsServide in a StatusUpdate. AuthrsService
+// caches the sender along with the expected (u64) transaction ID, which is used as a consistency
+// check in callbacks.
+type PinReceiver = Option<(u64, Sender<Pin>)>;
+type SelectionReceiver = Option<(u64, Sender<Option<usize>>)>;
+
+fn status_callback(
+ status_rx: Receiver<StatusUpdate>,
+ tid: u64,
+ origin: &String,
+ browsing_context_id: u64,
+ transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsService */
+) -> Result<(), nsresult> {
+ let origin = Some(origin.as_str());
+ let browsing_context_id = Some(browsing_context_id);
+ loop {
+ match status_rx.recv() {
+ Ok(StatusUpdate::SelectDeviceNotice) => {
+ debug!("STATUS: Please select a device by touching one of them.");
+ send_prompt(
+ BrowserPromptType::SelectDevice,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ debug!("STATUS: Waiting for user presence");
+ send_prompt(
+ BrowserPromptType::Presence,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let mut guard = transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ warn!("STATUS: received status update after end of transaction.");
+ break;
+ };
+ transaction.pin_receiver.replace((tid, sender));
+ send_prompt(
+ BrowserPromptType::PinRequired,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, retries))) => {
+ let mut guard = transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ warn!("STATUS: received status update after end of transaction.");
+ break;
+ };
+ transaction.pin_receiver.replace((tid, sender));
+ send_prompt(
+ BrowserPromptType::PinInvalid { retries },
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ send_prompt(
+ BrowserPromptType::PinAuthBlocked,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ send_prompt(
+ BrowserPromptType::DeviceBlocked,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinNotSet)) => {
+ send_prompt(
+ BrowserPromptType::PinNotSet,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(retries))) => {
+ send_prompt(
+ BrowserPromptType::UvInvalid { retries },
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ send_prompt(
+ BrowserPromptType::UvBlocked,
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooShort))
+ | Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooLong(..))) => {
+ // These should never happen.
+ warn!("STATUS: Got unexpected StatusPinUv-error.");
+ }
+ Ok(StatusUpdate::InteractiveManagement(_)) => {
+ debug!("STATUS: interactive management");
+ }
+ Ok(StatusUpdate::SelectResultNotice(sender, entities)) => {
+ debug!("STATUS: select result notice");
+ let mut guard = transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ warn!("STATUS: received status update after end of transaction.");
+ break;
+ };
+ transaction.selection_receiver.replace((tid, sender));
+ send_prompt(
+ BrowserPromptType::SelectSignResult {
+ entities: &entities,
+ },
+ tid,
+ origin,
+ browsing_context_id,
+ )?;
+ }
+ Err(RecvError) => {
+ debug!("STATUS: end");
+ break;
+ }
+ }
+ }
+ Ok(())
+}
+
+#[derive(Clone)]
+struct RegisterPromise(RefPtr<nsIWebAuthnRegisterPromise>);
+
+impl RegisterPromise {
+ fn resolve_or_reject(&self, result: Result<RegisterResult, nsresult>) -> Result<(), nsresult> {
+ match result {
+ Ok(result) => {
+ let wrapped_result =
+ WebAuthnRegisterResult::allocate(InitWebAuthnRegisterResult { result })
+ .query_interface::<nsIWebAuthnRegisterResult>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ unsafe { self.0.Resolve(wrapped_result.coerce()) };
+ }
+ Err(result) => {
+ unsafe { self.0.Reject(result) };
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+struct SignPromise(RefPtr<nsIWebAuthnSignPromise>);
+
+impl SignPromise {
+ fn resolve_or_reject(&self, result: Result<SignResult, nsresult>) -> Result<(), nsresult> {
+ match result {
+ Ok(result) => {
+ let wrapped_result =
+ WebAuthnSignResult::allocate(InitWebAuthnSignResult { result })
+ .query_interface::<nsIWebAuthnSignResult>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ unsafe { self.0.Resolve(wrapped_result.coerce()) };
+ }
+ Err(result) => {
+ unsafe { self.0.Reject(result) };
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+enum TransactionPromise {
+ Listen,
+ Register(RegisterPromise),
+ Sign(SignPromise),
+}
+
+impl TransactionPromise {
+ fn reject(&self, err: nsresult) -> Result<(), nsresult> {
+ match self {
+ TransactionPromise::Listen => Ok(()),
+ TransactionPromise::Register(promise) => promise.resolve_or_reject(Err(err)),
+ TransactionPromise::Sign(promise) => promise.resolve_or_reject(Err(err)),
+ }
+ }
+}
+
+enum TransactionArgs {
+ Register(/* timeout */ u64, RegisterArgs),
+ Sign(/* timeout */ u64, SignArgs),
+}
+
+struct TransactionState {
+ tid: u64,
+ browsing_context_id: u64,
+ pending_args: Option<TransactionArgs>,
+ promise: TransactionPromise,
+ pin_receiver: PinReceiver,
+ selection_receiver: SelectionReceiver,
+ interactive_receiver: InteractiveManagementReceiver,
+ puat_cache: Option<PinUvAuthResult>, // Cached credential to avoid repeated PIN-entries
+}
+
+// AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
+#[xpcom(implement(nsIWebAuthnService), atomic)]
+pub struct AuthrsService {
+ usb_token_manager: Mutex<StateMachine>,
+ test_token_manager: TestTokenManager,
+ transaction: Arc<Mutex<Option<TransactionState>>>,
+}
+
+impl AuthrsService {
+ xpcom_method!(pin_callback => PinCallback(aTransactionId: u64, aPin: *const nsACString));
+ fn pin_callback(&self, transaction_id: u64, pin: &nsACString) -> Result<(), nsresult> {
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ // No ongoing transaction
+ return Err(NS_ERROR_FAILURE);
+ };
+ let Some((tid, channel)) = transaction.pin_receiver.take() else {
+ // We weren't expecting a pin.
+ return Err(NS_ERROR_FAILURE);
+ };
+ if tid != transaction_id {
+ // The browser is confused about which transaction is active.
+ // This shouldn't happen
+ return Err(NS_ERROR_FAILURE);
+ }
+ channel
+ .send(Pin::new(&pin.to_string()))
+ .or(Err(NS_ERROR_FAILURE))
+ } else {
+ // Silently accept request, if all webauthn-options are disabled.
+ // Used for testing.
+ Ok(())
+ }
+ }
+
+ xpcom_method!(selection_callback => SelectionCallback(aTransactionId: u64, aSelection: u64));
+ fn selection_callback(&self, transaction_id: u64, selection: u64) -> Result<(), nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(transaction) = guard.as_mut() else {
+ // No ongoing transaction
+ return Err(NS_ERROR_FAILURE);
+ };
+ let Some((tid, channel)) = transaction.selection_receiver.take() else {
+ // We weren't expecting a selection.
+ return Err(NS_ERROR_FAILURE);
+ };
+ if tid != transaction_id {
+ // The browser is confused about which transaction is active.
+ // This shouldn't happen
+ return Err(NS_ERROR_FAILURE);
+ }
+ channel
+ .send(Some(selection as usize))
+ .or(Err(NS_ERROR_FAILURE))
+ }
+
+ xpcom_method!(get_is_uvpaa => GetIsUVPAA() -> bool);
+ fn get_is_uvpaa(&self) -> Result<bool, nsresult> {
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ Ok(false)
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ Ok(self.test_token_manager.has_platform_authenticator())
+ } else {
+ Err(NS_ERROR_NOT_AVAILABLE)
+ }
+ }
+
+ xpcom_method!(make_credential => MakeCredential(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnRegisterArgs, aPromise: *const nsIWebAuthnRegisterPromise));
+ fn make_credential(
+ &self,
+ tid: u64,
+ browsing_context_id: u64,
+ args: &nsIWebAuthnRegisterArgs,
+ promise: &nsIWebAuthnRegisterPromise,
+ ) -> Result<(), nsresult> {
+ self.reset()?;
+
+ let promise = RegisterPromise(RefPtr::new(promise));
+
+ let mut origin = nsString::new();
+ unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
+
+ let mut relying_party_id = nsString::new();
+ unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
+
+ let mut client_data_hash = ThinVec::new();
+ unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
+ let mut client_data_hash_arr = [0u8; 32];
+ client_data_hash_arr.copy_from_slice(&client_data_hash);
+
+ let mut timeout_ms = 0u32;
+ unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
+
+ let mut exclude_list = ThinVec::new();
+ unsafe { args.GetExcludeList(&mut exclude_list) }.to_result()?;
+ let exclude_list = exclude_list
+ .iter_mut()
+ .map(|id| PublicKeyCredentialDescriptor {
+ id: id.to_vec(),
+ transports: vec![],
+ })
+ .collect();
+
+ let mut relying_party_name = nsString::new();
+ unsafe { args.GetRpName(&mut *relying_party_name) }.to_result()?;
+
+ let mut user_id = ThinVec::new();
+ unsafe { args.GetUserId(&mut user_id) }.to_result()?;
+
+ let mut user_name = nsString::new();
+ unsafe { args.GetUserName(&mut *user_name) }.to_result()?;
+
+ let mut user_display_name = nsString::new();
+ unsafe { args.GetUserDisplayName(&mut *user_display_name) }.to_result()?;
+
+ let mut cose_algs = ThinVec::new();
+ unsafe { args.GetCoseAlgs(&mut cose_algs) }.to_result()?;
+ let pub_cred_params = cose_algs
+ .iter()
+ .filter_map(|alg| PublicKeyCredentialParameters::try_from(*alg).ok())
+ .collect();
+
+ let mut resident_key = nsString::new();
+ unsafe { args.GetResidentKey(&mut *resident_key) }.to_result()?;
+ let resident_key_req = if resident_key.eq("required") {
+ ResidentKeyRequirement::Required
+ } else if resident_key.eq("preferred") {
+ ResidentKeyRequirement::Preferred
+ } else if resident_key.eq("discouraged") {
+ ResidentKeyRequirement::Discouraged
+ } else {
+ return Err(NS_ERROR_FAILURE);
+ };
+
+ let mut user_verification = nsString::new();
+ unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
+ let user_verification_req = if user_verification.eq("required") {
+ UserVerificationRequirement::Required
+ } else if user_verification.eq("discouraged") {
+ UserVerificationRequirement::Discouraged
+ } else {
+ UserVerificationRequirement::Preferred
+ };
+
+ let mut authenticator_attachment = nsString::new();
+ if unsafe { args.GetAuthenticatorAttachment(&mut *authenticator_attachment) }
+ .to_result()
+ .is_ok()
+ {
+ if authenticator_attachment.eq("platform") {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ let mut attestation_conveyance_preference = nsString::new();
+ unsafe { args.GetAttestationConveyancePreference(&mut *attestation_conveyance_preference) }
+ .to_result()?;
+ let none_attestation = !(attestation_conveyance_preference.eq("indirect")
+ || attestation_conveyance_preference.eq("direct")
+ || attestation_conveyance_preference.eq("enterprise"));
+
+ let mut cred_props = false;
+ unsafe { args.GetCredProps(&mut cred_props) }.to_result()?;
+
+ let mut min_pin_length = false;
+ unsafe { args.GetMinPinLength(&mut min_pin_length) }.to_result()?;
+
+ // TODO(Bug 1593571) - Add this to the extensions
+ // let mut hmac_create_secret = None;
+ // let mut maybe_hmac_create_secret = false;
+ // match unsafe { args.GetHmacCreateSecret(&mut maybe_hmac_create_secret) }.to_result() {
+ // Ok(_) => hmac_create_secret = Some(maybe_hmac_create_secret),
+ // _ => (),
+ // }
+
+ let origin = origin.to_string();
+ let info = RegisterArgs {
+ client_data_hash: client_data_hash_arr,
+ relying_party: RelyingParty {
+ id: relying_party_id.to_string(),
+ name: None,
+ },
+ origin: origin.clone(),
+ user: PublicKeyCredentialUserEntity {
+ id: user_id.to_vec(),
+ name: Some(user_name.to_string()),
+ display_name: None,
+ },
+ pub_cred_params,
+ exclude_list,
+ user_verification_req,
+ resident_key_req,
+ extensions: AuthenticationExtensionsClientInputs {
+ cred_props: cred_props.then_some(true),
+ min_pin_length: min_pin_length.then_some(true),
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
+ };
+
+ *self.transaction.lock().unwrap() = Some(TransactionState {
+ tid,
+ browsing_context_id,
+ pending_args: Some(TransactionArgs::Register(timeout_ms as u64, info)),
+ promise: TransactionPromise::Register(promise),
+ pin_receiver: None,
+ selection_receiver: None,
+ interactive_receiver: None,
+ puat_cache: None,
+ });
+
+ if none_attestation
+ || static_prefs::pref!("security.webauth.webauthn_testing_allow_direct_attestation")
+ {
+ self.resume_make_credential(tid, none_attestation)
+ } else {
+ send_prompt(
+ BrowserPromptType::RegisterDirect,
+ tid,
+ Some(&origin),
+ Some(browsing_context_id),
+ )
+ }
+ }
+
+ xpcom_method!(resume_make_credential => ResumeMakeCredential(aTid: u64, aForceNoneAttestation: bool));
+ fn resume_make_credential(
+ &self,
+ tid: u64,
+ force_none_attestation: bool,
+ ) -> Result<(), nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ if state.tid != tid {
+ return Err(NS_ERROR_FAILURE);
+ };
+ let browsing_context_id = state.browsing_context_id;
+ let Some(TransactionArgs::Register(timeout_ms, info)) = state.pending_args.take() else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ // We have to drop the guard here, as there _may_ still be another operation
+ // ongoing and `register()` below will try to cancel it. This will call the state
+ // callback of that operation, which in turn may try to access `transaction`, deadlocking.
+ drop(guard);
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ let status_transaction = self.transaction.clone();
+ let status_origin = info.origin.clone();
+ RunnableBuilder::new("AuthrsService::MakeCredential::StatusReceiver", move || {
+ let _ = status_callback(
+ status_rx,
+ tid,
+ &status_origin,
+ browsing_context_id,
+ status_transaction,
+ );
+ })
+ .may_block(true)
+ .dispatch_background_task()?;
+
+ let callback_transaction = self.transaction.clone();
+ let callback_origin = info.origin.clone();
+ let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
+ Box::new(move |mut result| {
+ let mut guard = callback_transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return;
+ };
+ if state.tid != tid {
+ return;
+ }
+ let TransactionPromise::Register(ref promise) = state.promise else {
+ return;
+ };
+ if let Ok(inner) = result.as_mut() {
+ // Tokens always provide attestation, but the user may have asked we not
+ // include the attestation statement in the response.
+ if force_none_attestation {
+ inner.att_obj.anonymize();
+ }
+ }
+ if let Err(AuthenticatorError::CredentialExcluded) = result {
+ let _ = send_prompt(
+ BrowserPromptType::AlreadyRegistered,
+ tid,
+ Some(&callback_origin),
+ Some(browsing_context_id),
+ );
+ }
+ if should_cancel_prompts(&result) {
+ // Some errors are accompanied by prompts that should persist after the
+ // operation terminates.
+ let _ = cancel_prompts(tid);
+ }
+ let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
+ *guard = None;
+ }),
+ );
+
+ // The authenticator crate provides an `AuthenticatorService` which can dispatch a request
+ // in parallel to any number of transports. We only support the USB transport in production
+ // configurations, so we do not need the full generality of `AuthenticatorService` here.
+ // We disable the USB transport in tests that use virtual devices.
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ // TODO(Bug 1855290) Remove this presence prompt
+ send_prompt(
+ BrowserPromptType::Presence,
+ tid,
+ Some(&info.origin),
+ Some(browsing_context_id),
+ )?;
+ self.usb_token_manager.lock().unwrap().register(
+ timeout_ms,
+ info,
+ status_tx,
+ state_callback,
+ );
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ self.test_token_manager
+ .register(timeout_ms, info, status_tx, state_callback);
+ } else {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ Ok(())
+ }
+
+ xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnSignArgs, aPromise: *const nsIWebAuthnSignPromise));
+ fn get_assertion(
+ &self,
+ tid: u64,
+ browsing_context_id: u64,
+ args: &nsIWebAuthnSignArgs,
+ promise: &nsIWebAuthnSignPromise,
+ ) -> Result<(), nsresult> {
+ self.reset()?;
+
+ let promise = SignPromise(RefPtr::new(promise));
+
+ let mut origin = nsString::new();
+ unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
+
+ let mut relying_party_id = nsString::new();
+ unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
+
+ let mut client_data_hash = ThinVec::new();
+ unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
+ let mut client_data_hash_arr = [0u8; 32];
+ client_data_hash_arr.copy_from_slice(&client_data_hash);
+
+ let mut timeout_ms = 0u32;
+ unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
+
+ let mut allow_list = ThinVec::new();
+ unsafe { args.GetAllowList(&mut allow_list) }.to_result()?;
+ let allow_list = allow_list
+ .iter()
+ .map(|id| PublicKeyCredentialDescriptor {
+ id: id.to_vec(),
+ transports: vec![],
+ })
+ .collect();
+
+ let mut user_verification = nsString::new();
+ unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
+ let user_verification_req = if user_verification.eq("required") {
+ UserVerificationRequirement::Required
+ } else if user_verification.eq("discouraged") {
+ UserVerificationRequirement::Discouraged
+ } else {
+ UserVerificationRequirement::Preferred
+ };
+
+ let mut app_id = None;
+ let mut maybe_app_id = nsString::new();
+ match unsafe { args.GetAppId(&mut *maybe_app_id) }.to_result() {
+ Ok(_) => app_id = Some(maybe_app_id.to_string()),
+ _ => (),
+ }
+
+ let mut conditionally_mediated = false;
+ unsafe { args.GetConditionallyMediated(&mut conditionally_mediated) }.to_result()?;
+
+ let info = SignArgs {
+ client_data_hash: client_data_hash_arr,
+ relying_party_id: relying_party_id.to_string(),
+ origin: origin.to_string(),
+ allow_list,
+ user_verification_req,
+ user_presence_req: true,
+ extensions: AuthenticationExtensionsClientInputs {
+ app_id,
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
+ };
+
+ let mut guard = self.transaction.lock().unwrap();
+ *guard = Some(TransactionState {
+ tid,
+ browsing_context_id,
+ pending_args: Some(TransactionArgs::Sign(timeout_ms as u64, info)),
+ promise: TransactionPromise::Sign(promise),
+ pin_receiver: None,
+ selection_receiver: None,
+ interactive_receiver: None,
+ puat_cache: None,
+ });
+
+ if !conditionally_mediated {
+ // Immediately proceed to the modal UI flow.
+ self.do_get_assertion(None, guard)
+ } else {
+ // Cache the request and wait for the conditional UI to request autofill entries, etc.
+ Ok(())
+ }
+ }
+
+ fn do_get_assertion(
+ &self,
+ mut selected_credential_id: Option<Vec<u8>>,
+ mut guard: MutexGuard<Option<TransactionState>>,
+ ) -> Result<(), nsresult> {
+ let Some(state) = guard.as_mut() else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ let browsing_context_id = state.browsing_context_id;
+ let tid = state.tid;
+ let (timeout_ms, mut info) = match state.pending_args.take() {
+ Some(TransactionArgs::Sign(timeout_ms, info)) => (timeout_ms, info),
+ _ => return Err(NS_ERROR_FAILURE),
+ };
+
+ if let Some(id) = selected_credential_id.take() {
+ if info.allow_list.is_empty() {
+ info.allow_list.push(PublicKeyCredentialDescriptor {
+ id,
+ transports: vec![],
+ });
+ } else {
+ // We need to ensure that the selected credential id
+ // was in the original allow_list.
+ info.allow_list.retain(|cred| cred.id == id);
+ if info.allow_list.is_empty() {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+ }
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ let status_transaction = self.transaction.clone();
+ let status_origin = info.origin.to_string();
+ RunnableBuilder::new("AuthrsService::GetAssertion::StatusReceiver", move || {
+ let _ = status_callback(
+ status_rx,
+ tid,
+ &status_origin,
+ browsing_context_id,
+ status_transaction,
+ );
+ })
+ .may_block(true)
+ .dispatch_background_task()?;
+
+ let uniq_allowed_cred = if info.allow_list.len() == 1 {
+ info.allow_list.first().cloned()
+ } else {
+ None
+ };
+
+ let callback_transaction = self.transaction.clone();
+ let state_callback = StateCallback::<Result<SignResult, AuthenticatorError>>::new(
+ Box::new(move |mut result| {
+ let mut guard = callback_transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return;
+ };
+ if state.tid != tid {
+ return;
+ }
+ let TransactionPromise::Sign(ref promise) = state.promise else {
+ return;
+ };
+ if uniq_allowed_cred.is_some() {
+ // In CTAP 2.0, but not CTAP 2.1, the assertion object's credential field
+ // "May be omitted if the allowList has exactly one credential." If we had
+ // a unique allowed credential, then copy its descriptor to the output.
+ if let Ok(inner) = result.as_mut() {
+ inner.assertion.credentials = uniq_allowed_cred;
+ }
+ }
+ if should_cancel_prompts(&result) {
+ // Some errors are accompanied by prompts that should persist after the
+ // operation terminates.
+ let _ = cancel_prompts(tid);
+ }
+ let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
+ *guard = None;
+ }),
+ );
+
+ // TODO(Bug 1855290) Remove this presence prompt
+ send_prompt(
+ BrowserPromptType::Presence,
+ tid,
+ Some(&info.origin),
+ Some(browsing_context_id),
+ )?;
+
+ // As in `register`, we are intentionally avoiding `AuthenticatorService` here.
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ self.usb_token_manager.lock().unwrap().sign(
+ timeout_ms as u64,
+ info,
+ status_tx,
+ state_callback,
+ );
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ self.test_token_manager
+ .sign(timeout_ms as u64, info, status_tx, state_callback);
+ } else {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ Ok(())
+ }
+
+ xpcom_method!(has_pending_conditional_get => HasPendingConditionalGet(aBrowsingContextId: u64, aOrigin: *const nsAString) -> u64);
+ fn has_pending_conditional_get(
+ &self,
+ browsing_context_id: u64,
+ origin: &nsAString,
+ ) -> Result<u64, nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return Ok(0);
+ };
+ let Some(TransactionArgs::Sign(_, info)) = state.pending_args.as_ref() else {
+ return Ok(0);
+ };
+ if state.browsing_context_id != browsing_context_id {
+ return Ok(0);
+ }
+ if !info.origin.eq(&origin.to_string()) {
+ return Ok(0);
+ }
+ Ok(state.tid)
+ }
+
+ xpcom_method!(get_autofill_entries => GetAutoFillEntries(aTransactionId: u64) -> ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>);
+ fn get_autofill_entries(
+ &self,
+ tid: u64,
+ ) -> Result<ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>, nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ if state.tid != tid {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ }
+ let Some(TransactionArgs::Sign(_, info)) = state.pending_args.as_ref() else {
+ return Err(NS_ERROR_NOT_AVAILABLE);
+ };
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ // We don't currently support silent discovery for credentials on USB tokens.
+ return Ok(thin_vec![]);
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ return self
+ .test_token_manager
+ .get_autofill_entries(&info.relying_party_id, &info.allow_list);
+ } else {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ xpcom_method!(select_autofill_entry => SelectAutoFillEntry(aTid: u64, aCredentialId: *const ThinVec<u8>));
+ fn select_autofill_entry(&self, tid: u64, credential_id: &ThinVec<u8>) -> Result<(), nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ if tid != state.tid {
+ return Err(NS_ERROR_FAILURE);
+ }
+ self.do_get_assertion(Some(credential_id.to_vec()), guard)
+ }
+
+ xpcom_method!(resume_conditional_get => ResumeConditionalGet(aTid: u64));
+ fn resume_conditional_get(&self, tid: u64) -> Result<(), nsresult> {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_mut() else {
+ return Err(NS_ERROR_FAILURE);
+ };
+ if tid != state.tid {
+ return Err(NS_ERROR_FAILURE);
+ }
+ self.do_get_assertion(None, guard)
+ }
+
+ // Clears the transaction state if tid matches the ongoing transaction ID.
+ // Returns whether the tid was a match.
+ fn clear_transaction(&self, tid: u64) -> bool {
+ let mut guard = self.transaction.lock().unwrap();
+ let Some(state) = guard.as_ref() else {
+ return true; // workaround for Bug 1864526.
+ };
+ if state.tid != tid {
+ // Ignore the cancellation request if the transaction
+ // ID does not match.
+ return false;
+ }
+ // It's possible that we haven't dispatched the request to the usb_token_manager yet,
+ // e.g. if we're waiting for resume_make_credential. So reject the promise and drop the
+ // state here rather than from the StateCallback
+ let _ = state.promise.reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ *guard = None;
+ true
+ }
+
+ xpcom_method!(cancel => Cancel(aTransactionId: u64));
+ fn cancel(&self, tid: u64) -> Result<(), nsresult> {
+ if self.clear_transaction(tid) {
+ self.usb_token_manager.lock().unwrap().cancel();
+ }
+ Ok(())
+ }
+
+ xpcom_method!(reset => Reset());
+ fn reset(&self) -> Result<(), nsresult> {
+ {
+ if let Some(state) = self.transaction.lock().unwrap().take() {
+ cancel_prompts(state.tid)?;
+ state.promise.reject(NS_ERROR_DOM_ABORT_ERR)?;
+ }
+ } // release the transaction lock so a StateCallback can take it
+ self.usb_token_manager.lock().unwrap().cancel();
+ Ok(())
+ }
+
+ xpcom_method!(
+ add_virtual_authenticator => AddVirtualAuthenticator(
+ protocol: *const nsACString,
+ transport: *const nsACString,
+ hasResidentKey: bool,
+ hasUserVerification: bool,
+ isUserConsenting: bool,
+ isUserVerified: bool) -> u64
+ );
+ fn add_virtual_authenticator(
+ &self,
+ protocol: &nsACString,
+ transport: &nsACString,
+ has_resident_key: bool,
+ has_user_verification: bool,
+ is_user_consenting: bool,
+ is_user_verified: bool,
+ ) -> Result<u64, nsresult> {
+ let protocol = match protocol.to_string().as_str() {
+ "ctap1/u2f" => AuthenticatorVersion::U2F_V2,
+ "ctap2" => AuthenticatorVersion::FIDO_2_0,
+ "ctap2_1" => AuthenticatorVersion::FIDO_2_1,
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+ let transport = transport.to_string();
+ match transport.as_str() {
+ "usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (),
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+ self.test_token_manager.add_virtual_authenticator(
+ protocol,
+ transport,
+ has_resident_key,
+ has_user_verification,
+ is_user_consenting,
+ is_user_verified,
+ )
+ }
+
+ xpcom_method!(remove_virtual_authenticator => RemoveVirtualAuthenticator(authenticatorId: u64));
+ fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> {
+ self.test_token_manager
+ .remove_virtual_authenticator(authenticator_id)
+ }
+
+ xpcom_method!(
+ add_credential => AddCredential(
+ authenticatorId: u64,
+ credentialId: *const nsACString,
+ isResidentCredential: bool,
+ rpId: *const nsACString,
+ privateKey: *const nsACString,
+ userHandle: *const nsACString,
+ signCount: u32)
+ );
+ fn add_credential(
+ &self,
+ authenticator_id: u64,
+ credential_id: &nsACString,
+ is_resident_credential: bool,
+ rp_id: &nsACString,
+ private_key: &nsACString,
+ user_handle: &nsACString,
+ sign_count: u32,
+ ) -> Result<(), nsresult> {
+ let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(credential_id)
+ .or(Err(NS_ERROR_INVALID_ARG))?;
+ let private_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(private_key)
+ .or(Err(NS_ERROR_INVALID_ARG))?;
+ let user_handle = base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(user_handle)
+ .or(Err(NS_ERROR_INVALID_ARG))?;
+ self.test_token_manager.add_credential(
+ authenticator_id,
+ &credential_id,
+ &private_key,
+ &user_handle,
+ sign_count,
+ rp_id.to_string(),
+ is_resident_credential,
+ )
+ }
+
+ xpcom_method!(get_credentials => GetCredentials(authenticatorId: u64) -> ThinVec<Option<RefPtr<nsICredentialParameters>>>);
+ fn get_credentials(
+ &self,
+ authenticator_id: u64,
+ ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
+ self.test_token_manager.get_credentials(authenticator_id)
+ }
+
+ xpcom_method!(remove_credential => RemoveCredential(authenticatorId: u64, credentialId: *const nsACString));
+ fn remove_credential(
+ &self,
+ authenticator_id: u64,
+ credential_id: &nsACString,
+ ) -> Result<(), nsresult> {
+ let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(credential_id)
+ .or(Err(NS_ERROR_INVALID_ARG))?;
+ self.test_token_manager
+ .remove_credential(authenticator_id, credential_id.as_ref())
+ }
+
+ xpcom_method!(remove_all_credentials => RemoveAllCredentials(authenticatorId: u64));
+ fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> {
+ self.test_token_manager
+ .remove_all_credentials(authenticator_id)
+ }
+
+ xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: u64, isUserVerified: bool));
+ fn set_user_verified(
+ &self,
+ authenticator_id: u64,
+ is_user_verified: bool,
+ ) -> Result<(), nsresult> {
+ self.test_token_manager
+ .set_user_verified(authenticator_id, is_user_verified)
+ }
+
+ xpcom_method!(listen => Listen());
+ pub(crate) fn listen(&self) -> Result<(), nsresult> {
+ // For now, we don't support softtokens
+ if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ return Ok(());
+ }
+
+ {
+ let mut guard = self.transaction.lock().unwrap();
+ if guard.as_ref().is_some() {
+ // ignore listen() and continue with ongoing transaction
+ return Ok(());
+ }
+ *guard = Some(TransactionState {
+ tid: 0,
+ browsing_context_id: 0,
+ pending_args: None,
+ promise: TransactionPromise::Listen,
+ pin_receiver: None,
+ selection_receiver: None,
+ interactive_receiver: None,
+ puat_cache: None,
+ });
+ }
+
+ // We may get from status_updates info about certain errors (e.g. PinErrors)
+ // which we want to present to the user. We will ignore the following error
+ // which is caused by us "hanging up" on the StatusUpdate-channel and return
+ // the PinError instead, via `upcoming_error`.
+ let upcoming_error = Arc::new(Mutex::new(None));
+ let upcoming_error_c = upcoming_error.clone();
+ let callback_transaction = self.transaction.clone();
+ let state_callback = StateCallback::<Result<ManageResult, AuthenticatorError>>::new(
+ Box::new(move |result| {
+ let mut guard = callback_transaction.lock().unwrap();
+ match guard.as_mut() {
+ Some(state) => {
+ match state.promise {
+ TransactionPromise::Listen => (),
+ _ => return,
+ }
+ *guard = None;
+ }
+ // We have no transaction anymore, this means cancel() was called
+ None => (),
+ }
+ let msg = match result {
+ Ok(_) => BrowserPromptType::ListenSuccess,
+ Err(e) => {
+ // See if we have a cached error that should replace this error
+ let replacement = if let Ok(mut x) = upcoming_error_c.lock() {
+ x.take()
+ } else {
+ None
+ };
+ let replaced_err = replacement.unwrap_or(e);
+ let err = authrs_to_prompt(replaced_err);
+ BrowserPromptType::ListenError {
+ error: Box::new(err),
+ }
+ }
+ };
+ let _ = send_about_prompt(&msg);
+ }),
+ );
+
+ // Calling `manage()` within the lock, to avoid race conditions
+ // where we might check listen_blocked, see that it's false,
+ // continue along, but in parallel `make_credential()` aborts the
+ // interactive process shortly after, setting listen_blocked to true,
+ // then accessing usb_token_manager afterwards and at the same time
+ // we do it here, causing a runtime crash for trying to mut-borrow it twice.
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ let status_transaction = self.transaction.clone();
+ RunnableBuilder::new(
+ "AuthrsTransport::AboutWebauthn::StatusReceiver",
+ move || {
+ let _ = interactive_status_callback(status_rx, status_transaction, upcoming_error);
+ },
+ )
+ .may_block(true)
+ .dispatch_background_task()?;
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ self.usb_token_manager.lock().unwrap().manage(
+ 60 * 1000 * 1000,
+ status_tx,
+ state_callback,
+ );
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ // We don't yet support softtoken
+ } else {
+ // Silently accept request, if all webauthn-options are disabled.
+ // Used for testing.
+ }
+ Ok(())
+ }
+
+ xpcom_method!(run_command => RunCommand(c_cmd: *const nsACString));
+ pub fn run_command(&self, c_cmd: &nsACString) -> Result<(), nsresult> {
+ // Always test if it can be parsed from incoming JSON (even for tests)
+ let incoming: RequestWrapper =
+ serde_json::from_str(&c_cmd.to_utf8()).or(Err(NS_ERROR_DOM_OPERATION_ERR))?;
+ if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
+ let guard = self.transaction.lock().unwrap();
+ let puat = guard.as_ref().and_then(|g| g.puat_cache.clone());
+ let command = match incoming {
+ RequestWrapper::Quit => InteractiveRequest::Quit,
+ RequestWrapper::ChangePIN(a, b) => InteractiveRequest::ChangePIN(a, b),
+ RequestWrapper::SetPIN(a) => InteractiveRequest::SetPIN(a),
+ RequestWrapper::CredentialManagement(c) => {
+ InteractiveRequest::CredentialManagement(c, puat)
+ }
+ RequestWrapper::BioEnrollment(c) => InteractiveRequest::BioEnrollment(c, puat),
+ };
+ match &guard.as_ref().unwrap().interactive_receiver {
+ Some(channel) => channel.send(command).or(Err(NS_ERROR_FAILURE)),
+ // Either we weren't expecting a pin, or the controller is confused
+ // about which transaction is active. Neither is recoverable, so it's
+ // OK to drop the PinReceiver here.
+ _ => Err(NS_ERROR_FAILURE),
+ }
+ } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ // We don't yet support softtoken
+ Ok(())
+ } else {
+ // Silently accept request, if all webauthn-options are disabled.
+ // Used for testing.
+ Ok(())
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn authrs_service_constructor(result: *mut *const nsIWebAuthnService) -> nsresult {
+ let wrapper = AuthrsService::allocate(InitAuthrsService {
+ usb_token_manager: Mutex::new(StateMachine::new()),
+ test_token_manager: TestTokenManager::new(),
+ transaction: Arc::new(Mutex::new(None)),
+ });
+
+ #[cfg(feature = "fuzzing")]
+ {
+ let fuzzing_config = static_prefs::pref!("fuzzing.webauthn.authenticator_config");
+ if fuzzing_config != 0 {
+ let is_user_verified = (fuzzing_config & 0x01) != 0;
+ let is_user_consenting = (fuzzing_config & 0x02) != 0;
+ let has_user_verification = (fuzzing_config & 0x04) != 0;
+ let has_resident_key = (fuzzing_config & 0x08) != 0;
+ let transport = nsCString::from(match (fuzzing_config & 0x10) >> 4 {
+ 0 => "usb",
+ 1 => "internal",
+ _ => unreachable!(),
+ });
+ let protocol = nsCString::from(match (fuzzing_config & 0x60) >> 5 {
+ 0 => "", // reserved
+ 1 => "ctap1/u2f",
+ 2 => "ctap2",
+ 3 => "ctap2_1",
+ _ => unreachable!(),
+ });
+ // If this fails it's probably because the protocol bits were zero,
+ // we'll just ignore it.
+ let _ = wrapper.add_virtual_authenticator(
+ &protocol,
+ &transport,
+ has_resident_key,
+ has_user_verification,
+ is_user_consenting,
+ is_user_verified,
+ );
+ }
+ }
+
+ unsafe {
+ RefPtr::new(wrapper.coerce::<nsIWebAuthnService>()).forget(&mut *result);
+ }
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn authrs_webauthn_att_obj_constructor(
+ att_obj_bytes: &ThinVec<u8>,
+ anonymize: bool,
+ result: *mut *const nsIWebAuthnAttObj,
+) -> nsresult {
+ if result.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ let mut att_obj: AttestationObject = match serde_cbor::from_slice(att_obj_bytes) {
+ Ok(att_obj) => att_obj,
+ Err(_) => return NS_ERROR_INVALID_ARG,
+ };
+
+ if anonymize {
+ att_obj.anonymize();
+ }
+
+ let wrapper = WebAuthnAttObj::allocate(InitWebAuthnAttObj { att_obj });
+
+ unsafe {
+ RefPtr::new(wrapper.coerce::<nsIWebAuthnAttObj>()).forget(&mut *result);
+ }
+
+ NS_OK
+}
diff --git a/dom/webauthn/authrs_bridge/src/test_token.rs b/dom/webauthn/authrs_bridge/src/test_token.rs
new file mode 100644
index 0000000000..afc2ddbc75
--- /dev/null
+++ b/dom/webauthn/authrs_bridge/src/test_token.rs
@@ -0,0 +1,974 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use authenticator::authenticatorservice::{RegisterArgs, SignArgs};
+use authenticator::crypto::{ecdsa_p256_sha256_sign_raw, COSEAlgorithm, COSEKey, SharedSecret};
+use authenticator::ctap2::{
+ attestation::{
+ AAGuid, AttestationObject, AttestationStatement, AttestationStatementPacked,
+ AttestedCredentialData, AuthenticatorData, AuthenticatorDataFlags, Extension,
+ },
+ client_data::ClientDataHash,
+ commands::{
+ client_pin::{ClientPIN, ClientPinResponse, PINSubcommand},
+ get_assertion::{GetAssertion, GetAssertionResponse, GetAssertionResult},
+ get_info::{AuthenticatorInfo, AuthenticatorOptions, AuthenticatorVersion},
+ get_version::{GetVersion, U2FInfo},
+ make_credentials::{MakeCredentials, MakeCredentialsResult},
+ reset::Reset,
+ selection::Selection,
+ RequestCtap1, RequestCtap2, StatusCode,
+ },
+ preflight::CheckKeyHandle,
+ server::{
+ AuthenticatorAttachment, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity,
+ RelyingParty,
+ },
+};
+use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError};
+use authenticator::{ctap2, statecallback::StateCallback};
+use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice};
+use authenticator::{RegisterResult, SignResult, StatusUpdate};
+use base64::Engine;
+use moz_task::RunnableBuilder;
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_OK};
+use nsstring::{nsACString, nsAString, nsCString, nsString};
+use rand::{thread_rng, RngCore};
+use std::cell::{Ref, RefCell};
+use std::collections::{hash_map::Entry, HashMap};
+use std::ops::{Deref, DerefMut};
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::mpsc::Sender;
+use std::sync::{Arc, Mutex};
+use thin_vec::ThinVec;
+use xpcom::interfaces::{nsICredentialParameters, nsIWebAuthnAutoFillEntry};
+use xpcom::{xpcom_method, RefPtr};
+
+// All TestTokens use this fixed, randomly generated, AAGUID
+const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([
+ 0x68, 0xe1, 0x00, 0xa5, 0x0b, 0x47, 0x91, 0x04, 0xb8, 0x54, 0x97, 0xa9, 0xba, 0x51, 0x06, 0x38,
+]);
+
+#[derive(Debug)]
+struct TestTokenCredential {
+ id: Vec<u8>,
+ privkey: Vec<u8>,
+ user_handle: Vec<u8>,
+ sign_count: AtomicU32,
+ is_discoverable_credential: bool,
+ rp: RelyingParty,
+}
+
+impl TestTokenCredential {
+ fn assert(
+ &self,
+ client_data_hash: &ClientDataHash,
+ flags: AuthenticatorDataFlags,
+ ) -> Result<GetAssertionResponse, HIDError> {
+ let credentials = Some(PublicKeyCredentialDescriptor {
+ id: self.id.clone(),
+ transports: vec![],
+ });
+
+ let auth_data = AuthenticatorData {
+ rp_id_hash: self.rp.hash(),
+ flags,
+ counter: self.sign_count.fetch_add(1, Ordering::Relaxed),
+ credential_data: None,
+ extensions: Extension::default(),
+ };
+
+ let user = Some(PublicKeyCredentialUserEntity {
+ id: self.user_handle.clone(),
+ ..Default::default()
+ });
+
+ let mut data = auth_data.to_vec();
+ data.extend_from_slice(client_data_hash.as_ref());
+ let signature =
+ ecdsa_p256_sha256_sign_raw(&self.privkey, &data).or(Err(HIDError::DeviceError))?;
+
+ Ok(GetAssertionResponse {
+ credentials,
+ auth_data,
+ signature,
+ user,
+ number_of_credentials: Some(1),
+ })
+ }
+}
+
+#[derive(Debug)]
+struct TestToken {
+ protocol: FidoProtocol,
+ transport: String,
+ versions: Vec<AuthenticatorVersion>,
+ has_resident_key: bool,
+ has_user_verification: bool,
+ is_user_consenting: bool,
+ is_user_verified: bool,
+ // This is modified in `make_credentials` which takes a &TestToken, but we only allow one transaction at a time.
+ credentials: RefCell<Vec<TestTokenCredential>>,
+ pin_token: [u8; 32],
+ shared_secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+}
+
+impl TestToken {
+ fn new(
+ versions: Vec<AuthenticatorVersion>,
+ transport: String,
+ has_resident_key: bool,
+ has_user_verification: bool,
+ is_user_consenting: bool,
+ is_user_verified: bool,
+ ) -> TestToken {
+ let mut pin_token = [0u8; 32];
+ thread_rng().fill_bytes(&mut pin_token);
+ Self {
+ protocol: FidoProtocol::CTAP2,
+ transport,
+ versions,
+ has_resident_key,
+ has_user_verification,
+ is_user_consenting,
+ is_user_verified,
+ credentials: RefCell::new(vec![]),
+ pin_token,
+ shared_secret: None,
+ authenticator_info: None,
+ }
+ }
+
+ fn insert_credential(
+ &self,
+ id: &[u8],
+ privkey: &[u8],
+ rp: &RelyingParty,
+ is_discoverable_credential: bool,
+ user_handle: &[u8],
+ sign_count: u32,
+ ) {
+ let c = TestTokenCredential {
+ id: id.to_vec(),
+ privkey: privkey.to_vec(),
+ rp: rp.clone(),
+ is_discoverable_credential,
+ user_handle: user_handle.to_vec(),
+ sign_count: AtomicU32::new(sign_count),
+ };
+
+ let mut credlist = self.credentials.borrow_mut();
+
+ match credlist.binary_search_by_key(&id, |probe| &probe.id) {
+ Ok(_) => {}
+ Err(idx) => credlist.insert(idx, c),
+ }
+ }
+
+ fn get_credentials(&self) -> Ref<Vec<TestTokenCredential>> {
+ self.credentials.borrow()
+ }
+
+ fn delete_credential(&mut self, id: &[u8]) -> bool {
+ let mut credlist = self.credentials.borrow_mut();
+ if let Ok(idx) = credlist.binary_search_by_key(&id, |probe| &probe.id) {
+ credlist.remove(idx);
+ return true;
+ }
+
+ false
+ }
+
+ fn delete_all_credentials(&mut self) {
+ self.credentials.borrow_mut().clear();
+ }
+
+ fn has_credential(&self, id: &[u8]) -> bool {
+ self.credentials
+ .borrow()
+ .binary_search_by_key(&id, |probe| &probe.id)
+ .is_ok()
+ }
+
+ fn max_supported_version(&self) -> AuthenticatorVersion {
+ self.authenticator_info
+ .as_ref()
+ .map_or(AuthenticatorVersion::U2F_V2, |info| {
+ info.max_supported_version()
+ })
+ }
+}
+
+impl FidoDevice for TestToken {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ Ok(())
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ true
+ }
+
+ fn initialized(&self) -> bool {
+ true
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ true
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.shared_secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, shared_secret: SharedSecret) {
+ self.shared_secret = Some(shared_secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1
+ }
+}
+
+impl FidoDeviceIO for TestToken {
+ fn send_msg_cancellable<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Out, HIDError> {
+ if !self.initialized() {
+ return Err(HIDError::DeviceNotInitialized);
+ }
+
+ match self.get_protocol() {
+ FidoProtocol::CTAP1 => self.send_ctap1_cancellable(msg, keep_alive),
+ FidoProtocol::CTAP2 => self.send_cbor_cancellable(msg, keep_alive),
+ }
+ }
+
+ fn send_cbor_cancellable<Req: RequestCtap2>(
+ &mut self,
+ msg: &Req,
+ _keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError> {
+ msg.send_to_virtual_device(self)
+ }
+
+ fn send_ctap1_cancellable<Req: RequestCtap1>(
+ &mut self,
+ msg: &Req,
+ _keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError> {
+ msg.send_to_virtual_device(self)
+ }
+}
+
+impl VirtualFidoDevice for TestToken {
+ fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError> {
+ let credlist = self.credentials.borrow();
+ let req_rp_hash = req.rp.hash();
+ let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash);
+ for credential in eligible_cred_iter {
+ if req.key_handle == credential.id {
+ return Ok(());
+ }
+ }
+ Err(HIDError::DeviceError)
+ }
+
+ fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError> {
+ match req.subcommand {
+ PINSubcommand::GetKeyAgreement => {
+ // We don't need to save, or even know, the private key for the public key returned
+ // here because we have access to the shared secret derived on the client side.
+ let (_private, public) = COSEKey::generate(COSEAlgorithm::ECDH_ES_HKDF256)
+ .map_err(|_| HIDError::DeviceError)?;
+ Ok(ClientPinResponse {
+ key_agreement: Some(public),
+ ..Default::default()
+ })
+ }
+ PINSubcommand::GetPinUvAuthTokenUsingUvWithPermissions => {
+ // TODO: permissions
+ if !self.is_user_consenting || !self.is_user_verified {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::OperationDenied,
+ None,
+ )));
+ }
+ let secret = match self.shared_secret.as_ref() {
+ Some(secret) => secret,
+ _ => return Err(HIDError::DeviceError),
+ };
+ let encrypted_pin_token = match secret.encrypt(&self.pin_token) {
+ Ok(token) => token,
+ _ => return Err(HIDError::DeviceError),
+ };
+ Ok(ClientPinResponse {
+ pin_token: Some(encrypted_pin_token),
+ ..Default::default()
+ })
+ }
+ _ => Err(HIDError::UnsupportedCommand),
+ }
+ }
+
+ fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError> {
+ // Algorithm 6.2.2 from CTAP 2.1
+ // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg
+
+ // 1. zero length pinUvAuthParam
+ // (not implemented)
+
+ // 2. Validate pinUvAuthParam
+ // Handled by caller
+
+ // 3. Initialize "uv" and "up" bits to false
+ let mut flags = AuthenticatorDataFlags::empty();
+
+ // 4. Handle all options
+ // 4.1 and 4.2
+ let effective_uv_opt =
+ req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none();
+
+ // 4.3
+ if effective_uv_opt && !self.has_user_verification {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::InvalidOption,
+ None,
+ )));
+ }
+
+ // 4.4 rk
+ // (not implemented, we don't encode it)
+
+ // 4.5
+ let effective_up_opt = req.options.user_presence.unwrap_or(true);
+
+ // 5. alwaysUv
+ // (not implemented)
+
+ // 6. User verification
+ // TODO: Permissions, (maybe) validate pinUvAuthParam
+ if self.is_user_verified && (effective_uv_opt || req.pin_uv_auth_param.is_some()) {
+ flags |= AuthenticatorDataFlags::USER_VERIFIED;
+ }
+
+ // 7. Locate credentials
+ let credlist = self.credentials.borrow();
+ let req_rp_hash = req.rp.hash();
+ let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash);
+
+ // 8. Set up=true if evidence of user interaction was provided in step 6.
+ // (not applicable, we use pinUvAuthParam)
+
+ // 9. User presence test
+ if effective_up_opt {
+ if self.is_user_consenting {
+ flags |= AuthenticatorDataFlags::USER_PRESENT;
+ } else {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::UpRequired,
+ None,
+ )));
+ }
+ }
+
+ // 10. Extensions
+ // (not implemented)
+
+ let mut assertions: Vec<GetAssertionResult> = vec![];
+ if !req.allow_list.is_empty() {
+ // 11. Non-discoverable credential case
+ // return at most one assertion matching an allowed credential ID
+ for credential in eligible_cred_iter {
+ if req.allow_list.iter().any(|x| x.id == credential.id) {
+ let mut assertion: GetAssertionResponse =
+ credential.assert(&req.client_data_hash, flags)?;
+ if req.allow_list.len() == 1
+ && self.max_supported_version() == AuthenticatorVersion::FIDO_2_0
+ {
+ // CTAP 2.0 authenticators are allowed to omit the credential ID in the
+ // response if the allow list contains exactly one entry. This behavior is
+ // a common source of bugs, e.g. Bug 1864504, so we'll exercise it here.
+ assertion.credentials = None;
+ }
+ assertions.push(GetAssertionResult {
+ assertion: assertion.into(),
+ attachment: AuthenticatorAttachment::Unknown,
+ extensions: Default::default(),
+ });
+ break;
+ }
+ }
+ } else {
+ // 12. Discoverable credential case
+ // return any number of assertions from credentials bound to this RP ID
+ for credential in eligible_cred_iter.filter(|x| x.is_discoverable_credential) {
+ let assertion = credential.assert(&req.client_data_hash, flags)?.into();
+ assertions.push(GetAssertionResult {
+ assertion,
+ attachment: AuthenticatorAttachment::Unknown,
+ extensions: Default::default(),
+ });
+ }
+ }
+
+ if assertions.is_empty() {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::NoCredentials,
+ None,
+ )));
+ }
+
+ Ok(assertions)
+ }
+
+ fn get_info(&self) -> Result<AuthenticatorInfo, HIDError> {
+ // This is a CTAP2.1 device with internal user verification support
+ Ok(AuthenticatorInfo {
+ versions: self.versions.clone(),
+ options: AuthenticatorOptions {
+ platform_device: self.transport == "internal",
+ resident_key: self.has_resident_key,
+ pin_uv_auth_token: Some(self.has_user_verification),
+ user_verification: Some(self.has_user_verification),
+ ..Default::default()
+ },
+ ..Default::default()
+ })
+ }
+
+ fn get_version(&self, _req: &GetVersion) -> Result<U2FInfo, HIDError> {
+ Err(HIDError::UnsupportedCommand)
+ }
+
+ fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError> {
+ // Algorithm 6.1.2 from CTAP 2.1
+ // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg
+
+ // 1. zero length pinUvAuthParam
+ // (not implemented)
+
+ // 2. Validate pinUvAuthParam
+ // Handled by caller
+
+ // 3. Validate pubKeyCredParams
+ if !req
+ .pub_cred_params
+ .iter()
+ .any(|x| x.alg == COSEAlgorithm::ES256)
+ {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::UnsupportedAlgorithm,
+ None,
+ )));
+ }
+
+ // 4. initialize "uv" and "up" bits to false
+ let mut flags = AuthenticatorDataFlags::empty();
+
+ // 5. process all options
+
+ // 5.1 and 5.2
+ let effective_uv_opt =
+ req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none();
+
+ // 5.3
+ if effective_uv_opt && !self.has_user_verification {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::InvalidOption,
+ None,
+ )));
+ }
+
+ // 5.4
+ if req.options.resident_key.unwrap_or(false) && !self.has_resident_key {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::UnsupportedOption,
+ None,
+ )));
+ }
+
+ // 5.6 and 5.7
+ // Nothing to do. We don't provide a way to set up=false.
+
+ // 6. alwaysUv option ID
+ // (not implemented)
+
+ // 7. and 8. makeCredUvNotRqd option ID
+ // (not implemented)
+
+ // 9. enterprise attestation
+ // (not implemented)
+
+ // 11. User verification
+ // TODO: Permissions, (maybe) validate pinUvAuthParam
+ if self.is_user_verified {
+ flags |= AuthenticatorDataFlags::USER_VERIFIED;
+ }
+
+ // 12. exclude list
+ // TODO: credProtect
+ if req.exclude_list.iter().any(|x| self.has_credential(&x.id)) {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::CredentialExcluded,
+ None,
+ )));
+ }
+
+ // 13. Set up=true if evidence of user interaction was provided in step 11.
+ // (not applicable, we use pinUvAuthParam)
+
+ // 14. User presence test
+ if self.is_user_consenting {
+ flags |= AuthenticatorDataFlags::USER_PRESENT;
+ } else {
+ return Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::UpRequired,
+ None,
+ )));
+ }
+
+ // 15. process extensions
+ let mut extensions = Extension::default();
+ if req.extensions.min_pin_length == Some(true) {
+ // a real authenticator would
+ // 1) return an actual minimum pin length, and
+ // 2) check the RP ID against an allowlist before providing any data
+ extensions.min_pin_length = Some(4);
+ }
+
+ if extensions.has_some() {
+ flags |= AuthenticatorDataFlags::EXTENSION_DATA;
+ }
+
+ // 16. Generate a new credential.
+ let (private, public) =
+ COSEKey::generate(COSEAlgorithm::ES256).map_err(|_| HIDError::DeviceError)?;
+ let counter = 0;
+
+ // 17. and 18. Store credential
+ //
+ // All of the credentials that we create are "resident"---we store the private key locally,
+ // and use a random value for the credential ID. The `req.options.resident_key` field
+ // determines whether we make the credential "discoverable".
+ let mut id = [0u8; 32];
+ thread_rng().fill_bytes(&mut id);
+ self.insert_credential(
+ &id,
+ &private,
+ &req.rp,
+ req.options.resident_key.unwrap_or(false),
+ &req.user.clone().unwrap_or_default().id,
+ counter,
+ );
+
+ // 19. Generate attestation statement
+ flags |= AuthenticatorDataFlags::ATTESTED;
+
+ let auth_data = AuthenticatorData {
+ rp_id_hash: req.rp.hash(),
+ flags,
+ counter,
+ credential_data: Some(AttestedCredentialData {
+ aaguid: VIRTUAL_TOKEN_AAGUID,
+ credential_id: id.to_vec(),
+ credential_public_key: public,
+ }),
+ extensions,
+ };
+
+ let mut data = auth_data.to_vec();
+ data.extend_from_slice(req.client_data_hash.as_ref());
+
+ let sig = ecdsa_p256_sha256_sign_raw(&private, &data).or(Err(HIDError::DeviceError))?;
+
+ let att_stmt = AttestationStatement::Packed(AttestationStatementPacked {
+ alg: COSEAlgorithm::ES256,
+ sig: sig.as_slice().into(),
+ attestation_cert: vec![],
+ });
+
+ let result = MakeCredentialsResult {
+ attachment: AuthenticatorAttachment::Unknown,
+ att_obj: AttestationObject {
+ att_stmt,
+ auth_data,
+ },
+ extensions: Default::default(),
+ };
+ Ok(result)
+ }
+
+ fn reset(&self, _req: &Reset) -> Result<(), HIDError> {
+ Err(HIDError::UnsupportedCommand)
+ }
+
+ fn selection(&self, _req: &Selection) -> Result<(), HIDError> {
+ Err(HIDError::UnsupportedCommand)
+ }
+}
+
+#[xpcom(implement(nsICredentialParameters), atomic)]
+struct CredentialParameters {
+ credential_id: Vec<u8>,
+ is_resident_credential: bool,
+ rp_id: String,
+ private_key: Vec<u8>,
+ user_handle: Vec<u8>,
+ sign_count: u32,
+}
+
+impl CredentialParameters {
+ xpcom_method!(get_credential_id => GetCredentialId() -> nsACString);
+ fn get_credential_id(&self) -> Result<nsCString, nsresult> {
+ Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .encode(&self.credential_id)
+ .into())
+ }
+
+ xpcom_method!(get_is_resident_credential => GetIsResidentCredential() -> bool);
+ fn get_is_resident_credential(&self) -> Result<bool, nsresult> {
+ Ok(self.is_resident_credential)
+ }
+
+ xpcom_method!(get_rp_id => GetRpId() -> nsACString);
+ fn get_rp_id(&self) -> Result<nsCString, nsresult> {
+ Ok(nsCString::from(&self.rp_id))
+ }
+
+ xpcom_method!(get_private_key => GetPrivateKey() -> nsACString);
+ fn get_private_key(&self) -> Result<nsCString, nsresult> {
+ Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .encode(&self.private_key)
+ .into())
+ }
+
+ xpcom_method!(get_user_handle => GetUserHandle() -> nsACString);
+ fn get_user_handle(&self) -> Result<nsCString, nsresult> {
+ Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .encode(&self.user_handle)
+ .into())
+ }
+
+ xpcom_method!(get_sign_count => GetSignCount() -> u32);
+ fn get_sign_count(&self) -> Result<u32, nsresult> {
+ Ok(self.sign_count)
+ }
+}
+
+#[xpcom(implement(nsIWebAuthnAutoFillEntry), atomic)]
+struct WebAuthnAutoFillEntry {
+ rp: String,
+ credential_id: Vec<u8>,
+}
+
+impl WebAuthnAutoFillEntry {
+ xpcom_method!(get_provider => GetProvider() -> u8);
+ fn get_provider(&self) -> Result<u8, nsresult> {
+ Ok(nsIWebAuthnAutoFillEntry::PROVIDER_TEST_TOKEN)
+ }
+
+ xpcom_method!(get_user_name => GetUserName() -> nsAString);
+ fn get_user_name(&self) -> Result<nsString, nsresult> {
+ Ok(nsString::from("Test User"))
+ }
+
+ xpcom_method!(get_rp_id => GetRpId() -> nsAString);
+ fn get_rp_id(&self) -> Result<nsString, nsresult> {
+ Ok(nsString::from(&self.rp))
+ }
+
+ xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
+ fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
+ Ok(self.credential_id.as_slice().into())
+ }
+}
+
+#[derive(Default)]
+pub(crate) struct TestTokenManager {
+ state: Arc<Mutex<HashMap<u64, TestToken>>>,
+}
+
+impl TestTokenManager {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn add_virtual_authenticator(
+ &self,
+ protocol: AuthenticatorVersion,
+ transport: String,
+ has_resident_key: bool,
+ has_user_verification: bool,
+ is_user_consenting: bool,
+ is_user_verified: bool,
+ ) -> Result<u64, nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = TestToken::new(
+ vec![protocol],
+ transport,
+ has_resident_key,
+ has_user_verification,
+ is_user_consenting,
+ is_user_verified,
+ );
+ loop {
+ let id = rand::random::<u64>() & 0x1f_ffff_ffff_ffffu64; // Make the id safe for JS (53 bits)
+ match guard.deref_mut().entry(id) {
+ Entry::Occupied(_) => continue,
+ Entry::Vacant(v) => {
+ v.insert(token);
+ return Ok(id);
+ }
+ };
+ }
+ }
+
+ pub fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ guard
+ .deref_mut()
+ .remove(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ Ok(())
+ }
+
+ pub fn add_credential(
+ &self,
+ authenticator_id: u64,
+ id: &[u8],
+ privkey: &[u8],
+ user_handle: &[u8],
+ sign_count: u32,
+ rp_id: String,
+ is_resident_credential: bool,
+ ) -> Result<(), nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = guard
+ .deref_mut()
+ .get_mut(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ let rp = RelyingParty::from(rp_id);
+ token.insert_credential(
+ id,
+ privkey,
+ &rp,
+ is_resident_credential,
+ user_handle,
+ sign_count,
+ );
+ Ok(())
+ }
+
+ pub fn get_credentials(
+ &self,
+ authenticator_id: u64,
+ ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = guard
+ .get_mut(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ let credentials = token.get_credentials();
+ let mut credentials_parameters = ThinVec::with_capacity(credentials.len());
+ for credential in credentials.deref() {
+ // CTAP1 credentials are not currently supported here.
+ let credential_parameters = CredentialParameters::allocate(InitCredentialParameters {
+ credential_id: credential.id.clone(),
+ is_resident_credential: credential.is_discoverable_credential,
+ rp_id: credential.rp.id.clone(),
+ private_key: credential.privkey.clone(),
+ user_handle: credential.user_handle.clone(),
+ sign_count: credential.sign_count.load(Ordering::Relaxed),
+ })
+ .query_interface::<nsICredentialParameters>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ credentials_parameters.push(Some(credential_parameters));
+ }
+ Ok(credentials_parameters)
+ }
+
+ pub fn remove_credential(&self, authenticator_id: u64, id: &[u8]) -> Result<(), nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = guard
+ .deref_mut()
+ .get_mut(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ if token.delete_credential(id) {
+ Ok(())
+ } else {
+ Err(NS_ERROR_INVALID_ARG)
+ }
+ }
+
+ pub fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = guard
+ .deref_mut()
+ .get_mut(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ token.delete_all_credentials();
+ Ok(())
+ }
+
+ pub fn set_user_verified(
+ &self,
+ authenticator_id: u64,
+ is_user_verified: bool,
+ ) -> Result<(), nsresult> {
+ let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let token = guard
+ .deref_mut()
+ .get_mut(&authenticator_id)
+ .ok_or(NS_ERROR_INVALID_ARG)?;
+ token.is_user_verified = is_user_verified;
+ Ok(())
+ }
+
+ pub fn register(
+ &self,
+ _timeout_ms: u64,
+ ctap_args: RegisterArgs,
+ status: Sender<StatusUpdate>,
+ callback: StateCallback<Result<RegisterResult, AuthenticatorError>>,
+ ) {
+ if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ return;
+ }
+
+ let state_obj = self.state.clone();
+
+ // Registration doesn't currently block, but it might in a future version, so we run it on
+ // a background thread.
+ let _ = RunnableBuilder::new("TestTokenManager::register", move || {
+ // TODO(Bug 1854278) We should actually run one thread per token here
+ // and attempt to fulfill this request in parallel.
+ for token in state_obj.lock().unwrap().values_mut() {
+ let _ = token.init();
+ if ctap2::register(
+ token,
+ ctap_args.clone(),
+ status.clone(),
+ callback.clone(),
+ &|| true,
+ ) {
+ // callback was called
+ return;
+ }
+ }
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed)));
+ })
+ .may_block(true)
+ .dispatch_background_task();
+ }
+
+ pub fn sign(
+ &self,
+ _timeout_ms: u64,
+ ctap_args: SignArgs,
+ status: Sender<StatusUpdate>,
+ callback: StateCallback<Result<SignResult, AuthenticatorError>>,
+ ) {
+ if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ return;
+ }
+
+ let state_obj = self.state.clone();
+
+ // Signing can block during signature selection, so we need to run it on a background thread.
+ let _ = RunnableBuilder::new("TestTokenManager::sign", move || {
+ // TODO(Bug 1854278) We should actually run one thread per token here
+ // and attempt to fulfill this request in parallel.
+ for token in state_obj.lock().unwrap().values_mut() {
+ let _ = token.init();
+ if ctap2::sign(
+ token,
+ ctap_args.clone(),
+ status.clone(),
+ callback.clone(),
+ &|| true,
+ ) {
+ // callback was called
+ return;
+ }
+ }
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed)));
+ })
+ .may_block(true)
+ .dispatch_background_task();
+ }
+
+ pub fn has_platform_authenticator(&self) -> bool {
+ if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
+ return false;
+ }
+
+ for token in self.state.lock().unwrap().values_mut() {
+ let _ = token.init();
+ if token.transport.as_str() == "internal" {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ pub fn get_autofill_entries(
+ &self,
+ rp_id: &str,
+ credential_filter: &Vec<PublicKeyCredentialDescriptor>,
+ ) -> Result<ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>, nsresult> {
+ let guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
+ let mut entries = ThinVec::new();
+
+ for token in guard.values() {
+ let credentials = token.get_credentials();
+ for credential in credentials.deref() {
+ // The relying party ID must match.
+ if !rp_id.eq(&credential.rp.id) {
+ continue;
+ }
+ // Only discoverable credentials are admissible.
+ if !credential.is_discoverable_credential {
+ continue;
+ }
+ // Only credentials listed in the credential filter (if it is
+ // non-empty) are admissible.
+ if credential_filter.len() > 0
+ && credential_filter
+ .iter()
+ .find(|cred| cred.id == credential.id)
+ .is_none()
+ {
+ continue;
+ }
+ let entry = WebAuthnAutoFillEntry::allocate(InitWebAuthnAutoFillEntry {
+ rp: credential.rp.id.clone(),
+ credential_id: credential.id.clone(),
+ })
+ .query_interface::<nsIWebAuthnAutoFillEntry>()
+ .ok_or(NS_ERROR_FAILURE)?;
+ entries.push(Some(entry));
+ }
+ }
+ Ok(entries)
+ }
+}
diff --git a/dom/webauthn/components.conf b/dom/webauthn/components.conf
new file mode 100644
index 0000000000..f49a7833e6
--- /dev/null
+++ b/dom/webauthn/components.conf
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{ebe8a51d-bd54-4838-b031-cd2289990e14}',
+ 'contract_ids': ['@mozilla.org/webauthn/service;1'],
+ 'headers': ['/dom/webauthn/WebAuthnService.h'],
+ 'constructor': 'mozilla::dom::NewWebAuthnService',
+ },
+]
diff --git a/dom/webauthn/libudev-sys/Cargo.toml b/dom/webauthn/libudev-sys/Cargo.toml
new file mode 100644
index 0000000000..b438a56927
--- /dev/null
+++ b/dom/webauthn/libudev-sys/Cargo.toml
@@ -0,0 +1,38 @@
+# This is a fork of libudev-sys that dynamically loads its symbols with
+# dlopen when they are first used. This continues a precedent established
+# by Firefox's gamepad APIs to minimize the footprint of features when they
+# aren't being used. Specifically, this avoids the cost of dynamically linking
+# in libudev at process startup.
+#
+# "-sys" crates are a convention in the rust ecosystem for "a simple header
+# for a C library that Rust code can use to build a richer and safer API on
+# top of". In this case, libudev-sys is used by the [libudev crate].
+#
+# As of this writing, this hack is being used by the [authenticator-rs] crate,
+# which uses the libudev crate.
+#
+# The libudev crate assumes libudev is being dynamically linked in, and
+# checks if its symbols are null before intializing anything else, so that
+# you can use it to detect if libudev is installed and gracefully
+# disable gamepads or whatever else if it's not.
+#
+# If we're missing any symbols the libudev crate needs, then rust will give
+# us a compilation error. It's not a problem if we export additional symbols.
+#
+# So while this is a bit of a weird hack, it works pretty robustly, and this
+# crate is basically just a header for libudev so it's not a particularly
+# significant maintenance burden.
+#
+# authenticator-rs: https://github.com/mozilla/authenticator-rs
+# libudev crate: https://crates.io/crates/libudev
+
+[package]
+name = "libudev-sys"
+version = "0.1.3"
+authors = ["Tim Taubert <ttaubert@mozilla.com>"]
+description = "FFI bindings to libudev"
+license = "MPL-2.0"
+
+[dependencies]
+lazy_static = "1.0"
+libc = "0.2"
diff --git a/dom/webauthn/libudev-sys/src/lib.rs b/dom/webauthn/libudev-sys/src/lib.rs
new file mode 100644
index 0000000000..a08234a4c6
--- /dev/null
+++ b/dom/webauthn/libudev-sys/src/lib.rs
@@ -0,0 +1,182 @@
+/* -*- Mode: rust; rust-indent-offset: 2 -*- */
+/* 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/. */
+
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+
+#[macro_use]
+extern crate lazy_static;
+extern crate libc;
+
+use libc::{c_void,c_int,c_char,c_ulonglong,dev_t};
+use libc::{RTLD_GLOBAL,RTLD_LAZY,RTLD_NOLOAD};
+use libc::{dlopen,dlclose,dlsym};
+use std::ffi::CString;
+use std::{marker,mem,ops,ptr};
+
+#[repr(C)]
+pub struct udev {
+ __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_list_entry {
+ __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_device {
+ __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_monitor {
+ __private: c_void
+}
+
+#[repr(C)]
+pub struct udev_enumerate {
+ __private: c_void
+}
+
+macro_rules! ifnull {
+ ($a:expr, $b:expr) => {
+ if $a.is_null() { $b } else { $a }
+ }
+}
+
+struct Library(*mut c_void);
+
+impl Library {
+ fn open(name: &'static str) -> Library {
+ let flags = RTLD_LAZY | RTLD_GLOBAL;
+ let flags_noload = flags | RTLD_NOLOAD;
+ let name = CString::new(name).unwrap();
+ let name = name.as_ptr();
+
+ Library(unsafe {
+ ifnull!(dlopen(name, flags_noload), dlopen(name, flags))
+ })
+ }
+
+ fn get(&self, name: &'static str) -> *mut c_void {
+ let name = CString::new(name).unwrap();
+ unsafe { dlsym(self.0, name.as_ptr()) }
+ }
+}
+
+impl Drop for Library {
+ fn drop(&mut self) {
+ unsafe { dlclose(self.0); }
+ }
+}
+
+unsafe impl Sync for Library {}
+unsafe impl Send for Library {}
+
+lazy_static! {
+ static ref LIBRARY: Library = {
+ Library::open("libudev.so.1")
+ };
+}
+
+pub struct Symbol<T> {
+ ptr: *mut c_void,
+ pd: marker::PhantomData<T>
+}
+
+impl<T> Symbol<T> {
+ fn new(ptr: *mut c_void) -> Self {
+ let default = Self::default as *mut c_void;
+ Self { ptr: ifnull!(ptr, default), pd: marker::PhantomData }
+ }
+
+ // This is the default symbol, used whenever dlopen() fails.
+ // Users of this library are expected to check whether udev_new() returns
+ // a nullptr, and if so they MUST NOT call any other exported functions.
+ extern "C" fn default() -> *mut c_void {
+ ptr::null_mut()
+ }
+}
+
+impl<T> ops::Deref for Symbol<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ unsafe { mem::transmute(&self.ptr) }
+ }
+}
+
+unsafe impl<T: Sync + Send> Sync for Symbol<T> {}
+unsafe impl<T: Sync + Send> Send for Symbol<T> {}
+
+macro_rules! define {
+ ($name:ident, $type:ty) => {
+ lazy_static! {
+ pub static ref $name : Symbol<$type> = {
+ Symbol::new(LIBRARY.get(stringify!($name)))
+ };
+ }
+ };
+}
+
+// udev
+define!(udev_new, extern "C" fn () -> *mut udev);
+define!(udev_unref, extern "C" fn (*mut udev) -> *mut udev);
+
+// udev_list
+define!(udev_list_entry_get_next, extern "C" fn (*mut udev_list_entry) -> *mut udev_list_entry);
+define!(udev_list_entry_get_name, extern "C" fn (*mut udev_list_entry) -> *const c_char);
+define!(udev_list_entry_get_value, extern "C" fn (*mut udev_list_entry) -> *const c_char);
+
+// udev_device
+define!(udev_device_ref, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_unref, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_new_from_syspath, extern "C" fn (*mut udev, *const c_char) -> *mut udev_device);
+define!(udev_device_get_parent, extern "C" fn (*mut udev_device) -> *mut udev_device);
+define!(udev_device_get_devpath, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_subsystem, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devtype, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_syspath, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysname, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysnum, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devnode, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_is_initialized, extern "C" fn (*mut udev_device) -> c_int);
+define!(udev_device_get_properties_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
+define!(udev_device_get_property_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
+define!(udev_device_get_driver, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_devnum, extern "C" fn (*mut udev_device) -> dev_t);
+define!(udev_device_get_action, extern "C" fn (*mut udev_device) -> *const c_char);
+define!(udev_device_get_sysattr_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
+define!(udev_device_set_sysattr_value, extern "C" fn (*mut udev_device, *const c_char, *mut c_char) -> c_int);
+define!(udev_device_get_sysattr_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
+define!(udev_device_get_seqnum, extern "C" fn (*mut udev_device) -> c_ulonglong);
+
+// udev_monitor
+define!(udev_monitor_ref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
+define!(udev_monitor_unref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
+define!(udev_monitor_new_from_netlink, extern "C" fn (*mut udev, *const c_char) -> *mut udev_monitor);
+define!(udev_monitor_enable_receiving, extern "C" fn (*mut udev_monitor) -> c_int);
+define!(udev_monitor_get_fd, extern "C" fn (*mut udev_monitor) -> c_int);
+define!(udev_monitor_receive_device, extern "C" fn (*mut udev_monitor) -> *mut udev_device);
+define!(udev_monitor_filter_add_match_subsystem_devtype, extern "C" fn (*mut udev_monitor, *const c_char, *const c_char) -> c_int);
+define!(udev_monitor_filter_add_match_tag, extern "C" fn (*mut udev_monitor, *const c_char) -> c_int);
+define!(udev_monitor_filter_remove, extern "C" fn (*mut udev_monitor) -> c_int);
+
+// udev_enumerate
+define!(udev_enumerate_unref, extern "C" fn (*mut udev_enumerate) -> *mut udev_enumerate);
+define!(udev_enumerate_new, extern "C" fn (*mut udev) -> *mut udev_enumerate);
+define!(udev_enumerate_add_match_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_nomatch_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_nomatch_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_property, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_tag, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_match_parent, extern "C" fn (*mut udev_enumerate, *mut udev_device) -> c_int);
+define!(udev_enumerate_add_match_is_initialized, extern "C" fn (*mut udev_enumerate) -> c_int);
+define!(udev_enumerate_add_match_sysname, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_add_syspath, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
+define!(udev_enumerate_scan_devices, extern "C" fn (*mut udev_enumerate) -> c_int);
+define!(udev_enumerate_get_list_entry, extern "C" fn (*mut udev_enumerate) -> *mut udev_list_entry);
diff --git a/dom/webauthn/moz.build b/dom/webauthn/moz.build
new file mode 100644
index 0000000000..5d84dc06e7
--- /dev/null
+++ b/dom/webauthn/moz.build
@@ -0,0 +1,97 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Web Authentication")
+
+IPDL_SOURCES += ["PWebAuthnTransaction.ipdl"]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+XPIDL_SOURCES += [
+ "nsIWebAuthnArgs.idl",
+ "nsIWebAuthnAttObj.idl",
+ "nsIWebAuthnPromise.idl",
+ "nsIWebAuthnResult.idl",
+ "nsIWebAuthnService.idl",
+]
+
+XPIDL_MODULE = "dom_webauthn"
+
+EXPORTS.mozilla.dom += [
+ "AuthenticatorAssertionResponse.h",
+ "AuthenticatorAttestationResponse.h",
+ "AuthenticatorResponse.h",
+ "PublicKeyCredential.h",
+ "WebAuthnManager.h",
+ "WebAuthnManagerBase.h",
+ "WebAuthnPromiseHolder.h",
+ "WebAuthnTransactionChild.h",
+ "WebAuthnTransactionParent.h",
+ "WebAuthnUtil.h",
+ "winwebauthn/webauthn.h",
+]
+
+UNIFIED_SOURCES += [
+ "AuthenticatorAssertionResponse.cpp",
+ "AuthenticatorAttestationResponse.cpp",
+ "AuthenticatorResponse.cpp",
+ "PublicKeyCredential.cpp",
+ "WebAuthnArgs.cpp",
+ "WebAuthnAutoFillEntry.cpp",
+ "WebAuthnManager.cpp",
+ "WebAuthnManagerBase.cpp",
+ "WebAuthnPromiseHolder.cpp",
+ "WebAuthnResult.cpp",
+ "WebAuthnService.cpp",
+ "WebAuthnTransactionChild.cpp",
+ "WebAuthnTransactionParent.cpp",
+ "WebAuthnUtil.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/crypto",
+ "/security/manager/ssl",
+ "/third_party/rust",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ UNIFIED_SOURCES += [
+ "AndroidWebAuthnService.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ UNIFIED_SOURCES += [
+ "MacOSWebAuthnService.mm",
+ ]
+ OS_LIBS += [
+ "-framework AuthenticationServices",
+ ]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ OS_LIBS += [
+ "hid",
+ ]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ EXPORTS.mozilla.dom += [
+ "WinWebAuthnService.h",
+ ]
+ UNIFIED_SOURCES += [
+ "WinWebAuthnService.cpp",
+ ]
+
+MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]
+BROWSER_CHROME_MANIFESTS += [
+ "tests/browser/browser.toml",
+]
diff --git a/dom/webauthn/nsIWebAuthnArgs.idl b/dom/webauthn/nsIWebAuthnArgs.idl
new file mode 100644
index 0000000000..72999092fa
--- /dev/null
+++ b/dom/webauthn/nsIWebAuthnArgs.idl
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+typedef long COSEAlgorithmIdentifier;
+
+[uuid(2fc8febe-a277-11ed-bda2-8f6495a5e75c)]
+interface nsIWebAuthnRegisterArgs : nsISupports {
+ // TODO(Bug 1820035) The origin is only used for prompt callbacks. Refactor and remove.
+ readonly attribute AString origin;
+
+ readonly attribute Array<octet> challenge;
+
+ readonly attribute ACString clientDataJSON;
+
+ readonly attribute Array<octet> clientDataHash;
+
+ // A PublicKeyCredentialRpEntity
+ readonly attribute AString rpId;
+ [must_use] readonly attribute AString rpName;
+
+ // A PublicKeyCredentialUserEntity
+ [must_use] readonly attribute Array<octet> userId;
+ [must_use] readonly attribute AString userName;
+ [must_use] readonly attribute AString userDisplayName;
+
+ // The spec defines this as a sequence<PublicKeyCredentialParameters>.
+ // We require type = "public-key" and only serialize the alg fields.
+ [must_use] readonly attribute Array<COSEAlgorithmIdentifier> coseAlgs;
+
+ // The spec defines this as a sequence<PublicKeyCredentialDescriptor>,
+ // we use separate arrays for the credential IDs and transports.
+ readonly attribute Array<Array<octet> > excludeList;
+ readonly attribute Array<octet> excludeListTransports;
+
+ // CTAP2 passes extensions in a CBOR map of extension identifier ->
+ // WebAuthn AuthenticationExtensionsClientInputs. That's not feasible here.
+ // So we define a getter for each supported extension input and use the
+ // return code to signal presence.
+ [must_use] readonly attribute bool credProps;
+ [must_use] readonly attribute bool hmacCreateSecret;
+ [must_use] readonly attribute bool minPinLength;
+
+ // Options.
+ readonly attribute AString residentKey;
+ readonly attribute AString userVerification;
+ [must_use] readonly attribute AString authenticatorAttachment;
+
+ // This is the WebAuthn PublicKeyCredentialCreationOptions timeout.
+ // Arguably we don't need to pass it through since WebAuthnController can
+ // cancel transactions.
+ readonly attribute uint32_t timeoutMS;
+
+ // This is the WebAuthn PublicKeyCredentialCreationOptions attestation.
+ // We might overwrite the provided value with "none" if the user declines the
+ // consent popup.
+ [must_use] readonly attribute AString attestationConveyancePreference;
+};
+
+[uuid(2e621cf4-a277-11ed-ae00-bf41a54ef553)]
+interface nsIWebAuthnSignArgs : nsISupports {
+ // TODO(Bug 1820035) The origin is only used for prompt callbacks. Refactor and remove.
+ readonly attribute AString origin;
+
+ // The spec only asks for the ID field of a PublicKeyCredentialRpEntity here
+ readonly attribute AString rpId;
+
+ readonly attribute Array<octet> challenge;
+
+ readonly attribute ACString clientDataJSON;
+
+ readonly attribute Array<octet> clientDataHash;
+
+ // The spec defines this as a sequence<PublicKeyCredentialDescriptor>,
+ // we use separate arrays for the credential IDs and transports.
+ readonly attribute Array<Array<octet> > allowList;
+ readonly attribute Array<octet> allowListTransports;
+
+ // CTAP2 passes extensions in a CBOR map of extension identifier ->
+ // WebAuthn AuthenticationExtensionsClientInputs. That's not feasible here.
+ // So we define a getter for each supported extension input and use the
+ // return code to signal presence.
+ [must_use] readonly attribute bool hmacCreateSecret;
+ [must_use] readonly attribute AString appId;
+
+ // Options
+ [must_use] readonly attribute AString userVerification;
+
+ // This is the WebAuthn PublicKeyCredentialCreationOptions timeout.
+ // Arguably we don't need to pass it through since WebAuthnController can
+ // cancel transactions.
+ readonly attribute unsigned long timeoutMS;
+
+ readonly attribute bool conditionallyMediated;
+};
diff --git a/dom/webauthn/nsIWebAuthnAttObj.idl b/dom/webauthn/nsIWebAuthnAttObj.idl
new file mode 100644
index 0000000000..32d4f0aba3
--- /dev/null
+++ b/dom/webauthn/nsIWebAuthnAttObj.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "nsIWebAuthnArgs.idl"
+
+[uuid(91e41be0-ed73-4a10-b55e-3312319bfddf)]
+interface nsIWebAuthnAttObj : nsISupports {
+ // The serialied attestation object as defined in
+ // https://www.w3.org/TR/webauthn-2/#sctn-attestation
+ readonly attribute Array<octet> attestationObject;
+
+ readonly attribute Array<octet> authenticatorData;
+
+ readonly attribute Array<octet> publicKey;
+
+ readonly attribute COSEAlgorithmIdentifier publicKeyAlgorithm;
+};
diff --git a/dom/webauthn/nsIWebAuthnPromise.idl b/dom/webauthn/nsIWebAuthnPromise.idl
new file mode 100644
index 0000000000..245515be3f
--- /dev/null
+++ b/dom/webauthn/nsIWebAuthnPromise.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "nsIWebAuthnResult.idl"
+
+[rust_sync, uuid(3c969aec-e0e0-4aa4-9422-394b321e6918)]
+interface nsIWebAuthnRegisterPromise : nsISupports
+{
+ [noscript] void resolve(in nsIWebAuthnRegisterResult aResult);
+ [noscript] void reject(in nsresult error);
+};
+
+[rust_sync, uuid(35e35bdc-5369-4bfe-8d5c-bdf7b782b735)]
+interface nsIWebAuthnSignPromise : nsISupports
+{
+ [noscript] void resolve(in nsIWebAuthnSignResult aResult);
+ [noscript] void reject(in nsresult error);
+};
diff --git a/dom/webauthn/nsIWebAuthnResult.idl b/dom/webauthn/nsIWebAuthnResult.idl
new file mode 100644
index 0000000000..aaf34cc5f9
--- /dev/null
+++ b/dom/webauthn/nsIWebAuthnResult.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+[uuid(0567c384-a728-11ed-85f7-030324a370f0)]
+interface nsIWebAuthnRegisterResult : nsISupports {
+ readonly attribute ACString clientDataJSON;
+
+ // The serialied attestation object as defined in
+ // https://www.w3.org/TR/webauthn-2/#sctn-attestation
+ // Includes the format, the attestation statement, and
+ // the authenticator data.
+ readonly attribute Array<octet> attestationObject;
+
+ // The Credential ID field of the Attestation Object's Attested
+ // Credential Data. This is used to construct the rawID field of a
+ // WebAuthn PublicKeyCredential without having to parse the
+ // attestationObject.
+ readonly attribute Array<octet> credentialId;
+
+ readonly attribute Array<AString> transports;
+
+ readonly attribute bool hmacCreateSecret;
+
+ [must_use] attribute bool credPropsRk;
+
+ [must_use] readonly attribute AString authenticatorAttachment;
+};
+
+// The nsIWebAuthnSignResult interface is used to construct IPDL-defined
+// WebAuthnGetAssertionResult from either Rust or C++.
+//
+[uuid(05fff816-a728-11ed-b9ac-ff38cc2c8c28)]
+interface nsIWebAuthnSignResult : nsISupports {
+ readonly attribute ACString clientDataJSON;
+
+ // The ID field of the PublicKeyCredentialDescriptor returned
+ // from authenticatorGetAssertion.
+ readonly attribute Array<octet> credentialId;
+
+ // The authData field of the authenticatorGetAssertion response
+ readonly attribute Array<octet> authenticatorData;
+
+ // The signature field of the authenticatorGetAssertion response
+ readonly attribute Array<octet> signature;
+
+ // The ID field of the PublicKeyCredentialUserEntity returned from
+ // authenticatorGetAssertion. (Optional)
+ [must_use] readonly attribute Array<octet> userHandle;
+
+ // The displayName field of the PublicKeyCredentialUserEntity
+ // returned from authenticatorGetAssertion. (Optional)
+ [must_use] readonly attribute ACString userName;
+
+ // appId field of AuthenticationExtensionsClientOutputs (Optional)
+ [must_use] attribute bool usedAppId;
+
+ [must_use] readonly attribute AString authenticatorAttachment;
+};
diff --git a/dom/webauthn/nsIWebAuthnService.idl b/dom/webauthn/nsIWebAuthnService.idl
new file mode 100644
index 0000000000..6525508057
--- /dev/null
+++ b/dom/webauthn/nsIWebAuthnService.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "nsIWebAuthnArgs.idl"
+#include "nsIWebAuthnPromise.idl"
+
+[scriptable, uuid(6c4ecd9f-57c0-4d7d-8080-bf6e4d499f8f)]
+interface nsICredentialParameters : nsISupports
+{
+ readonly attribute ACString credentialId;
+ readonly attribute bool isResidentCredential;
+ readonly attribute ACString rpId;
+ readonly attribute ACString privateKey;
+ readonly attribute ACString userHandle;
+ readonly attribute uint32_t signCount;
+};
+
+[scriptable, uuid(686d552e-a39d-4ba2-8127-faca54274039)]
+interface nsIWebAuthnAutoFillEntry: nsISupports
+{
+ const octet PROVIDER_UNKNOWN = 0;
+ const octet PROVIDER_TEST_TOKEN = 1;
+ const octet PROVIDER_PLATFORM_WINDOWS = 2;
+ const octet PROVIDER_PLATFORM_MACOS = 3;
+ const octet PROVIDER_PLATFORM_ANDROID = 4;
+
+ readonly attribute octet provider;
+ readonly attribute AString userName;
+ readonly attribute AString rpId;
+ readonly attribute Array<uint8_t> credentialId;
+};
+
+[scriptable, uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)]
+interface nsIWebAuthnService : nsISupports
+{
+ // IsUserVerifyingPlatformAuthenticatorAvailable
+ readonly attribute bool isUVPAA;
+
+ void makeCredential(
+ in uint64_t aTransactionId,
+ in uint64_t browsingContextId,
+ in nsIWebAuthnRegisterArgs args,
+ in nsIWebAuthnRegisterPromise promise);
+
+ void getAssertion(
+ in uint64_t aTransactionId,
+ in uint64_t browsingContextId,
+ in nsIWebAuthnSignArgs args,
+ in nsIWebAuthnSignPromise promise);
+
+ // Cancel the ongoing transaction and any prompts that are shown, but do not reject
+ // its promise. This is used by the IPC parent when it receives an abort signal.
+ // The IPC child has already rejected the promises at this point.
+ [noscript] void reset();
+
+ // Cancel the ongoing transaction. Reject its promise, but do not cancel
+ // prompts. This is used by WebAuthnPromptHelper when the user hits the
+ // "cancel" button.
+ void cancel(in uint64_t aTransactionId);
+
+ // `hasPendingConditionalGet` returns the transaction ID of a pending
+ // conditionally-mediated getAssertion promise. The browsing context and
+ // origin arguments must match those of the pending promise. If there is no
+ // pending getAssertion promise, or the browsing context and origin do not
+ // match, then `hasPendingConditionalGet` returns 0.
+ uint64_t hasPendingConditionalGet(in uint64_t aBrowsingContextId, in AString aOrigin);
+
+ // If there is a pending conditionally-mediated getAssertion promise with
+ // transaction ID equal to `aTransactionId`, `getAutoFillEntries` returns
+ // an nsIWebAuthnAutoFillEntry for each silently discoverable credential
+ // that can be used to fullfill the request.
+ Array<nsIWebAuthnAutoFillEntry> getAutoFillEntries(in uint64_t aTransactionId);
+
+ // A pending conditionally-mediated getAssertion promise is resolved by
+ // calling `selectAutoFillEntry` or `resumeConditionalGet`.
+ // `selectAutoFillEntry` specifies the credential ID that should be used to
+ // fulfill the request, whereas `resumeConditionalGet` indicates that any
+ // allowed credential can be used.
+ void selectAutoFillEntry(in uint64_t aTransactionId, in Array<uint8_t> aCredentialId);
+ void resumeConditionalGet(in uint64_t aTransactionId);
+
+ void pinCallback(in uint64_t aTransactionId, in ACString aPin);
+ void resumeMakeCredential(in uint64_t aTransactionId, in bool aForceNoneAttestation);
+ void selectionCallback(in uint64_t aTransactionId, in uint64_t aIndex);
+
+ // Adds a virtual (software) authenticator for use in tests (particularly
+ // tests run via WebDriver). See
+ // https://w3c.github.io/webauthn/#sctn-automation-add-virtual-authenticator.
+ uint64_t addVirtualAuthenticator(
+ in ACString protocol,
+ in ACString transport,
+ in bool hasResidentKey,
+ in bool hasUserVerification,
+ in bool isUserConsenting,
+ in bool isUserVerified);
+
+ // Removes a previously-added virtual authenticator, as identified by its
+ // id. See
+ // https://w3c.github.io/webauthn/#sctn-automation-remove-virtual-authenticator
+ void removeVirtualAuthenticator(in uint64_t authenticatorId);
+
+ // Adds a credential to a previously-added authenticator. See
+ // https://w3c.github.io/webauthn/#sctn-automation-add-credential
+ void addCredential(
+ in uint64_t authenticatorId,
+ in ACString credentialId,
+ in bool isResidentCredential,
+ in ACString rpId,
+ in ACString privateKey,
+ in ACString userHandle,
+ in uint32_t signCount);
+
+ // Gets all credentials that have been added to a virtual authenticator.
+ // See https://w3c.github.io/webauthn/#sctn-automation-get-credentials
+ Array<nsICredentialParameters> getCredentials(in uint64_t authenticatorId);
+
+ // Removes a credential from a virtual authenticator. See
+ // https://w3c.github.io/webauthn/#sctn-automation-remove-credential
+ void removeCredential(in uint64_t authenticatorId, in ACString credentialId);
+
+ // Removes all credentials from a virtual authenticator. See
+ // https://w3c.github.io/webauthn/#sctn-automation-remove-all-credentials
+ void removeAllCredentials(in uint64_t authenticatorId);
+
+ // Sets the "isUserVerified" bit on a virtual authenticator. See
+ // https://w3c.github.io/webauthn/#sctn-automation-set-user-verified
+ void setUserVerified(in uint64_t authenticatorId, in bool isUserVerified);
+
+ // about:webauthn-specific functions
+ void listen();
+ void runCommand(in ACString aCommand);
+};
diff --git a/dom/webauthn/tests/browser/browser.toml b/dom/webauthn/tests/browser/browser.toml
new file mode 100644
index 0000000000..41dc82d6ee
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser.toml
@@ -0,0 +1,35 @@
+[DEFAULT]
+support-files = [
+ "head.js",
+ "tab_webauthn_result.html",
+ "../pkijs/*",
+ "../cbor.js",
+ "../u2futil.js",
+]
+prefs = [
+ "security.webauth.webauthn=true",
+ "security.webauth.webauthn_enable_softtoken=true",
+ "security.webauth.webauthn_enable_usbtoken=false",
+ "security.webauthn.ctap2=true",
+ "security.webauthn.enable_conditional_mediation=true",
+ "security.webauthn.enable_macos_passkeys=false",
+]
+
+["browser_abort_visibility.js"]
+skip-if = [
+ "win11_2009", # Test not relevant on 1903+
+]
+
+["browser_fido_appid_extension.js"]
+skip-if = [
+ "win11_2009", # Test not relevant on 1903+
+]
+
+["browser_webauthn_conditional_mediation.js"]
+
+["browser_webauthn_ipaddress.js"]
+
+["browser_webauthn_prompts.js"]
+skip-if = [
+ "win11_2009", # Test not relevant on 1903+
+]
diff --git a/dom/webauthn/tests/browser/browser_abort_visibility.js b/dom/webauthn/tests/browser/browser_abort_visibility.js
new file mode 100644
index 0000000000..d8b2b5edcc
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_abort_visibility.js
@@ -0,0 +1,277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_URL =
+ "https://example.com/browser/dom/webauthn/tests/browser/tab_webauthn_result.html";
+
+add_task(async function test_setup() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.webauth.webauthn_enable_softtoken", false],
+ ["security.webauth.webauthn_enable_usbtoken", true],
+ ],
+ });
+});
+add_task(test_switch_tab);
+add_task(test_new_window_make);
+add_task(test_new_window_get);
+add_task(test_minimize_make);
+add_task(test_minimize_get);
+
+async function assertStatus(tab, expected) {
+ let actual = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ info("visbility state: " + content.document.visibilityState);
+ info("active: " + content.browsingContext.isActive);
+ return content.document.getElementById("status").value;
+ }
+ );
+ is(actual, expected, "webauthn request " + expected);
+}
+
+async function waitForStatus(tab, expected) {
+ /* eslint-disable no-shadow */
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [[expected]],
+ async function (expected) {
+ return ContentTaskUtils.waitForCondition(() => {
+ info(
+ "expecting " +
+ expected +
+ ", visbility state: " +
+ content.document.visibilityState
+ );
+ info(
+ "expecting " +
+ expected +
+ ", active: " +
+ content.browsingContext.isActive
+ );
+ return content.document.getElementById("status").value == expected;
+ });
+ }
+ );
+ /* eslint-enable no-shadow */
+
+ await assertStatus(tab, expected);
+}
+
+function startMakeCredentialRequest(tab) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const cose_alg_ECDSA_w_SHA256 = -7;
+
+ let publicKey = {
+ rp: { id: content.document.domain, name: "none" },
+ user: {
+ id: new Uint8Array(),
+ name: "none",
+ displayName: "none",
+ },
+ challenge: content.crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ pubKeyCredParams: [{ type: "public-key", alg: cose_alg_ECDSA_w_SHA256 }],
+ };
+
+ let status = content.document.getElementById("status");
+
+ info(
+ "Attempting to create credential for origin: " +
+ content.document.nodePrincipal.origin
+ );
+ content.navigator.credentials
+ .create({ publicKey })
+ .then(() => {
+ status.value = "completed";
+ })
+ .catch(() => {
+ status.value = "aborted";
+ });
+
+ status.value = "pending";
+ });
+}
+
+function startGetAssertionRequest(tab) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let newCredential = {
+ type: "public-key",
+ id: content.crypto.getRandomValues(new Uint8Array(16)),
+ transports: ["usb"],
+ };
+
+ let publicKey = {
+ challenge: content.crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: content.document.domain,
+ allowCredentials: [newCredential],
+ };
+
+ let status = content.document.getElementById("status");
+
+ info(
+ "Attempting to get credential for origin: " +
+ content.document.nodePrincipal.origin
+ );
+ content.navigator.credentials
+ .get({ publicKey })
+ .then(() => {
+ status.value = "completed";
+ })
+ .catch(ex => {
+ info("aborted: " + ex);
+ status.value = "aborted";
+ });
+
+ status.value = "pending";
+ });
+}
+
+// Test that MakeCredential() and GetAssertion() requests
+// are aborted when the current tab loses its focus.
+async function test_switch_tab() {
+ // Create a new tab for the MakeCredential() request.
+ let tab_create = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_URL
+ );
+
+ // Start the request.
+ await startMakeCredentialRequest(tab_create);
+ await assertStatus(tab_create, "pending");
+
+ // Open another tab and switch to it. The first will lose focus.
+ let tab_get = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ await assertStatus(tab_create, "pending");
+
+ // Start a GetAssertion() request in the second tab, the first is aborted
+ await startGetAssertionRequest(tab_get);
+ await waitForStatus(tab_create, "aborted");
+ await assertStatus(tab_get, "pending");
+
+ // Start a second request in the second tab. It should abort.
+ await startGetAssertionRequest(tab_get);
+ await waitForStatus(tab_get, "aborted");
+
+ // Close tabs.
+ BrowserTestUtils.removeTab(tab_create);
+ BrowserTestUtils.removeTab(tab_get);
+}
+
+function waitForWindowActive(win, active) {
+ return Promise.all([
+ BrowserTestUtils.waitForEvent(win, active ? "focus" : "blur"),
+ BrowserTestUtils.waitForEvent(win, active ? "activate" : "deactivate"),
+ ]);
+}
+
+async function test_new_window_make() {
+ // Create a new tab for the MakeCredential() request.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Start a MakeCredential request.
+ await startMakeCredentialRequest(tab);
+ await assertStatus(tab, "pending");
+
+ let windowGonePromise = waitForWindowActive(window, false);
+ // Open a new window. The tab will lose focus.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await windowGonePromise;
+ await assertStatus(tab, "pending");
+
+ let windowBackPromise = waitForWindowActive(window, true);
+ await BrowserTestUtils.closeWindow(win);
+ await windowBackPromise;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_new_window_get() {
+ // Create a new tab for the GetAssertion() request.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Start a GetAssertion request.
+ await startGetAssertionRequest(tab);
+ await assertStatus(tab, "pending");
+
+ let windowGonePromise = waitForWindowActive(window, false);
+ // Open a new window. The tab will lose focus.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await windowGonePromise;
+ await assertStatus(tab, "pending");
+
+ let windowBackPromise = waitForWindowActive(window, true);
+ await BrowserTestUtils.closeWindow(win);
+ await windowBackPromise;
+
+ // Close tab.
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function test_minimize_make() {
+ // Minimizing windows doesn't supported in headless mode.
+ if (Services.env.get("MOZ_HEADLESS")) {
+ return;
+ }
+
+ // Create a new window for the MakeCredential() request.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+
+ // Start a MakeCredential request.
+ await startMakeCredentialRequest(tab);
+ await assertStatus(tab, "pending");
+
+ // Minimize the window.
+ let windowGonePromise = waitForWindowActive(win, false);
+ win.minimize();
+ await assertStatus(tab, "pending");
+ await windowGonePromise;
+
+ // Restore the window.
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve, win));
+ await assertStatus(tab, "pending");
+
+ // Close window and wait for main window to be focused again.
+ let windowBackPromise = waitForWindowActive(window, true);
+ await BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ await windowBackPromise;
+}
+
+async function test_minimize_get() {
+ // Minimizing windows doesn't supported in headless mode.
+ if (Services.env.get("MOZ_HEADLESS")) {
+ return;
+ }
+
+ // Create a new window for the GetAssertion() request.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
+
+ // Start a GetAssertion request.
+ await startGetAssertionRequest(tab);
+ await assertStatus(tab, "pending");
+
+ // Minimize the window.
+ let windowGonePromise = waitForWindowActive(win, false);
+ win.minimize();
+ await assertStatus(tab, "pending");
+ await windowGonePromise;
+
+ // Restore the window.
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve, win));
+ await assertStatus(tab, "pending");
+
+ // Close window and wait for main window to be focused again.
+ let windowBackPromise = waitForWindowActive(window, true);
+ await BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ await windowBackPromise;
+}
diff --git a/dom/webauthn/tests/browser/browser_fido_appid_extension.js b/dom/webauthn/tests/browser/browser_fido_appid_extension.js
new file mode 100644
index 0000000000..fca4443074
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_fido_appid_extension.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_URL = "https://example.com/";
+
+let expectNotSupportedError = expectError("NotSupported");
+let expectNotAllowedError = expectError("NotAllowed");
+let expectSecurityError = expectError("Security");
+
+let gAppId = "https://example.com/appId";
+let gCrossOriginAppId = "https://example.org/appId";
+let gAuthenticatorId = add_virtual_authenticator();
+
+add_task(async function test_appid() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // The FIDO AppId extension can't be used for MakeCredential.
+ await promiseWebAuthnMakeCredential(tab, "none", "discouraged", {
+ appid: gAppId,
+ })
+ .then(arrivingHereIsBad)
+ .catch(expectNotSupportedError);
+
+ // Side-load a credential with an RP ID matching the App ID.
+ let credIdB64 = await addCredential(gAuthenticatorId, gAppId);
+ let credId = base64ToBytesUrlSafe(credIdB64);
+
+ // And another for a different origin
+ let crossOriginCredIdB64 = await addCredential(
+ gAuthenticatorId,
+ gCrossOriginAppId
+ );
+ let crossOriginCredId = base64ToBytesUrlSafe(crossOriginCredIdB64);
+
+ // The App ID extension is required
+ await promiseWebAuthnGetAssertion(tab, credId)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // The value in the App ID extension must match the origin.
+ await promiseWebAuthnGetAssertion(tab, crossOriginCredId, {
+ appid: gCrossOriginAppId,
+ })
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+
+ // The value in the App ID extension must match the credential's RP ID.
+ await promiseWebAuthnGetAssertion(tab, credId, { appid: gAppId + "2" })
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // Succeed with the right App ID.
+ let rpIdHash = await promiseWebAuthnGetAssertion(tab, credId, {
+ appid: gAppId,
+ })
+ .then(({ authenticatorData, extensions }) => {
+ is(extensions.appid, true, "appid extension was acted upon");
+ return authenticatorData.slice(0, 32);
+ })
+ .then(rpIdHash => {
+ // Make sure the returned RP ID hash matches the hash of the App ID.
+ checkRpIdHash(rpIdHash, gAppId);
+ })
+ .catch(arrivingHereIsBad);
+
+ removeCredential(gAuthenticatorId, credIdB64);
+ removeCredential(gAuthenticatorId, crossOriginCredIdB64);
+
+ // Close tab.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_appid_unused() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let appid = "https://example.com/appId";
+
+ let { attObj, rawId } = await promiseWebAuthnMakeCredential(tab);
+ let { authDataObj } = await webAuthnDecodeCBORAttestation(attObj);
+
+ // Make sure the RP ID hash matches what we calculate.
+ await checkRpIdHash(authDataObj.rpIdHash, "example.com");
+
+ // Get a new assertion.
+ let { clientDataJSON, authenticatorData, signature, extensions } =
+ await promiseWebAuthnGetAssertion(tab, rawId, { appid });
+
+ ok(
+ "appid" in extensions,
+ `appid should be populated in the extensions data, but saw: ` +
+ `${JSON.stringify(extensions)}`
+ );
+ is(extensions.appid, false, "appid extension should indicate it was unused");
+
+ // Check auth data.
+ let attestation = await webAuthnDecodeAuthDataArray(
+ new Uint8Array(authenticatorData)
+ );
+ is(
+ "" + (attestation.flags & flag_TUP),
+ "" + flag_TUP,
+ "Assertion's user presence byte set correctly"
+ );
+
+ // Verify the signature.
+ let params = await deriveAppAndChallengeParam(
+ "example.com",
+ clientDataJSON,
+ attestation
+ );
+ let signedData = await assembleSignedData(
+ params.appParam,
+ params.attestation.flags,
+ params.attestation.counter,
+ params.challengeParam
+ );
+ let valid = await verifySignature(
+ authDataObj.publicKeyHandle,
+ signedData,
+ signature
+ );
+ ok(valid, "signature is valid");
+
+ // Close tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/webauthn/tests/browser/browser_webauthn_conditional_mediation.js b/dom/webauthn/tests/browser/browser_webauthn_conditional_mediation.js
new file mode 100644
index 0000000000..fff1ec5dab
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_webauthn_conditional_mediation.js
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_URL = "https://example.com";
+
+let gAuthenticatorId = add_virtual_authenticator();
+let gExpectNotAllowedError = expectError("NotAllowed");
+let gExpectAbortError = expectError("Abort");
+let gPendingConditionalGetSubject = "webauthn:conditional-get-pending";
+let gWebAuthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+);
+
+add_task(async function test_webauthn_modal_request_cancels_conditional_get() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let browser = tab.linkedBrowser.browsingContext.embedderElement;
+ let browsingContextId = browser.browsingContext.id;
+
+ let transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.equal(transactionId, 0, "should not have a pending conditional get");
+
+ let requestStarted = TestUtils.topicObserved(gPendingConditionalGetSubject);
+
+ let active = true;
+ let condPromise = promiseWebAuthnGetAssertionDiscoverable(tab, "conditional")
+ .then(arrivingHereIsBad)
+ .catch(gExpectAbortError)
+ .then(() => (active = false));
+
+ await requestStarted;
+
+ transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.notEqual(transactionId, 0, "should have a pending conditional get");
+
+ ok(active, "conditional request should still be active");
+
+ let promptPromise = promiseNotification("webauthn-prompt-register-direct");
+ let modalPromise = promiseWebAuthnMakeCredential(tab, "direct")
+ .then(arrivingHereIsBad)
+ .catch(gExpectNotAllowedError);
+
+ await condPromise;
+
+ ok(!active, "conditional request should not be active");
+
+ // Cancel the modal request with the button.
+ await promptPromise;
+ PopupNotifications.panel.firstElementChild.secondaryButton.click();
+ await modalPromise;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_webauthn_resume_conditional_get() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let browser = tab.linkedBrowser.browsingContext.embedderElement;
+ let browsingContextId = browser.browsingContext.id;
+
+ let transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.equal(transactionId, 0, "should not have a pending conditional get");
+
+ let requestStarted = TestUtils.topicObserved(gPendingConditionalGetSubject);
+
+ let active = true;
+ let promise = promiseWebAuthnGetAssertionDiscoverable(tab, "conditional")
+ .then(arrivingHereIsBad)
+ .catch(gExpectNotAllowedError)
+ .then(() => (active = false));
+
+ await requestStarted;
+
+ transactionId = gWebAuthnService.hasPendingConditionalGet(0, TEST_URL);
+ Assert.equal(
+ transactionId,
+ 0,
+ "hasPendingConditionalGet should check the browsing context id"
+ );
+
+ transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ "https://example.org"
+ );
+ Assert.equal(
+ transactionId,
+ 0,
+ "hasPendingConditionalGet should check the origin"
+ );
+
+ transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.notEqual(transactionId, 0, "should have a pending conditional get");
+
+ ok(active, "request should still be active");
+
+ gWebAuthnService.resumeConditionalGet(transactionId);
+ await promise;
+
+ ok(!active, "request should not be active");
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_webauthn_select_autofill_entry() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Add credentials
+ let cred1 = await addCredential(gAuthenticatorId, "example.com");
+ let cred2 = await addCredential(gAuthenticatorId, "example.com");
+
+ let browser = tab.linkedBrowser.browsingContext.embedderElement;
+ let browsingContextId = browser.browsingContext.id;
+
+ let transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.equal(transactionId, 0, "should not have a pending conditional get");
+
+ let requestStarted = TestUtils.topicObserved(gPendingConditionalGetSubject);
+
+ let active = true;
+ let promise = promiseWebAuthnGetAssertionDiscoverable(tab, "conditional")
+ .catch(arrivingHereIsBad)
+ .then(() => (active = false));
+
+ await requestStarted;
+
+ transactionId = gWebAuthnService.hasPendingConditionalGet(
+ browsingContextId,
+ TEST_URL
+ );
+ Assert.notEqual(transactionId, 0, "should have a pending conditional get");
+
+ let autoFillEntries = gWebAuthnService.getAutoFillEntries(transactionId);
+ ok(
+ autoFillEntries.length == 2 &&
+ autoFillEntries[0].rpId == "example.com" &&
+ autoFillEntries[1].rpId == "example.com",
+ "should have two autofill entries for example.com"
+ );
+
+ gWebAuthnService.selectAutoFillEntry(
+ transactionId,
+ autoFillEntries[0].credentialId
+ );
+ let result = await promise;
+
+ ok(!active, "request should not be active");
+
+ // Remove credentials
+ gWebAuthnService.removeCredential(gAuthenticatorId, cred1);
+ gWebAuthnService.removeCredential(gAuthenticatorId, cred2);
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js b/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js
new file mode 100644
index 0000000000..6658ae5f02
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_virtual_authenticator();
+
+let expectSecurityError = expectError("Security");
+
+add_task(async function test_setup() {
+ return SpecialPowers.pushPrefEnv({
+ set: [["network.proxy.allow_hijacking_localhost", true]],
+ });
+});
+
+add_task(async function test_appid() {
+ // 127.0.0.1 triggers special cases in ssltunnel, so let's use .2!
+ const TEST_URL = "https://127.0.0.2/";
+
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ await promiseWebAuthnMakeCredential(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+
+ // Close tab.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/webauthn/tests/browser/browser_webauthn_prompts.js b/dom/webauthn/tests/browser/browser_webauthn_prompts.js
new file mode 100644
index 0000000000..05c77271d5
--- /dev/null
+++ b/dom/webauthn/tests/browser/browser_webauthn_prompts.js
@@ -0,0 +1,501 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["FullScreen"],
+ "chrome://browser/content/browser-fullScreenAndPointerLock.js"
+);
+
+const TEST_URL = "https://example.com/";
+var gAuthenticatorId;
+
+/**
+ * Waits for the PopupNotifications button enable delay to expire so the
+ * Notification can be interacted with using the buttons.
+ */
+async function waitForPopupNotificationSecurityDelay() {
+ let notification = PopupNotifications.panel.firstChild.notification;
+ let notificationEnableDelayMS = Services.prefs.getIntPref(
+ "security.notification_enable_delay"
+ );
+ await TestUtils.waitForCondition(
+ () => {
+ let timeSinceShown = performance.now() - notification.timeShown;
+ return timeSinceShown > notificationEnableDelayMS;
+ },
+ "Wait for security delay to expire",
+ 500,
+ 50
+ );
+}
+
+add_task(async function test_setup_usbtoken() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.webauth.webauthn_enable_softtoken", false],
+ ["security.webauth.webauthn_enable_usbtoken", true],
+ ],
+ });
+});
+add_task(test_register);
+add_task(test_register_escape);
+add_task(test_register_direct_cancel);
+add_task(test_register_direct_presence);
+add_task(test_sign);
+add_task(test_sign_escape);
+add_task(test_tab_switching);
+add_task(test_window_switching);
+add_task(async function test_setup_fullscreen() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.fullscreen.autohide", true],
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+});
+add_task(test_fullscreen_show_nav_toolbar);
+add_task(test_no_fullscreen_dom);
+add_task(async function test_setup_softtoken() {
+ gAuthenticatorId = add_virtual_authenticator();
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.webauth.webauthn_enable_softtoken", true],
+ ["security.webauth.webauthn_enable_usbtoken", false],
+ ],
+ });
+});
+add_task(test_register_direct_proceed);
+add_task(test_register_direct_proceed_anon);
+add_task(test_select_sign_result);
+
+function promiseNavToolboxStatus(aExpectedStatus) {
+ let navToolboxStatus;
+ return TestUtils.topicObserved("fullscreen-nav-toolbox", (subject, data) => {
+ navToolboxStatus = data;
+ return data == aExpectedStatus;
+ }).then(() =>
+ Assert.equal(
+ navToolboxStatus,
+ aExpectedStatus,
+ "nav toolbox is " + aExpectedStatus
+ )
+ );
+}
+
+function promiseFullScreenPaint(aExpectedStatus) {
+ return TestUtils.topicObserved("fullscreen-painted");
+}
+
+function triggerMainPopupCommand(popup) {
+ info("triggering main command");
+ let notifications = popup.childNodes;
+ ok(notifications.length, "at least one notification displayed");
+ let notification = notifications[0];
+ info("triggering command: " + notification.getAttribute("buttonlabel"));
+
+ return EventUtils.synthesizeMouseAtCenter(notification.button, {});
+}
+
+let expectNotAllowedError = expectError("NotAllowed");
+
+function verifyAnonymizedCertificate(aResult) {
+ return webAuthnDecodeCBORAttestation(aResult.attObj).then(
+ ({ fmt, attStmt }) => {
+ is(fmt, "none", "Is a None Attestation");
+ is(typeof attStmt, "object", "attStmt is a map");
+ is(Object.keys(attStmt).length, 0, "attStmt is empty");
+ }
+ );
+}
+
+async function verifyDirectCertificate(aResult) {
+ let clientDataHash = await crypto.subtle
+ .digest("SHA-256", aResult.clientDataJSON)
+ .then(digest => new Uint8Array(digest));
+ let { fmt, attStmt, authData, authDataObj } =
+ await webAuthnDecodeCBORAttestation(aResult.attObj);
+ is(fmt, "packed", "Is a Packed Attestation");
+ let signedData = new Uint8Array(authData.length + clientDataHash.length);
+ signedData.set(authData);
+ signedData.set(clientDataHash, authData.length);
+ let valid = await verifySignature(
+ authDataObj.publicKeyHandle,
+ signedData,
+ new Uint8Array(attStmt.sig)
+ );
+ ok(valid, "Signature is valid.");
+}
+
+async function test_register() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnMakeCredential(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+
+ // Cancel the request with the button.
+ ok(active, "request should still be active");
+ PopupNotifications.panel.firstElementChild.button.click();
+ await request;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_register_escape() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnMakeCredential(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+
+ // Cancel the request by hitting escape.
+ ok(active, "request should still be active");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await request;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_sign() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new assertion and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnGetAssertion(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+
+ // Cancel the request with the button.
+ ok(active, "request should still be active");
+ PopupNotifications.panel.firstElementChild.button.click();
+ await request;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_sign_escape() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new assertion and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnGetAssertion(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+
+ // Cancel the request by hitting escape.
+ ok(active, "request should still be active");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await request;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_register_direct_cancel() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential with direct attestation and wait for the prompt.
+ let active = true;
+ let promise = promiseWebAuthnMakeCredential(tab, "direct")
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-register-direct");
+
+ // Cancel the request.
+ ok(active, "request should still be active");
+ PopupNotifications.panel.firstElementChild.secondaryButton.click();
+ await promise;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_register_direct_presence() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential with direct attestation and wait for the prompt.
+ let active = true;
+ let promise = promiseWebAuthnMakeCredential(tab, "direct")
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-register-direct");
+
+ // Click "proceed" and wait for presence prompt
+ let presence = promiseNotification("webauthn-prompt-presence");
+ PopupNotifications.panel.firstElementChild.button.click();
+ await presence;
+
+ // Cancel the request.
+ ok(active, "request should still be active");
+ PopupNotifications.panel.firstElementChild.button.click();
+ await promise;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+// Add two tabs, open WebAuthn in the first, switch, assert the prompt is
+// not visible, switch back, assert the prompt is there and cancel it.
+async function test_tab_switching() {
+ // Open a new tab.
+ let tab_one = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnMakeCredential(tab_one)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+ is(PopupNotifications.panel.state, "open", "Doorhanger is visible");
+
+ // Open and switch to a second tab.
+ let tab_two = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.org/"
+ );
+
+ await TestUtils.waitForCondition(
+ () => PopupNotifications.panel.state == "closed"
+ );
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+
+ // Go back to the first tab
+ await BrowserTestUtils.removeTab(tab_two);
+
+ await promiseNotification("webauthn-prompt-presence");
+
+ await TestUtils.waitForCondition(
+ () => PopupNotifications.panel.state == "open"
+ );
+ is(PopupNotifications.panel.state, "open", "Doorhanger is visible");
+
+ // Cancel the request.
+ ok(active, "request should still be active");
+ await triggerMainPopupCommand(PopupNotifications.panel);
+ await request;
+ ok(!active, "request should be stopped");
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab_one);
+}
+
+// Add two tabs, open WebAuthn in the first, switch, assert the prompt is
+// not visible, switch back, assert the prompt is there and cancel it.
+async function test_window_switching() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential and wait for the prompt.
+ let active = true;
+ let request = promiseWebAuthnMakeCredential(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+ await promiseNotification("webauthn-prompt-presence");
+
+ await TestUtils.waitForCondition(
+ () => PopupNotifications.panel.state == "open"
+ );
+ is(PopupNotifications.panel.state, "open", "Doorhanger is visible");
+
+ // Open and switch to a second window
+ let new_window = await BrowserTestUtils.openNewBrowserWindow();
+ await SimpleTest.promiseFocus(new_window);
+
+ await TestUtils.waitForCondition(
+ () => new_window.PopupNotifications.panel.state == "closed"
+ );
+ is(
+ new_window.PopupNotifications.panel.state,
+ "closed",
+ "Doorhanger is hidden"
+ );
+
+ // Go back to the first tab
+ await BrowserTestUtils.closeWindow(new_window);
+ await SimpleTest.promiseFocus(window);
+
+ await TestUtils.waitForCondition(
+ () => PopupNotifications.panel.state == "open"
+ );
+ is(PopupNotifications.panel.state, "open", "Doorhanger is still visible");
+
+ // Cancel the request.
+ ok(active, "request should still be active");
+ await triggerMainPopupCommand(PopupNotifications.panel);
+ await request;
+ ok(!active, "request should be stopped");
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_register_direct_proceed() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential with direct attestation and wait for the prompt.
+ let request = promiseWebAuthnMakeCredential(tab, "direct");
+ await promiseNotification("webauthn-prompt-register-direct");
+
+ // Proceed.
+ PopupNotifications.panel.firstElementChild.button.click();
+
+ // Ensure we got "direct" attestation.
+ await request.then(verifyDirectCertificate);
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_register_direct_proceed_anon() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Request a new credential with direct attestation and wait for the prompt.
+ let request = promiseWebAuthnMakeCredential(tab, "direct");
+ await promiseNotification("webauthn-prompt-register-direct");
+
+ // Check "anonymize anyway" and proceed.
+ PopupNotifications.panel.firstElementChild.checkbox.checked = true;
+ PopupNotifications.panel.firstElementChild.button.click();
+
+ // Ensure we got "none" attestation.
+ await request.then(verifyAnonymizedCertificate);
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_select_sign_result() {
+ // Open a new tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Make two discoverable credentials for the same RP ID so that
+ // the user has to select one to return.
+ let cred1 = await addCredential(gAuthenticatorId, "example.com");
+ let cred2 = await addCredential(gAuthenticatorId, "example.com");
+
+ let active = true;
+ let request = promiseWebAuthnGetAssertionDiscoverable(tab)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+
+ // Ensure the selection prompt is shown
+ await promiseNotification("webauthn-prompt-select-sign-result");
+
+ ok(active, "request is active");
+
+ // Cancel the request
+ PopupNotifications.panel.firstElementChild.button.click();
+ await request;
+
+ await removeCredential(gAuthenticatorId, cred1);
+ await removeCredential(gAuthenticatorId, cred2);
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_fullscreen_show_nav_toolbar() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // Start with the window fullscreen and the nav toolbox hidden
+ let fullscreenState = window.fullScreen;
+
+ let navToolboxHiddenPromise = promiseNavToolboxStatus("hidden");
+
+ window.fullScreen = true;
+ FullScreen.hideNavToolbox(false);
+
+ await navToolboxHiddenPromise;
+
+ // Request a new credential and wait for the direct attestation consent
+ // prompt.
+ let promptPromise = promiseNotification("webauthn-prompt-register-direct");
+ let navToolboxShownPromise = promiseNavToolboxStatus("shown");
+
+ let active = true;
+ let requestPromise = promiseWebAuthnMakeCredential(tab, "direct")
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+
+ await Promise.all([promptPromise, navToolboxShownPromise]);
+
+ ok(active, "request is active");
+ ok(window.fullScreen, "window is fullscreen");
+
+ // Cancel the request.
+ PopupNotifications.panel.firstElementChild.secondaryButton.click();
+ await requestPromise;
+
+ window.fullScreen = fullscreenState;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function test_no_fullscreen_dom() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let fullScreenPaintPromise = promiseFullScreenPaint();
+ // Make a DOM element fullscreen
+ await ContentTask.spawn(tab.linkedBrowser, [], () => {
+ return content.document.body.requestFullscreen();
+ });
+ await fullScreenPaintPromise;
+ ok(!!document.fullscreenElement, "a DOM element is fullscreen");
+
+ // Request a new credential and wait for the direct attestation consent
+ // prompt.
+ let promptPromise = promiseNotification("webauthn-prompt-register-direct");
+ fullScreenPaintPromise = promiseFullScreenPaint();
+
+ let active = true;
+ let requestPromise = promiseWebAuthnMakeCredential(tab, "direct")
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError)
+ .then(() => (active = false));
+
+ await Promise.all([promptPromise, fullScreenPaintPromise]);
+
+ ok(active, "request is active");
+ ok(!document.fullscreenElement, "no DOM element is fullscreen");
+
+ // Cancel the request.
+ await waitForPopupNotificationSecurityDelay();
+ PopupNotifications.panel.firstElementChild.secondaryButton.click();
+ await requestPromise;
+
+ // Close tab.
+ await BrowserTestUtils.removeTab(tab);
+}
diff --git a/dom/webauthn/tests/browser/head.js b/dom/webauthn/tests/browser/head.js
new file mode 100644
index 0000000000..d6cbd56133
--- /dev/null
+++ b/dom/webauthn/tests/browser/head.js
@@ -0,0 +1,259 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let exports = this;
+
+const scripts = [
+ "pkijs/common.js",
+ "pkijs/asn1.js",
+ "pkijs/x509_schema.js",
+ "pkijs/x509_simpl.js",
+ "browser/cbor.js",
+ "browser/u2futil.js",
+];
+
+for (let script of scripts) {
+ Services.scriptloader.loadSubScript(
+ `chrome://mochitests/content/browser/dom/webauthn/tests/${script}`,
+ this
+ );
+}
+
+function add_virtual_authenticator(autoremove = true) {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+ let id = webauthnService.addVirtualAuthenticator(
+ "ctap2_1",
+ "internal",
+ true,
+ true,
+ true,
+ true
+ );
+ if (autoremove) {
+ registerCleanupFunction(() => {
+ webauthnService.removeVirtualAuthenticator(id);
+ });
+ }
+ return id;
+}
+
+async function addCredential(authenticatorId, rpId) {
+ let keyPair = await crypto.subtle.generateKey(
+ {
+ name: "ECDSA",
+ namedCurve: "P-256",
+ },
+ true,
+ ["sign"]
+ );
+
+ let credId = new Uint8Array(32);
+ crypto.getRandomValues(credId);
+ credId = bytesToBase64UrlSafe(credId);
+
+ let privateKey = await crypto.subtle
+ .exportKey("pkcs8", keyPair.privateKey)
+ .then(privateKey => bytesToBase64UrlSafe(privateKey));
+
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+
+ webauthnService.addCredential(
+ authenticatorId,
+ credId,
+ true, // resident key
+ rpId,
+ privateKey,
+ "VGVzdCBVc2Vy", // "Test User"
+ 0 // sign count
+ );
+
+ return credId;
+}
+
+async function removeCredential(authenticatorId, credId) {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+
+ webauthnService.removeCredential(authenticatorId, credId);
+}
+
+function memcmp(x, y) {
+ let xb = new Uint8Array(x);
+ let yb = new Uint8Array(y);
+
+ if (x.byteLength != y.byteLength) {
+ return false;
+ }
+
+ for (let i = 0; i < xb.byteLength; ++i) {
+ if (xb[i] != yb[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+}
+
+function expectError(aType) {
+ let expected = `${aType}Error`;
+ return function (aResult) {
+ is(
+ aResult.slice(0, expected.length),
+ expected,
+ `Expecting a ${aType}Error`
+ );
+ };
+}
+
+/* eslint-disable no-shadow */
+function promiseWebAuthnMakeCredential(
+ tab,
+ attestation = "none",
+ residentKey = "discouraged",
+ extensions = {}
+) {
+ return ContentTask.spawn(
+ tab.linkedBrowser,
+ [attestation, residentKey, extensions],
+ ([attestation, residentKey, extensions]) => {
+ const cose_alg_ECDSA_w_SHA256 = -7;
+
+ let challenge = content.crypto.getRandomValues(new Uint8Array(16));
+
+ let pubKeyCredParams = [
+ {
+ type: "public-key",
+ alg: cose_alg_ECDSA_w_SHA256,
+ },
+ ];
+
+ let publicKey = {
+ rp: { id: content.document.domain, name: "none" },
+ user: {
+ id: new Uint8Array(),
+ name: "none",
+ displayName: "none",
+ },
+ pubKeyCredParams,
+ authenticatorSelection: {
+ authenticatorAttachment: "cross-platform",
+ residentKey,
+ },
+ extensions,
+ attestation,
+ challenge,
+ };
+
+ return content.navigator.credentials
+ .create({ publicKey })
+ .then(credential => {
+ return {
+ clientDataJSON: credential.response.clientDataJSON,
+ attObj: credential.response.attestationObject,
+ rawId: credential.rawId,
+ };
+ });
+ }
+ );
+}
+
+function promiseWebAuthnGetAssertion(tab, key_handle = null, extensions = {}) {
+ return ContentTask.spawn(
+ tab.linkedBrowser,
+ [key_handle, extensions],
+ ([key_handle, extensions]) => {
+ let challenge = content.crypto.getRandomValues(new Uint8Array(16));
+ if (key_handle == null) {
+ key_handle = content.crypto.getRandomValues(new Uint8Array(16));
+ }
+
+ let credential = {
+ id: key_handle,
+ type: "public-key",
+ transports: ["usb"],
+ };
+
+ let publicKey = {
+ challenge,
+ extensions,
+ rpId: content.document.domain,
+ allowCredentials: [credential],
+ };
+
+ return content.navigator.credentials
+ .get({ publicKey })
+ .then(assertion => {
+ return {
+ authenticatorData: assertion.response.authenticatorData,
+ clientDataJSON: assertion.response.clientDataJSON,
+ extensions: assertion.getClientExtensionResults(),
+ signature: assertion.response.signature,
+ };
+ });
+ }
+ );
+}
+
+function promiseWebAuthnGetAssertionDiscoverable(
+ tab,
+ mediation = "optional",
+ extensions = {}
+) {
+ return ContentTask.spawn(
+ tab.linkedBrowser,
+ [extensions, mediation],
+ ([extensions, mediation]) => {
+ let challenge = content.crypto.getRandomValues(new Uint8Array(16));
+
+ let publicKey = {
+ challenge,
+ extensions,
+ rpId: content.document.domain,
+ allowCredentials: [],
+ };
+
+ return content.navigator.credentials.get({ publicKey, mediation });
+ }
+ );
+}
+
+function checkRpIdHash(rpIdHash, hostname) {
+ return crypto.subtle
+ .digest("SHA-256", string2buffer(hostname))
+ .then(calculatedRpIdHash => {
+ let calcHashStr = bytesToBase64UrlSafe(
+ new Uint8Array(calculatedRpIdHash)
+ );
+ let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(rpIdHash));
+
+ if (calcHashStr != providedHashStr) {
+ throw new Error("Calculated RP ID hash doesn't match.");
+ }
+ });
+}
+
+function promiseNotification(id) {
+ return new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("popupshown", function shown() {
+ let notification = PopupNotifications.getNotification(id);
+ if (notification) {
+ ok(true, `${id} prompt visible`);
+ PopupNotifications.panel.removeEventListener("popupshown", shown);
+ resolve();
+ }
+ });
+ });
+}
+/* eslint-enable no-shadow */
diff --git a/dom/webauthn/tests/browser/tab_webauthn_result.html b/dom/webauthn/tests/browser/tab_webauthn_result.html
new file mode 100644
index 0000000000..8e8b9f82cd
--- /dev/null
+++ b/dom/webauthn/tests/browser/tab_webauthn_result.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Generic W3C Web Authentication Test Result Page</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Generic W3C Web Authentication Test Result Page</h1>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1415675">Mozilla Bug 1415675</a>
+<input type="text" id="status" value="init" />
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/cbor.js b/dom/webauthn/tests/cbor.js
new file mode 100644
index 0000000000..3e1f300df3
--- /dev/null
+++ b/dom/webauthn/tests/cbor.js
@@ -0,0 +1,406 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+(function(global, undefined) { "use strict";
+var POW_2_24 = 5.960464477539063e-8,
+ POW_2_32 = 4294967296,
+ POW_2_53 = 9007199254740992;
+
+function encode(value) {
+ var data = new ArrayBuffer(256);
+ var dataView = new DataView(data);
+ var lastLength;
+ var offset = 0;
+
+ function prepareWrite(length) {
+ var newByteLength = data.byteLength;
+ var requiredLength = offset + length;
+ while (newByteLength < requiredLength)
+ newByteLength <<= 1;
+ if (newByteLength !== data.byteLength) {
+ var oldDataView = dataView;
+ data = new ArrayBuffer(newByteLength);
+ dataView = new DataView(data);
+ var uint32count = (offset + 3) >> 2;
+ for (var i = 0; i < uint32count; ++i)
+ dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
+ }
+
+ lastLength = length;
+ return dataView;
+ }
+ function commitWrite() {
+ offset += lastLength;
+ }
+ function writeFloat64(value) {
+ commitWrite(prepareWrite(8).setFloat64(offset, value));
+ }
+ function writeUint8(value) {
+ commitWrite(prepareWrite(1).setUint8(offset, value));
+ }
+ function writeUint8Array(value) {
+ var dataView = prepareWrite(value.length);
+ for (var i = 0; i < value.length; ++i)
+ dataView.setUint8(offset + i, value[i]);
+ commitWrite();
+ }
+ function writeUint16(value) {
+ commitWrite(prepareWrite(2).setUint16(offset, value));
+ }
+ function writeUint32(value) {
+ commitWrite(prepareWrite(4).setUint32(offset, value));
+ }
+ function writeUint64(value) {
+ var low = value % POW_2_32;
+ var high = (value - low) / POW_2_32;
+ var dataView = prepareWrite(8);
+ dataView.setUint32(offset, high);
+ dataView.setUint32(offset + 4, low);
+ commitWrite();
+ }
+ function writeTypeAndLength(type, length) {
+ if (length < 24) {
+ writeUint8(type << 5 | length);
+ } else if (length < 0x100) {
+ writeUint8(type << 5 | 24);
+ writeUint8(length);
+ } else if (length < 0x10000) {
+ writeUint8(type << 5 | 25);
+ writeUint16(length);
+ } else if (length < 0x100000000) {
+ writeUint8(type << 5 | 26);
+ writeUint32(length);
+ } else {
+ writeUint8(type << 5 | 27);
+ writeUint64(length);
+ }
+ }
+
+ function encodeItem(value) {
+ var i;
+
+ if (value === false)
+ return writeUint8(0xf4);
+ if (value === true)
+ return writeUint8(0xf5);
+ if (value === null)
+ return writeUint8(0xf6);
+ if (value === undefined)
+ return writeUint8(0xf7);
+
+ switch (typeof value) {
+ case "number":
+ if (Math.floor(value) === value) {
+ if (0 <= value && value <= POW_2_53)
+ return writeTypeAndLength(0, value);
+ if (-POW_2_53 <= value && value < 0)
+ return writeTypeAndLength(1, -(value + 1));
+ }
+ writeUint8(0xfb);
+ return writeFloat64(value);
+
+ case "string":
+ var utf8data = [];
+ for (i = 0; i < value.length; ++i) {
+ var charCode = value.charCodeAt(i);
+ if (charCode < 0x80) {
+ utf8data.push(charCode);
+ } else if (charCode < 0x800) {
+ utf8data.push(0xc0 | charCode >> 6);
+ utf8data.push(0x80 | charCode & 0x3f);
+ } else if (charCode < 0xd800) {
+ utf8data.push(0xe0 | charCode >> 12);
+ utf8data.push(0x80 | (charCode >> 6) & 0x3f);
+ utf8data.push(0x80 | charCode & 0x3f);
+ } else {
+ charCode = (charCode & 0x3ff) << 10;
+ charCode |= value.charCodeAt(++i) & 0x3ff;
+ charCode += 0x10000;
+
+ utf8data.push(0xf0 | charCode >> 18);
+ utf8data.push(0x80 | (charCode >> 12) & 0x3f);
+ utf8data.push(0x80 | (charCode >> 6) & 0x3f);
+ utf8data.push(0x80 | charCode & 0x3f);
+ }
+ }
+
+ writeTypeAndLength(3, utf8data.length);
+ return writeUint8Array(utf8data);
+
+ default:
+ var length;
+ if (Array.isArray(value)) {
+ length = value.length;
+ writeTypeAndLength(4, length);
+ for (i = 0; i < length; ++i)
+ encodeItem(value[i]);
+ } else if (value instanceof Uint8Array) {
+ writeTypeAndLength(2, value.length);
+ writeUint8Array(value);
+ } else {
+ var keys = Object.keys(value);
+ length = keys.length;
+ writeTypeAndLength(5, length);
+ for (i = 0; i < length; ++i) {
+ var key = keys[i];
+ encodeItem(key);
+ encodeItem(value[key]);
+ }
+ }
+ }
+ }
+
+ encodeItem(value);
+
+ if ("slice" in data)
+ return data.slice(0, offset);
+
+ var ret = new ArrayBuffer(offset);
+ var retView = new DataView(ret);
+ for (var i = 0; i < offset; ++i)
+ retView.setUint8(i, dataView.getUint8(i));
+ return ret;
+}
+
+function decode(data, tagger, simpleValue) {
+ var dataView = new DataView(data);
+ var offset = 0;
+
+ if (typeof tagger !== "function")
+ tagger = function(value) { return value; };
+ if (typeof simpleValue !== "function")
+ simpleValue = function() { return undefined; };
+
+ function commitRead(length, value) {
+ offset += length;
+ return value;
+ }
+ function readArrayBuffer(length) {
+ return commitRead(length, new Uint8Array(data, offset, length));
+ }
+ function readFloat16() {
+ var tempArrayBuffer = new ArrayBuffer(4);
+ var tempDataView = new DataView(tempArrayBuffer);
+ var value = readUint16();
+
+ var sign = value & 0x8000;
+ var exponent = value & 0x7c00;
+ var fraction = value & 0x03ff;
+
+ if (exponent === 0x7c00)
+ exponent = 0xff << 10;
+ else if (exponent !== 0)
+ exponent += (127 - 15) << 10;
+ else if (fraction !== 0)
+ return (sign ? -1 : 1) * fraction * POW_2_24;
+
+ tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
+ return tempDataView.getFloat32(0);
+ }
+ function readFloat32() {
+ return commitRead(4, dataView.getFloat32(offset));
+ }
+ function readFloat64() {
+ return commitRead(8, dataView.getFloat64(offset));
+ }
+ function readUint8() {
+ return commitRead(1, dataView.getUint8(offset));
+ }
+ function readUint16() {
+ return commitRead(2, dataView.getUint16(offset));
+ }
+ function readUint32() {
+ return commitRead(4, dataView.getUint32(offset));
+ }
+ function readUint64() {
+ return readUint32() * POW_2_32 + readUint32();
+ }
+ function readBreak() {
+ if (dataView.getUint8(offset) !== 0xff)
+ return false;
+ offset += 1;
+ return true;
+ }
+ function readLength(additionalInformation) {
+ if (additionalInformation < 24)
+ return additionalInformation;
+ if (additionalInformation === 24)
+ return readUint8();
+ if (additionalInformation === 25)
+ return readUint16();
+ if (additionalInformation === 26)
+ return readUint32();
+ if (additionalInformation === 27)
+ return readUint64();
+ if (additionalInformation === 31)
+ return -1;
+ throw "Invalid length encoding";
+ }
+ function readIndefiniteStringLength(majorType) {
+ var initialByte = readUint8();
+ if (initialByte === 0xff)
+ return -1;
+ var length = readLength(initialByte & 0x1f);
+ if (length < 0 || (initialByte >> 5) !== majorType)
+ throw "Invalid indefinite length element";
+ return length;
+ }
+
+ function appendUtf16Data(utf16data, length) {
+ for (var i = 0; i < length; ++i) {
+ var value = readUint8();
+ if (value & 0x80) {
+ if (value < 0xe0) {
+ value = (value & 0x1f) << 6
+ | (readUint8() & 0x3f);
+ length -= 1;
+ } else if (value < 0xf0) {
+ value = (value & 0x0f) << 12
+ | (readUint8() & 0x3f) << 6
+ | (readUint8() & 0x3f);
+ length -= 2;
+ } else {
+ value = (value & 0x0f) << 18
+ | (readUint8() & 0x3f) << 12
+ | (readUint8() & 0x3f) << 6
+ | (readUint8() & 0x3f);
+ length -= 3;
+ }
+ }
+
+ if (value < 0x10000) {
+ utf16data.push(value);
+ } else {
+ value -= 0x10000;
+ utf16data.push(0xd800 | (value >> 10));
+ utf16data.push(0xdc00 | (value & 0x3ff));
+ }
+ }
+ }
+
+ function decodeItem() {
+ var initialByte = readUint8();
+ var majorType = initialByte >> 5;
+ var additionalInformation = initialByte & 0x1f;
+ var i;
+ var length;
+
+ if (majorType === 7) {
+ switch (additionalInformation) {
+ case 25:
+ return readFloat16();
+ case 26:
+ return readFloat32();
+ case 27:
+ return readFloat64();
+ }
+ }
+
+ length = readLength(additionalInformation);
+ if (length < 0 && (majorType < 2 || 6 < majorType))
+ throw "Invalid length";
+
+ switch (majorType) {
+ case 0:
+ return length;
+ case 1:
+ return -1 - length;
+ case 2:
+ if (length < 0) {
+ var elements = [];
+ var fullArrayLength = 0;
+ while ((length = readIndefiniteStringLength(majorType)) >= 0) {
+ fullArrayLength += length;
+ elements.push(readArrayBuffer(length));
+ }
+ var fullArray = new Uint8Array(fullArrayLength);
+ var fullArrayOffset = 0;
+ for (i = 0; i < elements.length; ++i) {
+ fullArray.set(elements[i], fullArrayOffset);
+ fullArrayOffset += elements[i].length;
+ }
+ return fullArray;
+ }
+ return readArrayBuffer(length);
+ case 3:
+ var utf16data = [];
+ if (length < 0) {
+ while ((length = readIndefiniteStringLength(majorType)) >= 0)
+ appendUtf16Data(utf16data, length);
+ } else
+ appendUtf16Data(utf16data, length);
+ return String.fromCharCode.apply(null, utf16data);
+ case 4:
+ var retArray;
+ if (length < 0) {
+ retArray = [];
+ while (!readBreak())
+ retArray.push(decodeItem());
+ } else {
+ retArray = new Array(length);
+ for (i = 0; i < length; ++i)
+ retArray[i] = decodeItem();
+ }
+ return retArray;
+ case 5:
+ var retObject = {};
+ for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
+ var key = decodeItem();
+ retObject[key] = decodeItem();
+ }
+ return retObject;
+ case 6:
+ return tagger(decodeItem(), length);
+ case 7:
+ switch (length) {
+ case 20:
+ return false;
+ case 21:
+ return true;
+ case 22:
+ return null;
+ case 23:
+ return undefined;
+ default:
+ return simpleValue(length);
+ }
+ }
+ }
+
+ var ret = decodeItem();
+ if (offset !== data.byteLength)
+ throw "Remaining bytes";
+ return ret;
+}
+
+var obj = { encode: encode, decode: decode };
+
+if (typeof define === "function" && define.amd)
+ define("cbor/cbor", obj);
+else if (typeof module !== "undefined" && module.exports)
+ module.exports = obj;
+else if (!global.CBOR)
+ global.CBOR = obj;
+
+})(this);
diff --git a/dom/webauthn/tests/empty.html b/dom/webauthn/tests/empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/webauthn/tests/empty.html
diff --git a/dom/webauthn/tests/get_assertion_dead_object.html b/dom/webauthn/tests/get_assertion_dead_object.html
new file mode 100644
index 0000000000..e7de9d3deb
--- /dev/null
+++ b/dom/webauthn/tests/get_assertion_dead_object.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+</head>
+<body>
+<script type="text/javascript">
+ window.addEventListener('load', function() {
+ let o = [];
+ o[0] = window.navigator;
+ document.writeln('');
+ // Since the USB token is enabled by default, this will pop up a notification that the
+ // user can insert/interact with it. Since this is just a test, this won't happen. The
+ // request will eventually time out.
+ // Unfortunately the minimum timeout is 15 seconds.
+ o[0].credentials.get({ publicKey: { challenge: new Uint8Array(128), timeout: 15000 } });
+ o.forEach((n, i) => o[i] = null);
+ });
+</script>
+</body>
+</html>
diff --git a/dom/webauthn/tests/mochitest.toml b/dom/webauthn/tests/mochitest.toml
new file mode 100644
index 0000000000..2082d39a58
--- /dev/null
+++ b/dom/webauthn/tests/mochitest.toml
@@ -0,0 +1,118 @@
+[DEFAULT]
+support-files = [
+ "cbor.js",
+ "u2futil.js",
+ "pkijs/*",
+ "get_assertion_dead_object.html",
+ "empty.html",
+]
+scheme = "https"
+prefs = [
+ "dom.security.featurePolicy.webidl.enabled=true",
+ "security.webauth.webauthn=true",
+ "security.webauth.webauthn_enable_softtoken=true",
+ "security.webauth.webauthn_enable_usbtoken=false",
+ "security.webauthn.ctap2=true",
+ "security.webauthn.enable_conditional_mediation=true",
+ "security.webauthn.enable_macos_passkeys=false",
+]
+
+["test_webauthn_abort_signal.html"]
+fail-if = ["xorigin"]
+skip-if = [
+ "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.)
+ "os == 'android'", # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android
+]
+
+["test_webauthn_attestation_conveyance.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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_authenticator_selection.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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_authenticator_transports.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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_crossorigin_featurepolicy.html"]
+fail-if = ["xorigin"] # Cross-origin use of WebAuthn requires a feature policy.
+
+["test_webauthn_ctap2_omitted_credential_id.html"]
+fail-if = ["xorigin"] # NotAllowedError
+
+["test_webauthn_get_assertion.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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.)
+ "os == 'android'", # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android
+]
+
+["test_webauthn_get_assertion_dead_object.html"]
+skip-if = [
+ "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_isplatformauthenticatoravailable.html"]
+
+["test_webauthn_loopback.html"]
+skip-if = [
+ "xorigin", # Hangs, JavaScript error: https://example.org/tests/SimpleTest/SimpleTest.js, line 76: DataCloneError: The object could not be cloned.
+ "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_make_credential.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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_no_token.html"]
+skip-if = [
+ "xorigin", # JavaScript error: https://example.org/tests/SimpleTest/SimpleTest.js, line 76: DataCloneError: The object could not be cloned.
+ "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.)
+ "os == 'android'", # Test disables all tokens, which is an unsupported configuration on android.
+]
+
+["test_webauthn_sameorigin.html"]
+fail-if = ["xorigin"] # NotAllowedError
+skip-if = [
+ "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_sameoriginwithancestors.html"]
+skip-if = [
+ "xorigin", # this test has its own cross-origin setup
+ "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 = [
+ "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_webdriver_virtual_authenticator.html"]
+fail-if = ["xorigin"] # Cross-origin use of WebAuthn requires a feature policy.
diff --git a/dom/webauthn/tests/pkijs/LICENSE b/dom/webauthn/tests/pkijs/LICENSE
new file mode 100644
index 0000000000..4f71696a7d
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, GMO GlobalSign
+Copyright (c) 2015, Peculiar Ventures
+All rights reserved.
+
+Author 2014-2015, Yury Strozhevsky
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+ list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/dom/webauthn/tests/pkijs/README b/dom/webauthn/tests/pkijs/README
new file mode 100644
index 0000000000..9213c9d438
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/README
@@ -0,0 +1 @@
+PKIjs and ASN1js are from https://pkijs.org/ and https://asn1js.org/. \ No newline at end of file
diff --git a/dom/webauthn/tests/pkijs/asn1.js b/dom/webauthn/tests/pkijs/asn1.js
new file mode 100644
index 0000000000..ddee052407
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/asn1.js
@@ -0,0 +1,5466 @@
+/*
+ * Copyright (c) 2014, GMO GlobalSign
+ * Copyright (c) 2015, Peculiar Ventures
+ * All rights reserved.
+ *
+ * Author 2014-2015, Yury Strozhevsky <www.strozhevsky.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ */
+(
+function(in_window)
+{
+ //**************************************************************************************
+ // #region Declaration of global variables
+ //**************************************************************************************
+ // #region "org" namespace
+ if(typeof in_window.org === "undefined")
+ in_window.org = {};
+ else
+ {
+ if(typeof in_window.org !== "object")
+ throw new Error("Name org already exists and it's not an object");
+ }
+ // #endregion
+
+ // #region "org.pkijs" namespace
+ if(typeof in_window.org.pkijs === "undefined")
+ in_window.org.pkijs = {};
+ else
+ {
+ if(typeof in_window.org.pkijs !== "object")
+ throw new Error("Name org.pkijs already exists and it's not an object" + " but " + (typeof in_window.org.pkijs));
+ }
+ // #endregion
+
+ // #region "org.pkijs.asn1" namespace
+ if(typeof in_window.org.pkijs.asn1 === "undefined")
+ in_window.org.pkijs.asn1 = {};
+ else
+ {
+ if(typeof in_window.org.pkijs.asn1 !== "object")
+ throw new Error("Name org.pkijs.asn1 already exists and it's not an object" + " but " + (typeof in_window.org.pkijs.asn1));
+ }
+ // #endregion
+
+ // #region "local" namespace
+ var local = {};
+ // #endregion
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Aux-functions
+ //**************************************************************************************
+ function util_frombase(input_buffer, input_base)
+ {
+ /// <summary>Convert number from 2^base to 2^10</summary>
+ /// <param name="input_buffer" type="Uint8Array">Array of bytes representing the number to convert</param>
+ /// <param name="input_base" type="Number">The base of initial number</param>
+
+ var result = 0;
+
+ for(var i = (input_buffer.length - 1); i >= 0; i-- )
+ result += input_buffer[(input_buffer.length - 1) - i] * Math.pow(2, input_base * i);
+
+ return result;
+ }
+ //**************************************************************************************
+ function util_tobase(value, base, reserved)
+ {
+ /// <summary>Convert number from 2^10 to 2^base</summary>
+ /// <param name="value" type="Number">The number to convert</param>
+ /// <param name="base" type="Number">The base for 2^base</param>
+ /// <param name="reserved" type="Number">Pre-defined number of bytes in output array (-1 = limited by function itself)</param>
+
+ reserved = reserved || (-1);
+
+ var result = 0;
+ var biggest = Math.pow(2, base);
+
+ for(var i = 1; i < 8; i++)
+ {
+ if(value < biggest)
+ {
+ var ret_buf;
+
+ if( reserved < 0 )
+ {
+ ret_buf = new ArrayBuffer(i);
+ result = i;
+ }
+ else
+ {
+ if(reserved < i)
+ return (new ArrayBuffer(0));
+
+ ret_buf = new ArrayBuffer(reserved);
+
+ result = reserved;
+ }
+
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var j = ( i - 1 ); j >= 0; j-- )
+ {
+ var basis = Math.pow(2, j * base);
+
+ ret_view[ result - j - 1 ] = Math.floor( value / basis );
+ value -= ( ret_view[ result - j - 1 ] ) * basis;
+ }
+
+ return ret_buf;
+ }
+
+ biggest *= Math.pow(2, base);
+ }
+ }
+ //**************************************************************************************
+ function util_encode_tc(value)
+ {
+ /// <summary>Encode integer value to "two complement" format</summary>
+ /// <param name="value" type="Number">Value to encode</param>
+
+ var mod_value = (value < 0) ? (value * (-1)) : value;
+ var big_int = 128;
+
+ for(var i = 1; i < 8; i++)
+ {
+ if( mod_value <= big_int )
+ {
+ if( value < 0 )
+ {
+ var small_int = big_int - mod_value;
+
+ var ret_buf = util_tobase( small_int, 8, i );
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[ 0 ] |= 0x80;
+
+ return ret_buf;
+ }
+ else
+ {
+ var ret_buf = util_tobase( mod_value, 8, i );
+ var ret_view = new Uint8Array(ret_buf);
+
+ if( ret_view[ 0 ] & 0x80 )
+ {
+ var temp_buf = util_copybuf(ret_buf);
+ var temp_view = new Uint8Array(temp_buf);
+
+ ret_buf = new ArrayBuffer( ret_buf.byteLength + 1 );
+ ret_view = new Uint8Array(ret_buf);
+
+ for(var k = 0; k < temp_buf.byteLength; k++)
+ ret_view[k + 1] = temp_view[k];
+
+ ret_view[0] = 0x00;
+ }
+
+ return ret_buf;
+ }
+ }
+
+ big_int *= Math.pow(2, 8);
+ }
+
+ return (new ArrayBuffer(0));
+ }
+ //**************************************************************************************
+ function util_decode_tc()
+ {
+ /// <summary>Decoding of "two complement" values</summary>
+ /// <remarks>The function must be called in scope of instance of "hex_block" class ("value_hex" and "warnings" properties must be present)</remarks>
+
+ var buf = new Uint8Array(this.value_hex);
+
+ if(this.value_hex.byteLength >= 2)
+ {
+ var condition_1 = (buf[0] == 0xFF) && (buf[1] & 0x80);
+ var condition_2 = (buf[0] == 0x00) && ((buf[1] & 0x80) == 0x00);
+
+ if(condition_1 || condition_2)
+ this.warnings.push("Needlessly long format");
+ }
+
+ // #region Create big part of the integer
+ var big_int_buffer = new ArrayBuffer(this.value_hex.byteLength);
+ var big_int_view = new Uint8Array(big_int_buffer);
+ for(var i = 0; i < this.value_hex.byteLength; i++)
+ big_int_view[i] = 0;
+
+ big_int_view[0] = (buf[0] & 0x80); // mask only the biggest bit
+
+ var big_int = util_frombase(big_int_view, 8);
+ // #endregion
+
+ // #region Create small part of the integer
+ var small_int_buffer = new ArrayBuffer(this.value_hex.byteLength);
+ var small_int_view = new Uint8Array(small_int_buffer);
+ for(var j = 0; j < this.value_hex.byteLength; j++)
+ small_int_view[j] = buf[j];
+
+ small_int_view[0] &= 0x7F; // mask biggest bit
+
+ var small_int = util_frombase(small_int_view, 8);
+ // #endregion
+
+ return (small_int - big_int);
+ }
+ //**************************************************************************************
+ function util_copybuf(input_buffer)
+ {
+ /// <summary>Creating a copy of input ArrayBuffer</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ArrayBuffer for coping</param>
+
+ if(check_buffer_params(input_buffer, 0, input_buffer.byteLength) === false)
+ return (new ArrayBuffer(0));
+
+ var input_view = new Uint8Array(input_buffer);
+
+ var ret_buf = new ArrayBuffer(input_buffer.byteLength);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < input_buffer.byteLength; i++)
+ ret_view[i] = input_view[i];
+
+ return ret_buf;
+ }
+ //**************************************************************************************
+ function util_copybuf_offset(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Creating a copy of input ArrayBuffer</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ArrayBuffer for coping</param>
+
+ if(check_buffer_params(input_buffer, input_offset, input_length) === false)
+ return (new ArrayBuffer(0));
+
+ var input_view = new Uint8Array(input_buffer, input_offset, input_length);
+
+ var ret_buf = new ArrayBuffer(input_length);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < input_length; i++)
+ ret_view[i] = input_view[i];
+
+ return ret_buf;
+ }
+ //**************************************************************************************
+ function util_concatbuf(input_buf1, input_buf2)
+ {
+ /// <summary>Concatenate two ArrayBuffers</summary>
+ /// <param name="input_buf1" type="ArrayBuffer">First ArrayBuffer (first part of concatenated array)</param>
+ /// <param name="input_buf2" type="ArrayBuffer">Second ArrayBuffer (second part of concatenated array)</param>
+
+ var input_view1 = new Uint8Array(input_buf1);
+ var input_view2 = new Uint8Array(input_buf2);
+
+ var ret_buf = new ArrayBuffer(input_buf1.byteLength + input_buf2.byteLength);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < input_buf1.byteLength; i++)
+ ret_view[i] = input_view1[i];
+
+ for(var j = 0; j < input_buf2.byteLength; j++)
+ ret_view[input_buf1.byteLength + j] = input_view2[j];
+
+ return ret_buf;
+ }
+ //**************************************************************************************
+ function check_buffer_params(input_buffer, input_offset, input_length)
+ {
+ if((input_buffer instanceof ArrayBuffer) === false)
+ {
+ this.error = "Wrong parameter: input_buffer must be \"ArrayBuffer\"";
+ return false;
+ }
+
+ if(input_buffer.byteLength === 0)
+ {
+ this.error = "Wrong parameter: input_buffer has zero length";
+ return false;
+ }
+
+ if(input_offset < 0)
+ {
+ this.error = "Wrong parameter: input_offset less than zero";
+ return false;
+ }
+
+ if(input_length < 0)
+ {
+ this.error = "Wrong parameter: input_length less than zero";
+ return false;
+ }
+
+ if((input_buffer.byteLength - input_offset - input_length) < 0)
+ {
+ this.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
+ return false;
+ }
+
+ return true;
+ }
+ //**************************************************************************************
+ function to_hex_codes(input_buffer, input_offset, input_lenght)
+ {
+ if(check_buffer_params(input_buffer, input_offset, input_lenght) === false)
+ return "";
+
+ var result = "";
+
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_lenght);
+
+ for(var i = 0; i < int_buffer.length; i++)
+ {
+ var str = int_buffer[i].toString(16).toUpperCase();
+ result = result + ((str.length === 1) ? " 0" : " ") + str;
+ }
+
+ return result;
+ }
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of base block class
+ //**************************************************************************************
+ local.base_block =
+ function()
+ {
+ /// <summary>General class of all ASN.1 blocks</summary>
+
+ if(arguments[0] instanceof Object)
+ {
+ this.block_length = in_window.org.pkijs.getValue(arguments[0], "block_length", 0);
+ this.error = in_window.org.pkijs.getValue(arguments[0], "error", new String());
+ this.warnings = in_window.org.pkijs.getValue(arguments[0], "warnings", new Array());
+ if("value_before_decode" in arguments[0])
+ this.value_before_decode = util_copybuf(arguments[0].value_before_decode);
+ else
+ this.value_before_decode = new ArrayBuffer(0);
+ }
+ else
+ {
+ this.block_length = 0;
+ this.error = new String();
+ this.warnings = new Array();
+ /// <field>Copy of the value of incoming ArrayBuffer done before decoding</field>
+ this.value_before_decode = new ArrayBuffer(0);
+ }
+ };
+ //**************************************************************************************
+ local.base_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "base_block";
+ };
+ //**************************************************************************************
+ local.base_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ return {
+ block_name: local.base_block.prototype.block_name.call(this),
+ block_length: this.block_length,
+ error: this.error,
+ warnings: this.warnings,
+ value_before_decode: in_window.org.pkijs.bufferToHexCodes(this.value_before_decode, 0, this.value_before_decode.byteLength)
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of hex block class
+ //**************************************************************************************
+ local.hex_block =
+ function()
+ {
+ /// <summary>Descendant of "base_block" with internal ArrayBuffer. Need to have it in case it is not possible to store ASN.1 value in native formats</summary>
+
+ local.base_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", false);
+ if("value_hex" in arguments[0])
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+ else
+ this.value_hex = new ArrayBuffer(0);
+ }
+ else
+ {
+ this.is_hex_only = false;
+ this.value_hex = new ArrayBuffer(0);
+ }
+ };
+ //**************************************************************************************
+ local.hex_block.prototype = new local.base_block();
+ local.hex_block.constructor = local.hex_block;
+ //**************************************************************************************
+ local.hex_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "hex_block";
+ };
+ //**************************************************************************************
+ local.hex_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.warnings.push("Zero buffer length");
+ return input_offset;
+ }
+ // #endregion
+
+ // #region Copy input buffer to internal buffer
+ this.value_hex = new ArrayBuffer(input_length);
+ var view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < int_buffer.length; i++)
+ view[i] = int_buffer[i];
+ // #endregion
+
+ this.block_length = input_length;
+
+ return (input_offset + input_length);
+ };
+ //**************************************************************************************
+ local.hex_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.is_hex_only !== true)
+ {
+ this.error = "Flag \"is_hex_only\" is not set, abort";
+ return (new ArrayBuffer(0));
+ }
+
+ var ret_buf = new ArrayBuffer(this.value_hex.byteLength);
+
+ if(size_only === true)
+ return ret_buf;
+
+ var ret_view = new Uint8Array(ret_buf);
+ var cur_view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < cur_view.length; i++)
+ ret_view[i] = cur_view[i];
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ local.hex_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.base_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.hex_block.prototype.block_name.call(this);
+ _object.is_hex_only = this.is_hex_only;
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of identification block class
+ //**************************************************************************************
+ local.identification_block =
+ function()
+ {
+ /// <summary>Base class of ASN.1 "identification block"</summary>
+
+ local.hex_block.call(this, arguments[0]);
+
+ this.tag_class = (-1);
+ this.tag_number = (-1);
+ this.is_constructed = false;
+
+ if(arguments[0] instanceof Object)
+ {
+ if("id_block" in arguments[0])
+ {
+ // #region Properties from hex_block class
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0].id_block, "is_hex_only", false);
+ this.value_hex = in_window.org.pkijs.getValue(arguments[0].id_block, "value_hex", new ArrayBuffer(0));
+ // #endregion
+
+ this.tag_class = in_window.org.pkijs.getValue(arguments[0].id_block, "tag_class", (-1));
+ this.tag_number = in_window.org.pkijs.getValue(arguments[0].id_block, "tag_number", (-1));
+ this.is_constructed = in_window.org.pkijs.getValue(arguments[0].id_block, "is_constructed", false);
+ }
+ }
+ };
+ //**************************************************************************************
+ local.identification_block.prototype = new local.hex_block();
+ local.identification_block.constructor = local.identification_block;
+ //**************************************************************************************
+ local.identification_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "identification_block";
+ };
+ //**************************************************************************************
+ local.identification_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ var first_octet = 0;
+
+ switch(this.tag_class)
+ {
+ case 1:
+ first_octet |= 0x00; // UNIVERSAL
+ break;
+ case 2:
+ first_octet |= 0x40; // APPLICATION
+ break;
+ case 3:
+ first_octet |= 0x80; // CONTEXT-SPECIFIC
+ break;
+ case 4:
+ first_octet |= 0xC0; // PRIVATE
+ break;
+ default:
+ this.error = "Unknown tag class";
+ return (new ArrayBuffer(0));
+ }
+
+ if(this.is_constructed)
+ first_octet |= 0x20;
+
+ if((this.tag_number < 31) && (!this.is_hex_only))
+ {
+ var ret_buf = new ArrayBuffer(1);
+ var ret_view = new Uint8Array(ret_buf);
+
+ if(!size_only)
+ {
+ var number = this.tag_number;
+ number &= 0x1F;
+ first_octet |= number;
+
+ ret_view[0] = first_octet;
+ }
+
+ return ret_buf;
+ }
+ else
+ {
+ if(this.is_hex_only === false)
+ {
+ var encoded_buf = util_tobase(this.tag_number, 7);
+ var encoded_view = new Uint8Array(encoded_buf);
+ var size = encoded_buf.byteLength;
+
+ var ret_buf = new ArrayBuffer(size + 1);
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[0] = (first_octet | 0x1F);
+
+ if(!size_only)
+ {
+ for(var i = 0; i < (size - 1) ; i++)
+ ret_view[i + 1] = encoded_view[i] | 0x80;
+
+ ret_view[size] = encoded_view[size - 1];
+ }
+
+ return ret_buf;
+ }
+ else
+ {
+ var ret_buf = new ArrayBuffer(this.value_hex.byteLength + 1);
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[0] = (first_octet | 0x1F);
+
+ if(size_only === false)
+ {
+ var cur_view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < (cur_view.length - 1); i++)
+ ret_view[i + 1] = cur_view[i] | 0x80;
+
+ ret_view[this.value_hex.byteLength] = cur_view[cur_view.length - 1];
+ }
+
+ return ret_buf;
+ }
+ }
+ };
+ //**************************************************************************************
+ local.identification_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.error = "Zero buffer length";
+ return (-1);
+ }
+ // #endregion
+
+ // #region Find tag class
+ var tag_class_mask = int_buffer[0] & 0xC0;
+
+ switch(tag_class_mask)
+ {
+ case 0x00:
+ this.tag_class = (1); // UNIVERSAL
+ break;
+ case 0x40:
+ this.tag_class = (2); // APPLICATION
+ break;
+ case 0x80:
+ this.tag_class = (3); // CONTEXT-SPECIFIC
+ break;
+ case 0xC0:
+ this.tag_class = (4); // PRIVATE
+ break;
+ default:
+ this.error = "Unknown tag class";
+ return ( -1 );
+ }
+ // #endregion
+
+ // #region Find it's constructed or not
+ this.is_constructed = (int_buffer[0] & 0x20) == 0x20;
+ // #endregion
+
+ // #region Find tag number
+ this.is_hex_only = false;
+
+ var tag_number_mask = int_buffer[0] & 0x1F;
+
+ // #region Simple case (tag number < 31)
+ if(tag_number_mask != 0x1F)
+ {
+ this.tag_number = (tag_number_mask);
+ this.block_length = 1;
+ }
+ // #endregion
+ // #region Tag number bigger or equal to 31
+ else
+ {
+ var count = 1;
+
+ this.value_hex = new ArrayBuffer(255);
+ var tag_number_buffer_max_length = 255;
+ var int_tag_number_buffer = new Uint8Array(this.value_hex);
+
+ while(int_buffer[count] & 0x80)
+ {
+ int_tag_number_buffer[count - 1] = int_buffer[count] & 0x7F;
+ count++;
+
+ if(count >= int_buffer.length)
+ {
+ this.error = "End of input reached before message was fully decoded";
+ return (-1);
+ }
+
+ // #region In case if tag number length is greater than 255 bytes (rare but possible case)
+ if(count == tag_number_buffer_max_length)
+ {
+ tag_number_buffer_max_length += 255;
+
+ var temp_buffer = new ArrayBuffer(tag_number_buffer_max_length);
+ var temp_buffer_view = new Uint8Array(temp_buffer);
+
+ for(var i = 0; i < int_tag_number_buffer.length; i++)
+ temp_buffer_view[i] = int_tag_number_buffer[i];
+
+ this.value_hex = new ArrayBuffer(tag_number_buffer_max_length);
+ int_tag_number_buffer = new Uint8Array(this.value_hex);
+ }
+ // #endregion
+ }
+
+ this.block_length = (count + 1);
+ int_tag_number_buffer[count - 1] = int_buffer[count] & 0x7F; // Write last byte to buffer
+
+ // #region Cut buffer
+ var temp_buffer = new ArrayBuffer(count);
+ var temp_buffer_view = new Uint8Array(temp_buffer);
+ for(var i = 0; i < count; i++)
+ temp_buffer_view[i] = int_tag_number_buffer[i];
+
+ this.value_hex = new ArrayBuffer(count);
+ int_tag_number_buffer = new Uint8Array(this.value_hex);
+ int_tag_number_buffer.set(temp_buffer_view);
+ // #endregion
+
+ // #region Try to convert long tag number to short form
+ if(this.block_length <= 9)
+ this.tag_number = util_frombase(int_tag_number_buffer, 7);
+ else
+ {
+ this.is_hex_only = true;
+ this.warnings.push("Tag too long, represented as hex-coded");
+ }
+ // #endregion
+ }
+ // #endregion
+ // #endregion
+
+ // #region Check if constructed encoding was using for primitive type
+ if(((this.tag_class == 1)) &&
+ (this.is_constructed))
+ {
+ switch(this.tag_number)
+ {
+ case 1: // BOOLEAN
+ case 2: // REAL
+ case 5: // NULL
+ case 6: // OBJECT IDENTIFIER
+ case 9: // REAL
+ case 14: // TIME
+ case 23:
+ case 24:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ this.error = "Constructed encoding used for primitive type";
+ return (-1);
+ default:
+ ;
+ }
+ }
+ // #endregion
+
+ return ( input_offset + this.block_length ); // Return current offset in input buffer
+ };
+ //**************************************************************************************
+ local.identification_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.identification_block.prototype.block_name.call(this);
+ _object.tag_class = this.tag_class;
+ _object.tag_number = this.tag_number;
+ _object.is_constructed = this.is_constructed;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of length block class
+ //**************************************************************************************
+ local.length_block =
+ function()
+ {
+ /// <summary>Base class of ASN.1 "length block"</summary>
+
+ local.base_block.call(this, arguments[0]);
+
+ this.is_indefinite_form = false;
+ this.long_form_used = false;
+ this.length = (0);
+
+ if(arguments[0] instanceof Object)
+ {
+ if("len_block" in arguments[0])
+ {
+ this.is_indefinite_form = in_window.org.pkijs.getValue(arguments[0].len_block, "is_indefinite_form", false);
+ this.long_form_used = in_window.org.pkijs.getValue(arguments[0].len_block, "long_form_used", false);
+ this.length = in_window.org.pkijs.getValue(arguments[0].len_block, "length", 0);
+ }
+ }
+ };
+ //**************************************************************************************
+ local.length_block.prototype = new local.base_block();
+ local.length_block.constructor = local.length_block;
+ //**************************************************************************************
+ local.length_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "length_block";
+ };
+ //**************************************************************************************
+ local.length_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.error = "Zero buffer length";
+ return (-1);
+ }
+
+ if(int_buffer[0] == 0xFF)
+ {
+ this.error = "Length block 0xFF is reserved by standard";
+ return (-1);
+ }
+ // #endregion
+
+ // #region Check for length form type
+ this.is_indefinite_form = int_buffer[0] == 0x80;
+ // #endregion
+
+ // #region Stop working in case of indefinite length form
+ if(this.is_indefinite_form == true)
+ {
+ this.block_length = 1;
+ return (input_offset + this.block_length);
+ }
+ // #endregion
+
+ // #region Check is long form of length encoding using
+ this.long_form_used = !!(int_buffer[0] & 0x80);
+ // #endregion
+
+ // #region Stop working in case of short form of length value
+ if(this.long_form_used == false)
+ {
+ this.length = (int_buffer[0]);
+ this.block_length = 1;
+ return (input_offset + this.block_length);
+ }
+ // #endregion
+
+ // #region Calculate length value in case of long form
+ var count = int_buffer[0] & 0x7F;
+
+ if(count > 8) // Too big length value
+ {
+ this.error = "Too big integer";
+ return (-1);
+ }
+
+ if((count + 1) > int_buffer.length)
+ {
+ this.error = "End of input reached before message was fully decoded";
+ return (-1);
+ }
+
+ var length_buffer_view = new Uint8Array(count);
+
+ for(var i = 0; i < count; i++)
+ length_buffer_view[i] = int_buffer[i + 1];
+
+ if(length_buffer_view[count - 1] == 0x00)
+ this.warnings.push("Needlessly long encoded length");
+
+ this.length = util_frombase(length_buffer_view, 8);
+
+ if(this.long_form_used && (this.length <= 127))
+ this.warnings.push("Unneccesary usage of long length form");
+
+ this.block_length = count + 1;
+ // #endregion
+
+ return (input_offset + this.block_length); // Return current offset in input buffer
+ };
+ //**************************************************************************************
+ local.length_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.length > 127)
+ this.long_form_used = true;
+
+ if(this.is_indefinite_form)
+ {
+ var ret_buf = new ArrayBuffer(1);
+
+ if(size_only === false)
+ {
+ var ret_view = new Uint8Array(ret_buf);
+ ret_view[0] = 0x80;
+ }
+
+ return ret_buf;
+ }
+
+ if(this.long_form_used === true)
+ {
+ var encoded_buf = util_tobase(this.length, 8);
+
+ if(encoded_buf.byteLength > 127)
+ {
+ this.error = "Too big length";
+ return (new ArrayBuffer(0));
+ }
+
+ var ret_buf = new ArrayBuffer(encoded_buf.byteLength + 1);
+
+ if(size_only === true)
+ return ret_buf;
+
+ var encoded_view = new Uint8Array(encoded_buf);
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[0] = encoded_buf.byteLength | 0x80;
+
+ for(var i = 0; i < encoded_buf.byteLength; i++)
+ ret_view[i + 1] = encoded_view[i];
+
+ return ret_buf;
+ }
+ else
+ {
+ var ret_buf = new ArrayBuffer(1);
+
+ if(size_only === false)
+ {
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[0] = this.length;
+ }
+
+ return ret_buf;
+ }
+
+ return (new ArrayBuffer(0));
+ };
+ //**************************************************************************************
+ local.length_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.base_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.length_block.prototype.block_name.call(this);
+ _object.is_indefinite_form = this.is_indefinite_form;
+ _object.long_form_used = this.long_form_used;
+ _object.length = this.length;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of value block class
+ //**************************************************************************************
+ local.value_block =
+ function()
+ {
+ /// <summary>Generic class of ASN.1 "value block"</summary>
+ local.base_block.call(this, arguments[0]);
+ };
+ //**************************************************************************************
+ local.value_block.prototype = new local.base_block();
+ local.value_block.constructor = local.value_block;
+ //**************************************************************************************
+ local.value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "value_block";
+ };
+ //**************************************************************************************
+ local.value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.base_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.value_block.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of basic ASN.1 block class
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block =
+ function()
+ {
+ /// <summary>Base class of ASN.1 block (identification block + length block + value block)</summary>
+
+ local.base_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.name = in_window.org.pkijs.getValue(arguments[0], "name", "");
+ this.optional = in_window.org.pkijs.getValue(arguments[0], "optional", false);
+
+ if("primitive_schema" in arguments[0])
+ this.primitive_schema = arguments[0].primitive_schema;
+ }
+
+ this.id_block = new local.identification_block(arguments[0]);
+ this.len_block = new local.length_block(arguments[0]);
+ this.value_block = new local.value_block(arguments[0]);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block.prototype = new local.base_block();
+ in_window.org.pkijs.asn1.ASN1_block.constructor = in_window.org.pkijs.asn1.ASN1_block;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "ASN1_block";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ var ret_buf;
+
+ var id_block_buf = this.id_block.toBER(size_only);
+ var value_block_size_buf = this.value_block.toBER(true);
+
+ this.len_block.length = value_block_size_buf.byteLength;
+ var len_block_buf = this.len_block.toBER(size_only);
+
+ ret_buf = util_concatbuf(id_block_buf, len_block_buf);
+
+ var value_block_buf;
+
+ if(size_only === false)
+ value_block_buf = this.value_block.toBER(size_only);
+ else
+ value_block_buf = new ArrayBuffer(this.len_block.length);
+
+ ret_buf = util_concatbuf(ret_buf, value_block_buf);
+
+ if(this.len_block.is_indefinite_form === true)
+ {
+ var indef_buf = new ArrayBuffer(2);
+
+ if(size_only === false)
+ {
+ var indef_view = new Uint8Array(indef_buf);
+
+ indef_view[0] = 0x00;
+ indef_view[1] = 0x00;
+ }
+
+ ret_buf = util_concatbuf(ret_buf, indef_buf);
+ }
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.base_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.ASN1_block.prototype.block_name.call(this);
+ _object.id_block = this.id_block.toJSON();
+ _object.len_block = this.len_block.toJSON();
+ _object.value_block = this.value_block.toJSON();
+
+ if("name" in this)
+ _object.name = this.name;
+ if("optional" in this)
+ _object.optional = this.optional;
+ if("primitive_schema" in this)
+ _object.primitive_schema = this.primitive_schema.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of basic block for all PRIMITIVE types
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block =
+ function()
+ {
+ /// <summary>Base class of ASN.1 value block for primitive values (non-constructive encoding)</summary>
+
+ local.value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ // #region Variables from "hex_block" class
+ if("value_hex" in arguments[0])
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+ else
+ this.value_hex = new ArrayBuffer(0);
+
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", true);
+ // #endregion
+ }
+ else
+ {
+ // #region Variables from "hex_block" class
+ this.value_hex = new ArrayBuffer(0);
+ this.is_hex_only = true;
+ // #endregion
+ }
+ };
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block.prototype = new local.value_block();
+ local.ASN1_PRIMITIVE_value_block.constructor = local.ASN1_PRIMITIVE_value_block;
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.warnings.push("Zero buffer length");
+ return input_offset;
+ }
+ // #endregion
+
+ // #region Copy input buffer into internal buffer
+ this.value_hex = new ArrayBuffer(int_buffer.length);
+ var value_hex_view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < int_buffer.length; i++)
+ value_hex_view[i] = int_buffer[i];
+ // #endregion
+
+ this.block_length = input_length;
+
+ return (input_offset + input_length);
+ };
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ return util_copybuf(this.value_hex);
+ };
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "ASN1_PRIMITIVE_value_block";
+ };
+ //**************************************************************************************
+ local.ASN1_PRIMITIVE_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.ASN1_PRIMITIVE_value_block.prototype.block_name.call(this);
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+ _object.is_hex_only = this.is_hex_only;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_PRIMITIVE =
+ function()
+ {
+ /// <summary>Base class of ASN.1 block for primitive values (non-constructive encoding)</summary>
+
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.id_block.is_constructed = false;
+ this.value_block = new local.ASN1_PRIMITIVE_value_block(arguments[0]);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_PRIMITIVE.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.ASN1_PRIMITIVE.constructor = in_window.org.pkijs.asn1.ASN1_PRIMITIVE;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_PRIMITIVE.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "PRIMITIVE";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_PRIMITIVE.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.ASN1_PRIMITIVE.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of basic block for all CONSTRUCTED types
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block =
+ function()
+ {
+ /// <summary>Base class of ASN.1 value block for constructive values (constructive encoding)</summary>
+
+ local.value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.value = in_window.org.pkijs.getValue(arguments[0], "value", new Array());
+ this.is_indefinite_form = in_window.org.pkijs.getValue(arguments[0], "is_indefinite_form", false);
+ }
+ else
+ {
+ this.value = new Array();
+ this.is_indefinite_form = false;
+ }
+ };
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block.prototype = new local.value_block();
+ local.ASN1_CONSTRUCTED_value_block.constructor = local.ASN1_CONSTRUCTED_value_block;
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Store initial offset and length
+ var initial_offset = input_offset;
+ var initial_length = input_length;
+ // #endregion
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.warnings.push("Zero buffer length");
+ return input_offset;
+ }
+ // #endregion
+
+ // #region Aux function
+ function check_len(_indefinite_length, _length)
+ {
+ if(_indefinite_length == true)
+ return 1;
+
+ return _length;
+ }
+ // #endregion
+
+ var current_offset = input_offset;
+
+ while(check_len(this.is_indefinite_form, input_length) > 0)
+ {
+ var return_object = fromBER_raw(input_buffer, current_offset, input_length);
+ if(return_object.offset == (-1))
+ {
+ this.error = return_object.result.error;
+ this.warnings.concat(return_object.result.warnings);
+ return (-1);
+ }
+
+ current_offset = return_object.offset;
+
+ this.block_length += return_object.result.block_length;
+ input_length -= return_object.result.block_length;
+
+ this.value.push(return_object.result);
+
+ if((this.is_indefinite_form == true) && (return_object.result.block_name() == in_window.org.pkijs.asn1.EOC.prototype.block_name()))
+ break;
+ }
+
+ if(this.is_indefinite_form == true)
+ {
+ if(this.value[this.value.length - 1].block_name() == in_window.org.pkijs.asn1.EOC.prototype.block_name())
+ this.value.pop();
+ else
+ this.warnings.push("No EOC block encoded");
+ }
+
+ // #region Copy "input_buffer" to "value_before_decode"
+ this.value_before_decode = util_copybuf_offset(input_buffer, initial_offset, initial_length);
+ // #endregion
+
+ return current_offset;
+ };
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ var ret_buf = new ArrayBuffer(0);
+
+ for(var i = 0; i < this.value.length; i++)
+ {
+ var value_buf = this.value[i].toBER(size_only);
+ ret_buf = util_concatbuf(ret_buf, value_buf);
+ }
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "ASN1_CONSTRUCTED_value_block";
+ };
+ //**************************************************************************************
+ local.ASN1_CONSTRUCTED_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.ASN1_CONSTRUCTED_value_block.prototype.block_name.call(this);
+ _object.is_indefinite_form = this.is_indefinite_form;
+ _object.value = new Array();
+ for(var i = 0; i < this.value.length; i++)
+ _object.value.push(this.value[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED =
+ function()
+ {
+ /// <summary>Base class of ASN.1 block for constructive values (constructive encoding)</summary>
+
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.id_block.is_constructed = true;
+ this.value_block = new local.ASN1_CONSTRUCTED_value_block(arguments[0]);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.constructor = in_window.org.pkijs.asn1.ASN1_CONSTRUCTED;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "CONSTRUCTED";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ this.value_block.is_indefinite_form = this.len_block.is_indefinite_form;
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 EOC type class
+ //**************************************************************************************
+ local.EOC_value_block =
+ function()
+ {
+ local.value_block.call(this, arguments[0]);
+ };
+ //**************************************************************************************
+ local.EOC_value_block.prototype = new local.value_block();
+ local.EOC_value_block.constructor = local.EOC_value_block;
+ //**************************************************************************************
+ local.EOC_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region There is no "value block" for EOC type and we need to return the same offset
+ return input_offset;
+ // #endregion
+ };
+ //**************************************************************************************
+ local.EOC_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ return (new ArrayBuffer(0));
+ };
+ //**************************************************************************************
+ local.EOC_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "EOC_value_block";
+ };
+ //**************************************************************************************
+ local.EOC_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.EOC_value_block.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.EOC =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.EOC_value_block();
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 0; // EOC
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.EOC.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.EOC.constructor = local.EOC_value_block;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.EOC.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "END_OF_CONTENT";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.EOC.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.EOC.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 BOOLEAN type class
+ //**************************************************************************************
+ local.BOOLEAN_value_block =
+ function()
+ {
+ local.value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.value = in_window.org.pkijs.getValue(arguments[0], "value", false);
+
+ // #region Variables from hex_block class
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", false);
+ if("value_hex" in arguments[0])
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+ else
+ {
+ this.value_hex = new ArrayBuffer(1);
+ if(this.value === true)
+ {
+ var view = new Uint8Array(this.value_hex);
+ view[0] = 0xFF;
+ }
+ }
+ // #endregion
+ }
+ else
+ {
+ this.value = false;
+
+ // #region Variables from hex_block class
+ this.is_hex_only = false;
+ this.value_hex = new ArrayBuffer(1);
+ // #endregion
+ }
+ };
+ //**************************************************************************************
+ local.BOOLEAN_value_block.prototype = new local.value_block();
+ local.BOOLEAN_value_block.constructor = local.BOOLEAN_value_block;
+ //**************************************************************************************
+ local.BOOLEAN_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ if(input_length > 1)
+ this.warnings.push("BOOLEAN value encoded in more then 1 octet");
+
+ this.value = int_buffer[0] != 0x00;
+
+ this.is_hex_only = true;
+
+ // #region Copy input buffer to internal array
+ this.value_hex = new ArrayBuffer(int_buffer.length);
+ var view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < int_buffer.length; i++)
+ view[i] = int_buffer[i];
+ // #endregion
+
+ this.block_length = input_length;
+
+ return (input_offset + input_length);
+ };
+ //**************************************************************************************
+ local.BOOLEAN_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ return this.value_hex;
+ };
+ //**************************************************************************************
+ local.BOOLEAN_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BOOLEAN_value_block";
+ };
+ //**************************************************************************************
+ local.BOOLEAN_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.BOOLEAN_value_block.prototype.block_name.call(this);
+ _object.value = this.value;
+ _object.is_hex_only = this.is_hex_only;
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BOOLEAN =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.BOOLEAN_value_block(arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 1; // BOOLEAN
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BOOLEAN.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.BOOLEAN.constructor = local.BOOLEAN_value_block;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BOOLEAN.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BOOLEAN";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BOOLEAN.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.BOOLEAN.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 SEQUENCE and SET type classes
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SEQUENCE =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 16; // SEQUENCE
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SEQUENCE.prototype = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED();
+ in_window.org.pkijs.asn1.SEQUENCE.constructor = in_window.org.pkijs.asn1.SEQUENCE;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SEQUENCE.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "SEQUENCE";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SEQUENCE.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.SEQUENCE.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SET =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 17; // SET
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SET.prototype = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED();
+ in_window.org.pkijs.asn1.SET.constructor = in_window.org.pkijs.asn1.SET;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SET.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "SET";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.SET.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_CONSTRUCTED.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.SET.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 NULL type class
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 5; // NULL
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.NULL.constructor = in_window.org.pkijs.asn1.NULL;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "NULL";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ if(this.len_block.length > 0)
+ this.warnings.push("Non-zero length of value block for NULL type");
+
+ if(this.id_block.error.length === 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length === 0)
+ this.block_length += this.len_block.block_length;
+
+ this.block_length += input_length;
+
+ return (input_offset + input_length);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ var ret_buf = new ArrayBuffer(2);
+
+ if(size_only === true)
+ return ret_buf;
+
+ var ret_view = new Uint8Array(ret_buf);
+ ret_view[0] = 0x05;
+ ret_view[1] = 0x00;
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NULL.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.NULL.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 OCTETSTRING type class
+ //**************************************************************************************
+ local.OCTETSTRING_value_block =
+ function()
+ {
+ /// <param name="input_value_hex" type="ArrayBuffer"></param>
+ /// <param name="input_value" type="Array"></param>
+ /// <param name="input_constructed" type="Boolean"></param>
+ /// <remarks>Value for the OCTETSTRING may be as hex, as well as a constructed value.</remarks>
+ /// <remarks>Constructed values consists of other OCTETSTRINGs</remarks>
+
+ local.ASN1_CONSTRUCTED_value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.is_constructed = in_window.org.pkijs.getValue(arguments[0], "is_constructed", false);
+
+ // #region Variables from hex_block type
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", false);
+ if("value_hex" in arguments[0])
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+ else
+ this.value_hex = new ArrayBuffer(0);
+ // #endregion
+ }
+ else
+ {
+ this.is_constructed = false;
+
+ // #region Variables from hex_block type
+ this.is_hex_only = false;
+ this.value_hex = new ArrayBuffer(0);
+ // #endregion
+ }
+ };
+ //**************************************************************************************
+ local.OCTETSTRING_value_block.prototype = new local.ASN1_CONSTRUCTED_value_block();
+ local.OCTETSTRING_value_block.constructor = local.OCTETSTRING_value_block;
+ //**************************************************************************************
+ local.OCTETSTRING_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = 0;
+
+ if(this.is_constructed == true)
+ {
+ this.is_hex_only = false;
+
+ result_offset = local.ASN1_CONSTRUCTED_value_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ if(result_offset == (-1))
+ return result_offset;
+
+ for(var i = 0; i < this.value.length; i++)
+ {
+ var current_block_name = this.value[i].block_name();
+
+ if(current_block_name == in_window.org.pkijs.asn1.EOC.prototype.block_name())
+ {
+ if(this.is_indefinite_form == true)
+ break;
+ else
+ {
+ this.error = "EOC is unexpected, OCTET STRING may consists of OCTET STRINGs only";
+ return (-1);
+ }
+ }
+
+ if(current_block_name != in_window.org.pkijs.asn1.OCTETSTRING.prototype.block_name())
+ {
+ this.error = "OCTET STRING may consists of OCTET STRINGs only";
+ return (-1);
+ }
+ }
+ }
+ else
+ {
+ this.is_hex_only = true;
+
+ result_offset = local.hex_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ this.block_length = input_length;
+ }
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ local.OCTETSTRING_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.is_constructed === true)
+ return local.ASN1_CONSTRUCTED_value_block.prototype.toBER.call(this, size_only);
+ else
+ {
+ var ret_buf = new ArrayBuffer(this.value_hex.byteLength);
+
+ if(size_only === true)
+ return ret_buf;
+
+ if(this.value_hex.byteLength == 0)
+ return ret_buf;
+
+ ret_buf = util_copybuf(this.value_hex);
+
+ return ret_buf;
+ }
+
+ return (new ArrayBuffer(0));
+ };
+ //**************************************************************************************
+ local.OCTETSTRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "OCTETSTRING_value_block";
+ };
+ //**************************************************************************************
+ local.OCTETSTRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.ASN1_CONSTRUCTED_value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.OCTETSTRING_value_block.prototype.block_name.call(this);
+ _object.is_constructed = this.is_constructed;
+ _object.is_hex_only = this.is_hex_only;
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.OCTETSTRING_value_block(arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 4; // OCTETSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.OCTETSTRING.constructor = in_window.org.pkijs.asn1.OCTETSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ this.value_block.is_constructed = this.id_block.is_constructed;
+ this.value_block.is_indefinite_form = this.len_block.is_indefinite_form;
+
+ // #region Ability to encode empty OCTET STRING
+ if(input_length == 0)
+ {
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ return input_offset;
+ }
+ // #endregion
+
+ return in_window.org.pkijs.asn1.ASN1_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING.prototype.block_name =
+ function()
+ {
+ return "OCTETSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.OCTETSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OCTETSTRING.prototype.isEqual =
+ function(octetString)
+ {
+ /// <summaryChecking that two OCTETSTRINGs are equal></summary>
+ /// <param name="octetString" type="in_window.org.pkijs.asn1.OCTETSTRING">The OCTETSTRING to compare with</param>
+
+ // #region Check input type
+ if((octetString instanceof in_window.org.pkijs.asn1.OCTETSTRING) == false)
+ return false;
+ // #endregion
+
+ // #region Compare two JSON strings
+ if(JSON.stringify(this) != JSON.stringify(octetString))
+ return false;
+ // #endregion
+
+ return true;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 BITSTRING type class
+ //**************************************************************************************
+ local.BITSTRING_value_block =
+ function()
+ {
+ local.ASN1_CONSTRUCTED_value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.unused_bits = in_window.org.pkijs.getValue(arguments[0], "unused_bits", 0);
+ this.is_constructed = in_window.org.pkijs.getValue(arguments[0], "is_constructed", false);
+
+ // #region Variables from hex_block type
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", false);
+
+ if("value_hex" in arguments[0])
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+ else
+ this.value_hex = new ArrayBuffer(0);
+
+ this.block_length = this.value_hex.byteLength;
+ // #endregion
+ }
+ else
+ {
+ this.unused_bits = 0;
+ this.is_constructed = false;
+
+ // #region Variables from hex_block type
+ this.is_hex_only = false;
+ this.value_hex = new ArrayBuffer(0);
+ // #endregion
+ }
+ };
+ //**************************************************************************************
+ local.BITSTRING_value_block.prototype = new local.ASN1_CONSTRUCTED_value_block();
+ local.BITSTRING_value_block.constructor = local.BITSTRING_value_block;
+ //**************************************************************************************
+ local.BITSTRING_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Ability to decode zero-length BITSTRING value
+ if(input_length == 0)
+ return input_offset;
+ // #endregion
+
+ var result_offset = (-1);
+
+ // #region If the BISTRING supposed to be a constructed value
+ if(this.is_constructed == true)
+ {
+ result_offset = local.ASN1_CONSTRUCTED_value_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ if(result_offset == (-1))
+ return result_offset;
+
+ for(var i = 0; i < this.value.length; i++)
+ {
+ var current_block_name = this.value[i].block_name();
+
+ if(current_block_name == in_window.org.pkijs.asn1.EOC.prototype.block_name())
+ {
+ if(this.is_indefinite_form == true)
+ break;
+ else
+ {
+ this.error = "EOC is unexpected, BIT STRING may consists of BIT STRINGs only";
+ return (-1);
+ }
+ }
+
+ if(current_block_name != in_window.org.pkijs.asn1.BITSTRING.prototype.block_name())
+ {
+ this.error = "BIT STRING may consists of BIT STRINGs only";
+ return (-1);
+ }
+
+ if((this.unused_bits > 0) && (this.value[i].unused_bits > 0))
+ {
+ this.error = "Usign of \"unused bits\" inside constructive BIT STRING allowed for least one only";
+ return (-1);
+ }
+ else
+ {
+ this.unused_bits = this.value[i].unused_bits;
+ if(this.unused_bits > 7)
+ {
+ this.error = "Unused bits for BITSTRING must be in range 0-7";
+ return (-1);
+ }
+ }
+ }
+
+ return result_offset;
+ }
+ // #endregion
+ // #region If the BITSTRING supposed to be a primitive value
+ else
+ {
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+
+ this.unused_bits = int_buffer[0];
+ if(this.unused_bits > 7)
+ {
+ this.error = "Unused bits for BITSTRING must be in range 0-7";
+ return (-1);
+ }
+
+ // #region Copy input buffer to internal buffer
+ this.value_hex = new ArrayBuffer(int_buffer.length - 1);
+ var view = new Uint8Array(this.value_hex);
+ for(var i = 0; i < (input_length - 1) ; i++)
+ view[i] = int_buffer[i + 1];
+ // #endregion
+
+ this.block_length = int_buffer.length;
+
+ return (input_offset + input_length);
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ local.BITSTRING_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.is_constructed === true)
+ return local.ASN1_CONSTRUCTED_value_block.prototype.toBER.call(this, size_only);
+ else
+ {
+ if(size_only === true)
+ return (new ArrayBuffer(this.value_hex.byteLength + 1));
+
+ if(this.value_hex.byteLength == 0)
+ return (new ArrayBuffer(0));
+
+ var cur_view = new Uint8Array(this.value_hex);
+
+ var ret_buf = new ArrayBuffer(this.value_hex.byteLength + 1);
+ var ret_view = new Uint8Array(ret_buf);
+
+ ret_view[0] = this.unused_bits;
+
+ for(var i = 0; i < this.value_hex.byteLength; i++)
+ ret_view[i + 1] = cur_view[i];
+
+ return ret_buf;
+ }
+
+ return (new ArrayBuffer(0));
+ };
+ //**************************************************************************************
+ local.BITSTRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BITSTRING_value_block";
+ };
+ //**************************************************************************************
+ local.BITSTRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.ASN1_CONSTRUCTED_value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.BITSTRING_value_block.prototype.block_name.call(this);
+ _object.unused_bits = this.unused_bits;
+ _object.is_constructed = this.is_constructed;
+ _object.is_hex_only = this.is_hex_only;
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BITSTRING =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.BITSTRING_value_block(arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 3; // BITSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BITSTRING.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.BITSTRING.constructor = in_window.org.pkijs.asn1.BITSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BITSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BITSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BITSTRING.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ // #region Ability to encode empty BITSTRING
+ if(input_length == 0)
+ return input_offset;
+ // #endregion
+
+ this.value_block.is_constructed = this.id_block.is_constructed;
+ this.value_block.is_indefinite_form = this.len_block.is_indefinite_form;
+
+ return in_window.org.pkijs.asn1.ASN1_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BITSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.BITSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 INTEGER type class
+ //**************************************************************************************
+ local.INTEGER_value_block =
+ function()
+ {
+ local.value_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.value_dec = in_window.org.pkijs.getValue(arguments[0], "value", 0);
+
+ // #region Variables from hex_block type
+ this.is_hex_only = in_window.org.pkijs.getValue(arguments[0], "is_hex_only", false);
+ if("value_hex" in arguments[0])
+ {
+ this.value_hex = util_copybuf(arguments[0].value_hex);
+
+ if(this.value_hex.byteLength >= 4) // Dummy's protection
+ this.is_hex_only = true;
+ else
+ this.value_dec = util_decode_tc.call(this);
+ }
+ else
+ this.value_hex = util_encode_tc(this.value_dec);
+ // #endregion
+ }
+ else
+ {
+ this.value_dec = 0;
+
+ // #region Variables from hex_block type
+ this.is_hex_only = false;
+ this.value_hex = new ArrayBuffer(0);
+ // #endregion
+ }
+ };
+ //**************************************************************************************
+ local.INTEGER_value_block.prototype = new local.value_block();
+ local.INTEGER_value_block.constructor = local.INTEGER_value_block;
+ //**************************************************************************************
+ local.INTEGER_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = local.hex_block.prototype.fromBER.call(this, input_buffer, input_offset, input_length);
+ if(result_offset == (-1))
+ return result_offset;
+
+ if(this.value_hex.byteLength > 4) // In JavaScript we can effectively work with 32-bit integers only
+ {
+ this.warnings.push("Too big INTEGER for decoding, hex only");
+ this.is_hex_only = true;
+ }
+ else
+ this.value_dec = util_decode_tc.call(this);
+
+ this.block_length = input_length;
+
+ return (input_offset + input_length);
+ };
+ //**************************************************************************************
+ local.INTEGER_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.is_hex_only === false)
+ {
+ var encoded_buf = util_encode_tc(this.value_dec);
+ if(encoded_buf.byteLength == 0)
+ {
+ this.error = "Error during encoding INTEGER value";
+ return (new ArrayBuffer(0));
+ }
+
+ return util_copybuf(encoded_buf);
+ }
+ else
+ return util_copybuf(this.value_hex);
+
+ return (new ArrayBuffer(0));
+ };
+ //**************************************************************************************
+ local.INTEGER_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "INTEGER_value_block";
+ };
+ //**************************************************************************************
+ local.INTEGER_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.INTEGER_value_block.prototype.block_name.call(this);
+ _object.value_dec = this.value_dec;
+ _object.is_hex_only = this.is_hex_only;
+ _object.value_hex = in_window.org.pkijs.bufferToHexCodes(this.value_hex, 0, this.value_hex.byteLength);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.INTEGER =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.INTEGER_value_block(arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 2; // INTEGER
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.INTEGER.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.INTEGER.constructor = in_window.org.pkijs.asn1.INTEGER;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.INTEGER.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "INTEGER";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.INTEGER.prototype.isEqual =
+ function()
+ {
+ /// <summary>Compare two INTEGER object, or INTEGER and ArrayBuffer objects</summary>
+ /// <returns type="Boolean"></returns>
+
+ if(arguments[0] instanceof in_window.org.pkijs.asn1.INTEGER)
+ {
+ if(this.value_block.is_hex_only && arguments[0].value_block.is_hex_only) // Compare two ArrayBuffers
+ return in_window.org.pkijs.isEqual_buffer(this.value_block.value_hex, arguments[0].value_block.value_hex);
+ else
+ {
+ if(this.value_block.is_hex_only === arguments[0].value_block.is_hex_only)
+ return (this.value_block.value_dec == arguments[0].value_block.value_dec);
+ else
+ return false;
+ }
+ }
+ else
+ {
+ if(arguments[0] instanceof ArrayBuffer)
+ return in_window.org.pkijs.isEqual_buffer(this.value_block.value_hex, arguments[0]);
+ else
+ return false;
+ }
+
+ return false;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.INTEGER.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.INTEGER.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 ENUMERATED type class
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ENUMERATED =
+ function()
+ {
+ in_window.org.pkijs.asn1.INTEGER.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 10; // ENUMERATED
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ENUMERATED.prototype = new in_window.org.pkijs.asn1.INTEGER();
+ in_window.org.pkijs.asn1.ENUMERATED.constructor = in_window.org.pkijs.asn1.ENUMERATED;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ENUMERATED.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "ENUMERATED";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ENUMERATED.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.INTEGER.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.ENUMERATED.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of ASN.1 OBJECT IDENTIFIER type class
+ //**************************************************************************************
+ local.SID_value_block =
+ function()
+ {
+ local.hex_block.call(this, arguments[0]);
+
+ if(arguments[0] instanceof Object)
+ {
+ this.value_dec = in_window.org.pkijs.getValue(arguments[0], "value_dec", -1);
+ this.is_first_sid = in_window.org.pkijs.getValue(arguments[0], "is_first_sid", false);
+ }
+ else
+ {
+ this.value_dec = (-1);
+ this.is_first_sid = false;
+ }
+ };
+ //**************************************************************************************
+ local.SID_value_block.prototype = new local.hex_block();
+ local.SID_value_block.constructor = local.SID_value_block;
+ //**************************************************************************************
+ local.SID_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "sid_block";
+ };
+ //**************************************************************************************
+ local.SID_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ if(input_length == 0)
+ return input_offset;
+
+ // #region Basic check for parameters
+ if(check_buffer_params.call(this, input_buffer, input_offset, input_length) === false)
+ return (-1);
+ // #endregion
+
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+
+ this.value_hex = new ArrayBuffer(input_length);
+ var view = new Uint8Array(this.value_hex);
+
+ for(var i = 0; i < input_length; i++)
+ {
+ view[i] = int_buffer[i] & 0x7F;
+
+ this.block_length++;
+
+ if((int_buffer[i] & 0x80) == 0x00)
+ break;
+ }
+
+ // #region Ajust size of value_hex buffer
+ var temp_value_hex = new ArrayBuffer(this.block_length);
+ var temp_view = new Uint8Array(temp_value_hex);
+
+ for(var i = 0; i < this.block_length; i++)
+ temp_view[i] = view[i];
+
+ this.value_hex = util_copybuf(temp_value_hex);
+ view = new Uint8Array(this.value_hex);
+ // #endregion
+
+ if((int_buffer[this.block_length - 1] & 0x80) != 0x00)
+ {
+ this.error = "End of input reached before message was fully decoded";
+ return (-1);
+ }
+
+ if(view[0] == 0x00)
+ this.warnings.push("Needlessly long format of SID encoding");
+
+ if(this.block_length <= 8)
+ this.value_dec = util_frombase(view, 7);
+ else
+ {
+ this.is_hex_only = true;
+ this.warnings.push("Too big SID for decoding, hex only");
+ }
+
+ return (input_offset + this.block_length);
+ };
+ //**************************************************************************************
+ local.SID_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ if(this.is_hex_only)
+ {
+ if(size_only === true)
+ return (new ArrayBuffer(this.value_hex.byteLength));
+
+ var cur_view = new Uint8Array(this.value_hex);
+
+ var ret_buf = new ArrayBuffer(this.block_length);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < (this.block_length - 1) ; i++)
+ ret_view[i] = cur_view[i] | 0x80;
+
+ ret_view[this.block_length - 1] = cur_view[this.block_length - 1];
+
+ return ret_buf;
+ }
+ else
+ {
+ var encoded_buf = util_tobase(this.value_dec, 7);
+ if(encoded_buf.byteLength === 0)
+ {
+ this.error = "Error during encoding SID value";
+ return (new ArrayBuffer(0));
+ }
+
+ var ret_buf = new ArrayBuffer(encoded_buf.byteLength);
+
+ if(size_only === false)
+ {
+ var encoded_view = new Uint8Array(encoded_buf);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < (encoded_buf.byteLength - 1) ; i++)
+ ret_view[i] = encoded_view[i] | 0x80;
+
+ ret_view[encoded_buf.byteLength - 1] = encoded_view[encoded_buf.byteLength - 1];
+ }
+
+ return ret_buf;
+ }
+ };
+ //**************************************************************************************
+ local.SID_value_block.prototype.toString =
+ function()
+ {
+ var result = "";
+
+ if(this.is_hex_only === true)
+ result = to_hex_codes(this.value_hex);
+ else
+ {
+ if(this.is_first_sid)
+ {
+ var sid_value = this.value_dec;
+
+ if(this.value_dec <= 39)
+ result = "0.";
+ else
+ {
+ if(this.value_dec <= 79)
+ {
+ result = "1.";
+ sid_value -= 40;
+ }
+ else
+ {
+ result = "2.";
+ sid_value -= 80;
+ }
+ }
+
+ result = result + sid_value.toString();
+ }
+ else
+ result = this.value_dec.toString();
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ local.SID_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.SID_value_block.prototype.block_name.call(this);
+ _object.value_dec = this.value_dec;
+ _object.is_first_sid = this.is_first_sid;
+
+ return _object;
+ };
+ //**************************************************************************************
+ local.OID_value_block =
+ function()
+ {
+ local.value_block.call(this, arguments[0]);
+
+ this.value = new Array();
+
+ if(arguments[0] instanceof Object)
+ this.fromString(in_window.org.pkijs.getValue(arguments[0], "value", ""));
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype = new local.value_block();
+ local.OID_value_block.constructor = local.OID_value_block;
+ //**************************************************************************************
+ local.OID_value_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = input_offset;
+
+ while(input_length > 0)
+ {
+ var sid_block = new local.SID_value_block();
+ result_offset = sid_block.fromBER(input_buffer, result_offset, input_length);
+ if(result_offset == (-1))
+ {
+ this.block_length = 0;
+ this.error = sid_block.error;
+ return result_offset;
+ }
+
+ if(this.value.length == 0)
+ sid_block.is_first_sid = true;
+
+ this.block_length += sid_block.block_length;
+ input_length -= sid_block.block_length;
+
+ this.value.push(sid_block);
+ }
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype.toBER =
+ function(size_only)
+ {
+ /// <summary>Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)</summary>
+ /// <param name="size_only" type="Boolean">Flag that we need only a size of encoding, not a real array of bytes</param>
+
+ if(typeof size_only === "undefined")
+ size_only = false;
+
+ var ret_buf = new ArrayBuffer(0);
+
+ for(var i = 0; i < this.value.length; i++)
+ {
+ var value_buf = this.value[i].toBER(size_only);
+ if(value_buf.byteLength === 0)
+ {
+ this.error = this.value[i].error;
+ return (new ArrayBuffer(0));
+ }
+
+ ret_buf = util_concatbuf(ret_buf, value_buf);
+ }
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype.fromString =
+ function(str)
+ {
+ this.value = new Array(); // Clear existing SID values
+
+ var pos1 = 0;
+ var pos2 = 0;
+
+ var sid = "";
+
+ var flag = false;
+
+ do
+ {
+ pos2 = str.indexOf('.', pos1);
+ if(pos2 === (-1))
+ sid = str.substr(pos1);
+ else
+ sid = str.substr(pos1, pos2 - pos1);
+
+ pos1 = pos2 + 1;
+
+ if(flag)
+ {
+ var sid_block = this.value[0];
+
+ var plus = 0;
+
+ switch(sid_block.value_dec)
+ {
+ case 0:
+ break;
+ case 1:
+ plus = 40;
+ break;
+ case 2:
+ plus = 80;
+ break;
+ default:
+ this.value = new Array(); // clear SID array
+ return false; // ???
+ }
+
+ var parsedSID = parseInt(sid, 10);
+ if(isNaN(parsedSID))
+ return true;
+
+ sid_block.value_dec = parsedSID + plus;
+
+ flag = false;
+ }
+ else
+ {
+ var sid_block = new local.SID_value_block();
+ sid_block.value_dec = parseInt(sid, 10);
+ if(isNaN(sid_block.value_dec))
+ return true;
+
+ if(this.value.length === 0)
+ {
+ sid_block.is_first_sid = true;
+ flag = true;
+ }
+
+ this.value.push(sid_block);
+ }
+
+ } while(pos2 !== (-1));
+
+ return true;
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype.toString =
+ function()
+ {
+ var result = "";
+ var is_hex_only = false;
+
+ for(var i = 0; i < this.value.length; i++)
+ {
+ is_hex_only = this.value[i].is_hex_only;
+
+ var sid_str = this.value[i].toString();
+
+ if(i !== 0)
+ result = result + ".";
+
+ if(is_hex_only)
+ {
+ sid_str = "{" + sid_str + "}";
+
+ if(this.value[i].is_first_sid)
+ result = "2.{" + sid_str + " - 80}";
+ else
+ result = result + sid_str;
+ }
+ else
+ result = result + sid_str;
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "OID_value_block";
+ };
+ //**************************************************************************************
+ local.OID_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.value_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.OID_value_block.prototype.block_name.call(this);
+ _object.value = local.OID_value_block.prototype.toString.call(this);
+ _object.sid_array = new Array();
+ for(var i = 0; i < this.value.length; i++)
+ _object.sid_array.push(this.value[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OID =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.OID_value_block(arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 6; // OBJECT IDENTIFIER
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OID.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.OID.constructor = in_window.org.pkijs.asn1.OID;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OID.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "OID";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.OID.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.OID.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of all string's classes
+ //**************************************************************************************
+ local.UTF8STRING_value_block =
+ function()
+ {
+ local.hex_block.call(this, arguments[0]);
+
+ this.is_hex_only = true;
+ this.value = ""; // String representation of decoded ArrayBuffer
+ };
+ //**************************************************************************************
+ local.UTF8STRING_value_block.prototype = new local.hex_block();
+ local.UTF8STRING_value_block.constructor = local.UTF8STRING_value_block;
+ //**************************************************************************************
+ local.UTF8STRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "UTF8STRING_value_block";
+ };
+ //**************************************************************************************
+ local.UTF8STRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.UTF8STRING_value_block.prototype.block_name.call(this);
+ _object.value = this.value;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.UTF8STRING_value_block();
+
+ if(arguments[0] instanceof Object)
+ {
+ if("value" in arguments[0])
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.fromString.call(this,arguments[0].value);
+ }
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 12; // UTF8STRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.UTF8STRING.constructor = in_window.org.pkijs.asn1.UTF8STRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "UTF8STRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ /// <param name="input_buffer" type="ArrayBuffer">Array with encoded string</param>
+ this.value_block.value = String.fromCharCode.apply(null, new Uint8Array(input_buffer));
+
+ try
+ {
+ this.value_block.value = decodeURIComponent(escape(this.value_block.value));
+ }
+ catch(ex)
+ {
+ this.warnings.push("Error during \"decodeURIComponent\": " + ex + ", using raw string");
+ }
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.fromString =
+ function(input_string)
+ {
+ /// <param name="input_string" type="String">String with UNIVERSALSTRING value</param>
+
+ var str = unescape(encodeURIComponent(input_string));
+ var str_len = str.length;
+
+ this.value_block.value_hex = new ArrayBuffer(str_len);
+ var view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < str_len; i++)
+ view[i] = str.charCodeAt(i);
+
+ this.value_block.value = input_string;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.UTF8STRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ local.BMPSTRING_value_block =
+ function()
+ {
+ local.hex_block.call(this, arguments[0]);
+
+ this.is_hex_only = true;
+ this.value = "";
+ };
+ //**************************************************************************************
+ local.BMPSTRING_value_block.prototype = new local.hex_block();
+ local.BMPSTRING_value_block.constructor = local.BMPSTRING_value_block;
+ //**************************************************************************************
+ local.BMPSTRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BMPSTRING_value_block";
+ };
+ //**************************************************************************************
+ local.BMPSTRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.BMPSTRING_value_block.prototype.block_name.call(this);
+ _object.value = this.value;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.BMPSTRING_value_block();
+
+ if(arguments[0] instanceof Object)
+ {
+ if("value" in arguments[0])
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.fromString.call(this, arguments[0].value);
+ }
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 30; // BMPSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.BMPSTRING.constructor = in_window.org.pkijs.asn1.BMPSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "BMPSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ /// <param name="input_buffer" type="ArrayBuffer">Array with encoded string</param>
+
+ var copy_buffer = in_window.org.pkijs.copyBuffer(input_buffer);
+
+ var value_view = new Uint8Array(copy_buffer);
+
+ for(var i = 0; i < value_view.length; i = i + 2)
+ {
+ var temp = value_view[i];
+
+ value_view[i] = value_view[i + 1];
+ value_view[i + 1] = temp;
+ }
+
+ this.value_block.value = String.fromCharCode.apply(null, new Uint16Array(copy_buffer));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.fromString =
+ function(input_string)
+ {
+ /// <param name="input_string" type="String">String with UNIVERSALSTRING value</param>
+
+ var str_length = input_string.length;
+
+ this.value_block.value_hex = new ArrayBuffer(str_length * 2);
+ var value_hex_view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < str_length; i++)
+ {
+ var code_buf = util_tobase(input_string.charCodeAt(i), 8);
+ var code_view = new Uint8Array(code_buf);
+ if(code_view.length > 2)
+ continue;
+
+ var dif = 2 - code_view.length;
+
+ for(var j = (code_view.length - 1) ; j >= 0; j--)
+ value_hex_view[i * 2 + j + dif] = code_view[j];
+ }
+
+ this.value_block.value = input_string;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.BMPSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.BMPSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ local.UNIVERSALSTRING_value_block =
+ function()
+ {
+ local.hex_block.call(this, arguments[0]);
+
+ this.is_hex_only = true;
+ this.value = "";
+ };
+ //**************************************************************************************
+ local.UNIVERSALSTRING_value_block.prototype = new local.hex_block();
+ local.UNIVERSALSTRING_value_block.constructor = local.UNIVERSALSTRING_value_block;
+ //**************************************************************************************
+ local.UNIVERSALSTRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "UNIVERSALSTRING_value_block";
+ };
+ //**************************************************************************************
+ local.UNIVERSALSTRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.UNIVERSALSTRING_value_block.prototype.block_name.call(this);
+ _object.value = this.value;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.UNIVERSALSTRING_value_block();
+
+ if(arguments[0] instanceof Object)
+ {
+ if("value" in arguments[0])
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.fromString.call(this, arguments[0].value);
+ }
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 28; // UNIVERSALSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.constructor = in_window.org.pkijs.asn1.UNIVERSALSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "UNIVERSALSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ /// <param name="input_buffer" type="ArrayBuffer">Array with encoded string</param>
+
+ var copy_buffer = in_window.org.pkijs.copyBuffer(input_buffer);
+
+ var value_view = new Uint8Array(copy_buffer);
+
+ for(var i = 0; i < value_view.length; i = i + 4)
+ {
+ value_view[i] = value_view[i + 3];
+ value_view[i + 1] = value_view[i + 2];
+ value_view[i + 2] = 0x00;
+ value_view[i + 3] = 0x00;
+ }
+
+ this.value_block.value = String.fromCharCode.apply(null, new Uint32Array(copy_buffer));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.fromString =
+ function(input_string)
+ {
+ /// <param name="input_string" type="String">String with UNIVERSALSTRING value</param>
+
+ var str_length = input_string.length;
+
+ this.value_block.value_hex = new ArrayBuffer(str_length * 4);
+ var value_hex_view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < str_length; i++)
+ {
+ var code_buf = util_tobase(input_string.charCodeAt(i), 8);
+ var code_view = new Uint8Array(code_buf);
+ if(code_view.length > 4)
+ continue;
+
+ var dif = 4 - code_view.length;
+
+ for(var j = (code_view.length - 1) ; j >= 0; j--)
+ value_hex_view[i*4 + j + dif] = code_view[j];
+ }
+
+ this.value_block.value = input_string;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.UNIVERSALSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_value_block =
+ function()
+ {
+ local.hex_block.call(this, arguments[0]);
+
+ /// <field type="String">Native string representation</field>
+ this.value = "";
+ this.is_hex_only = true;
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_value_block.prototype = new local.hex_block();
+ local.SIMPLESTRING_value_block.constructor = local.SIMPLESTRING_value_block;
+ //**************************************************************************************
+ local.SIMPLESTRING_value_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "SIMPLESTRING_value_block";
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_value_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.hex_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.SIMPLESTRING_value_block.prototype.block_name.call(this);
+ _object.value = this.value;
+
+ return _object;
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block =
+ function()
+ {
+ in_window.org.pkijs.asn1.ASN1_block.call(this, arguments[0]);
+
+ this.value_block = new local.SIMPLESTRING_value_block();
+
+ if(arguments[0] instanceof Object)
+ {
+ if("value" in arguments[0])
+ local.SIMPLESTRING_block.prototype.fromString.call(this, arguments[0].value);
+ }
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype = new in_window.org.pkijs.asn1.ASN1_block();
+ local.SIMPLESTRING_block.constructor = local.SIMPLESTRING_block;
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "SIMPLESTRING";
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ local.SIMPLESTRING_block.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ /// <param name="input_buffer" type="ArrayBuffer">Array with encoded string</param>
+
+ this.value_block.value = String.fromCharCode.apply(null, new Uint8Array(input_buffer));
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype.fromString =
+ function(input_string)
+ {
+ /// <param name="input_string" type="String">String with UNIVERSALSTRING value</param>
+ var str_len = input_string.length;
+
+ this.value_block.value_hex = new ArrayBuffer(str_len);
+ var view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < str_len; i++)
+ view[i] = input_string.charCodeAt(i);
+
+ this.value_block.value = input_string;
+ };
+ //**************************************************************************************
+ local.SIMPLESTRING_block.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.ASN1_block.prototype.toJSON.call(this);
+
+ _object.block_name = local.SIMPLESTRING_block.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NUMERICSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 18; // NUMERICSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NUMERICSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.NUMERICSTRING.constructor = in_window.org.pkijs.asn1.NUMERICSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NUMERICSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "NUMERICSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.NUMERICSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.NUMERICSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.PRINTABLESTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 19; // PRINTABLESTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.PRINTABLESTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.PRINTABLESTRING.constructor = in_window.org.pkijs.asn1.PRINTABLESTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.PRINTABLESTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "PRINTABLESTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.PRINTABLESTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.PRINTABLESTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TELETEXSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 20; // TELETEXSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TELETEXSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.TELETEXSTRING.constructor = in_window.org.pkijs.asn1.TELETEXSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TELETEXSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "TELETEXSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TELETEXSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.TELETEXSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VIDEOTEXSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 21; // VIDEOTEXSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VIDEOTEXSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.VIDEOTEXSTRING.constructor = in_window.org.pkijs.asn1.VIDEOTEXSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VIDEOTEXSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "VIDEOTEXSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VIDEOTEXSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.VIDEOTEXSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.IA5STRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 22; // IA5STRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.IA5STRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.IA5STRING.constructor = in_window.org.pkijs.asn1.IA5STRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.IA5STRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "IA5STRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.IA5STRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.IA5STRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GRAPHICSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 25; // GRAPHICSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GRAPHICSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.GRAPHICSTRING.constructor = in_window.org.pkijs.asn1.GRAPHICSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GRAPHICSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "GRAPHICSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GRAPHICSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.GRAPHICSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VISIBLESTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 26; // VISIBLESTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VISIBLESTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.VISIBLESTRING.constructor = in_window.org.pkijs.asn1.VISIBLESTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VISIBLESTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "VISIBLESTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.VISIBLESTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.VISIBLESTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 27; // GENERALSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.GENERALSTRING.constructor = in_window.org.pkijs.asn1.GENERALSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "GENERALSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.GENERALSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.CHARACTERSTRING =
+ function()
+ {
+ local.SIMPLESTRING_block.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 29; // CHARACTERSTRING
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.CHARACTERSTRING.prototype = new local.SIMPLESTRING_block();
+ in_window.org.pkijs.asn1.CHARACTERSTRING.constructor = in_window.org.pkijs.asn1.CHARACTERSTRING;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.CHARACTERSTRING.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "CHARACTERSTRING";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.CHARACTERSTRING.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = local.SIMPLESTRING_block.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.CHARACTERSTRING.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of all date and time classes
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME =
+ function()
+ {
+ in_window.org.pkijs.asn1.VISIBLESTRING.call(this, arguments[0]);
+
+ this.year = 0;
+ this.month = 0;
+ this.day = 0;
+ this.hour = 0;
+ this.minute = 0;
+ this.second = 0;
+
+ // #region Create UTCTIME from ASN.1 UTC string value
+ if((arguments[0] instanceof Object) && ("value" in arguments[0]))
+ {
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromString.call(this, arguments[0].value);
+
+ this.value_block.value_hex = new ArrayBuffer(arguments[0].value.length);
+ var view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < arguments[0].value.length; i++)
+ view[i] = arguments[0].value.charCodeAt(i);
+ }
+ // #endregion
+ // #region Create UTCTIME from JavaScript Date type
+ if((arguments[0] instanceof Object) && ("value_date" in arguments[0]))
+ {
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromDate.call(this, arguments[0].value_date);
+ this.value_block.value_hex = in_window.org.pkijs.asn1.UTCTIME.prototype.toBuffer.call(this);
+ }
+ // #endregion
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 23; // UTCTIME
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype = new in_window.org.pkijs.asn1.VISIBLESTRING();
+ in_window.org.pkijs.asn1.UTCTIME.constructor = in_window.org.pkijs.asn1.UTCTIME;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromString.call(this, String.fromCharCode.apply(null, new Uint8Array(input_buffer)));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.toBuffer =
+ function()
+ {
+ var str = in_window.org.pkijs.asn1.UTCTIME.prototype.toString.call(this);
+
+ var buffer = new ArrayBuffer(str.length);
+ var view = new Uint8Array(buffer);
+
+ for(var i = 0; i < str.length; i++)
+ view[i] = str.charCodeAt(i);
+
+ return buffer;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromDate =
+ function(input_date)
+ {
+ /// <summary>Create "UTCTime" ASN.1 type from JavaScript "Date" type</summary>
+
+ this.year = input_date.getUTCFullYear();
+ this.month = input_date.getUTCMonth() + 1;
+ this.day = input_date.getUTCDate();
+ this.hour = input_date.getUTCHours();
+ this.minute = input_date.getUTCMinutes();
+ this.second = input_date.getUTCSeconds();
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.toDate =
+ function()
+ {
+ return (new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second)));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.fromString =
+ function(input_string)
+ {
+ /// <summary>Create "UTCTime" ASN.1 type from JavaScript "String" type</summary>
+
+ // #region Parse input string
+ var parser = /(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z/ig;
+ var parser_array = parser.exec(input_string);
+ if(parser_array === null)
+ {
+ this.error = "Wrong input string for convertion";
+ return;
+ }
+ // #endregion
+
+ // #region Store parsed values
+ var year = parseInt(parser_array[1], 10);
+ if(year >= 50)
+ this.year = 1900 + year;
+ else
+ this.year = 2000 + year;
+
+ this.month = parseInt(parser_array[2], 10);
+ this.day = parseInt(parser_array[3], 10);
+ this.hour = parseInt(parser_array[4], 10);
+ this.minute = parseInt(parser_array[5], 10);
+ this.second = parseInt(parser_array[6], 10);
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.toString =
+ function()
+ {
+ var output_array = new Array(7);
+
+ output_array[0] = in_window.org.pkijs.padNumber(((this.year < 2000) ? (this.year - 1900) : (this.year - 2000)), 2);
+ output_array[1] = in_window.org.pkijs.padNumber(this.month, 2);
+ output_array[2] = in_window.org.pkijs.padNumber(this.day, 2);
+ output_array[3] = in_window.org.pkijs.padNumber(this.hour, 2);
+ output_array[4] = in_window.org.pkijs.padNumber(this.minute, 2);
+ output_array[5] = in_window.org.pkijs.padNumber(this.second, 2);
+ output_array[6] = "Z";
+
+ return output_array.join('');
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "UTCTIME";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.UTCTIME.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.VISIBLESTRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.UTCTIME.prototype.block_name.call(this);
+ _object.year = this.year;
+ _object.month = this.month;
+ _object.day = this.day;
+ _object.hour = this.hour;
+ _object.minute = this.minute;
+ _object.second = this.second;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME =
+ function()
+ {
+ in_window.org.pkijs.asn1.VISIBLESTRING.call(this, arguments[0]);
+
+ this.year = 0;
+ this.month = 0;
+ this.day = 0;
+ this.hour = 0;
+ this.minute = 0;
+ this.second = 0;
+ this.millisecond = 0;
+
+ // #region Create GeneralizedTime from ASN.1 string value
+ if((arguments[0] instanceof Object) && ("value" in arguments[0]))
+ {
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromString.call(this, arguments[0].value);
+
+ this.value_block.value_hex = new ArrayBuffer(arguments[0].value.length);
+ var view = new Uint8Array(this.value_block.value_hex);
+
+ for(var i = 0; i < arguments[0].value.length; i++)
+ view[i] = arguments[0].value.charCodeAt(i);
+ }
+ // #endregion
+ // #region Create GeneralizedTime from JavaScript Date type
+ if((arguments[0] instanceof Object) && ("value_date" in arguments[0]))
+ {
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromDate.call(this, arguments[0].value_date);
+ this.value_block.value_hex = in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toBuffer.call(this);
+ }
+ // #endregion
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 24; // GENERALIZEDTIME
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype = new in_window.org.pkijs.asn1.VISIBLESTRING();
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.constructor = in_window.org.pkijs.asn1.GENERALIZEDTIME;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromBER =
+ function(input_buffer, input_offset, input_length)
+ {
+ /// <summary>Base function for converting block from BER encoded array of bytes</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array</param>
+ /// <param name="input_offset" type="Number">Offset in ASN.1 BER encoded array where decoding should be started</param>
+ /// <param name="input_length" type="Number">Maximum length of array of bytes which can be using in this function</param>
+
+ var result_offset = this.value_block.fromBER(input_buffer, input_offset, (this.len_block.is_indefinite_form == true) ? input_length : this.len_block.length);
+ if(result_offset == (-1))
+ {
+ this.error = this.value_block.error;
+ return result_offset;
+ }
+
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromBuffer.call(this, this.value_block.value_hex);
+
+ if(this.id_block.error.length == 0)
+ this.block_length += this.id_block.block_length;
+
+ if(this.len_block.error.length == 0)
+ this.block_length += this.len_block.block_length;
+
+ if(this.value_block.error.length == 0)
+ this.block_length += this.value_block.block_length;
+
+ return result_offset;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromBuffer =
+ function(input_buffer)
+ {
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromString.call(this, String.fromCharCode.apply(null, new Uint8Array(input_buffer)));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toBuffer =
+ function()
+ {
+ var str = in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toString.call(this);
+
+ var buffer = new ArrayBuffer(str.length);
+ var view = new Uint8Array(buffer);
+
+ for(var i = 0; i < str.length; i++)
+ view[i] = str.charCodeAt(i);
+
+ return buffer;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromDate =
+ function(input_date)
+ {
+ /// <summary>Create "GeneralizedTime" ASN.1 type from JavaScript "Date" type</summary>
+
+ this.year = input_date.getUTCFullYear();
+ this.month = input_date.getUTCMonth();
+ this.day = input_date.getUTCDate();
+ this.hour = input_date.getUTCHours();
+ this.minute = input_date.getUTCMinutes();
+ this.second = input_date.getUTCSeconds();
+ this.millisecond = input_date.getUTCMilliseconds();
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toDate =
+ function()
+ {
+ return (new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millisecond)));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.fromString =
+ function(input_string)
+ {
+ /// <summary>Create "GeneralizedTime" ASN.1 type from JavaScript "String" type</summary>
+
+ // #region Initial variables
+ var isUTC = false;
+
+ var timeString = "";
+ var dateTimeString = "";
+ var fractionPart = 0;
+
+ var parser;
+
+ var hourDifference = 0;
+ var minuteDifference = 0;
+ // #endregion
+
+ // #region Convert as UTC time
+ if(input_string[input_string.length - 1] == "Z")
+ {
+ timeString = input_string.substr(0, input_string.length - 1);
+
+ isUTC = true;
+ }
+ // #endregion
+ // #region Convert as local time
+ else
+ {
+ var number = new Number(input_string[input_string.length - 1]);
+
+ if(isNaN(number.valueOf()))
+ throw new Error("Wrong input string for convertion");
+
+ timeString = input_string;
+ }
+ // #endregion
+
+ // #region Check that we do not have a "+" and "-" symbols inside UTC time
+ if(isUTC)
+ {
+ if(timeString.indexOf("+") != (-1))
+ throw new Error("Wrong input string for convertion");
+
+ if(timeString.indexOf("-") != (-1))
+ throw new Error("Wrong input string for convertion");
+ }
+ // #endregion
+ // #region Get "UTC time difference" in case of local time
+ else
+ {
+ var multiplier = 1;
+ var differencePosition = timeString.indexOf("+");
+ var differenceString = "";
+
+ if(differencePosition == (-1))
+ {
+ differencePosition = timeString.indexOf("-");
+ multiplier = (-1);
+ }
+
+ if(differencePosition != (-1))
+ {
+ differenceString = timeString.substr(differencePosition + 1);
+ timeString = timeString.substr(0, differencePosition);
+
+ if((differenceString.length != 2) && (differenceString.length != 4))
+ throw new Error("Wrong input string for convertion");
+
+ var number = new Number(differenceString.substr(0, 2));
+
+ if(isNaN(number.valueOf()))
+ throw new Error("Wrong input string for convertion");
+
+ hourDifference = multiplier * number;
+
+ if(differenceString.length == 4)
+ {
+ number = new Number(differenceString.substr(2, 2));
+
+ if(isNaN(number.valueOf()))
+ throw new Error("Wrong input string for convertion");
+
+ minuteDifference = multiplier * number;
+ }
+ }
+ }
+ // #endregion
+
+ // #region Get position of fraction point
+ var fractionPointPosition = timeString.indexOf("."); // Check for "full stop" symbol
+ if(fractionPointPosition == (-1))
+ fractionPointPosition = timeString.indexOf(","); // Check for "comma" symbol
+ // #endregion
+
+ // #region Get fraction part
+ if(fractionPointPosition != (-1))
+ {
+ var fractionPartCheck = new Number("0" + timeString.substr(fractionPointPosition));
+
+ if(isNaN(fractionPartCheck.valueOf()))
+ throw new Error("Wrong input string for convertion");
+
+ fractionPart = fractionPartCheck.valueOf();
+
+ dateTimeString = timeString.substr(0, fractionPointPosition);
+ }
+ else
+ dateTimeString = timeString;
+ // #endregion
+
+ // #region Parse internal date
+ switch(true)
+ {
+ case (dateTimeString.length == 8): // "YYYYMMDD"
+ parser = /(\d{4})(\d{2})(\d{2})/ig;
+ if(fractionPointPosition !== (-1))
+ throw new Error("Wrong input string for convertion"); // Here we should not have a "fraction point"
+ break;
+ case (dateTimeString.length == 10): // "YYYYMMDDHH"
+ parser = /(\d{4})(\d{2})(\d{2})(\d{2})/ig;
+
+ if(fractionPointPosition !== (-1))
+ {
+ var fractionResult = 60 * fractionPart;
+ this.minute = Math.floor(fractionResult);
+
+ fractionResult = 60 * (fractionResult - this.minute);
+ this.second = Math.floor(fractionResult);
+
+ fractionResult = 1000 * (fractionResult - this.second);
+ this.millisecond = Math.floor(fractionResult);
+ }
+ break;
+ case (dateTimeString.length == 12): // "YYYYMMDDHHMM"
+ parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/ig;
+
+ if(fractionPointPosition !== (-1))
+ {
+ var fractionResult = 60 * fractionPart;
+ this.second = Math.floor(fractionResult);
+
+ fractionResult = 1000 * (fractionResult - this.second);
+ this.millisecond = Math.floor(fractionResult);
+ }
+ break;
+ case (dateTimeString.length == 14): // "YYYYMMDDHHMMSS"
+ parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ig;
+
+ if(fractionPointPosition !== (-1))
+ {
+ var fractionResult = 1000 * fractionPart;
+ this.millisecond = Math.floor(fractionResult);
+ }
+ break;
+ default:
+ throw new Error("Wrong input string for convertion");
+ }
+ // #endregion
+
+ // #region Put parsed values at right places
+ var parser_array = parser.exec(dateTimeString);
+ if(parser_array == null)
+ throw new Error("Wrong input string for convertion");
+
+ for(var j = 1; j < parser_array.length; j++)
+ {
+ switch(j)
+ {
+ case 1:
+ this.year = parseInt(parser_array[j], 10);
+ break;
+ case 2:
+ this.month = parseInt(parser_array[j], 10) - 1; // In JavaScript we have month range as "0 - 11"
+ break;
+ case 3:
+ this.day = parseInt(parser_array[j], 10);
+ break;
+ case 4:
+ this.hour = parseInt(parser_array[j], 10) + hourDifference;
+ break;
+ case 5:
+ this.minute = parseInt(parser_array[j], 10) + minuteDifference;
+ break;
+ case 6:
+ this.second = parseInt(parser_array[j], 10);
+ break;
+ default:
+ throw new Error("Wrong input string for convertion");
+ }
+ }
+ // #endregion
+
+ // #region Get final date
+ if(isUTC == false)
+ {
+ var tempDate = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
+
+ this.year = tempDate.getUTCFullYear();
+ this.month = tempDate.getUTCMonth();
+ this.day = tempDate.getUTCDay();
+ this.hour = tempDate.getUTCHours();
+ this.minute = tempDate.getUTCMinutes();
+ this.second = tempDate.getUTCSeconds();
+ this.millisecond = tempDate.getUTCMilliseconds();
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toString =
+ function()
+ {
+ var output_array = new Array();
+
+ output_array.push(in_window.org.pkijs.padNumber(this.year, 4));
+ output_array.push(in_window.org.pkijs.padNumber(this.month, 2));
+ output_array.push(in_window.org.pkijs.padNumber(this.day, 2));
+ output_array.push(in_window.org.pkijs.padNumber(this.hour, 2));
+ output_array.push(in_window.org.pkijs.padNumber(this.minute, 2));
+ output_array.push(in_window.org.pkijs.padNumber(this.second, 2));
+ if(this.millisecond != 0)
+ {
+ output_array.push(".");
+ output_array.push(in_window.org.pkijs.padNumber(this.millisecond, 3));
+ }
+ output_array.push("Z");
+
+ return output_array.join('');
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "GENERALIZEDTIME";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.VISIBLESTRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.GENERALIZEDTIME.prototype.block_name.call(this);
+ _object.year = this.year;
+ _object.month = this.month;
+ _object.day = this.day;
+ _object.hour = this.hour;
+ _object.minute = this.minute;
+ _object.second = this.second;
+ _object.millisecond = this.millisecond;
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATE =
+ function()
+ {
+ in_window.org.pkijs.asn1.UTF8STRING.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 31; // DATE
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATE.prototype = new in_window.org.pkijs.asn1.UTF8STRING();
+ in_window.org.pkijs.asn1.DATE.constructor = in_window.org.pkijs.asn1.DATE;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATE.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "DATE";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATE.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.DATE.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIMEOFDAY =
+ function()
+ {
+ in_window.org.pkijs.asn1.UTF8STRING.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 32; // TIMEOFDAY
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIMEOFDAY.prototype = new in_window.org.pkijs.asn1.UTF8STRING();
+ in_window.org.pkijs.asn1.TIMEOFDAY.constructor = in_window.org.pkijs.asn1.TIMEOFDAY;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIMEOFDAY.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "TIMEOFDAY";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIMEOFDAY.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.TIMEOFDAY.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATETIME =
+ function()
+ {
+ in_window.org.pkijs.asn1.UTF8STRING.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 33; // DATETIME
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATETIME.prototype = new in_window.org.pkijs.asn1.UTF8STRING();
+ in_window.org.pkijs.asn1.DATETIME.constructor = in_window.org.pkijs.asn1.DATETIME;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATETIME.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "DATETIME";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DATETIME.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.DATETIME.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DURATION =
+ function()
+ {
+ in_window.org.pkijs.asn1.UTF8STRING.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 34; // DURATION
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DURATION.prototype = new in_window.org.pkijs.asn1.UTF8STRING();
+ in_window.org.pkijs.asn1.DURATION.constructor = in_window.org.pkijs.asn1.DURATION;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DURATION.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "DURATION";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.DURATION.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.DURATION.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIME =
+ function()
+ {
+ in_window.org.pkijs.asn1.UTF8STRING.call(this, arguments[0]);
+
+ this.id_block.tag_class = 1; // UNIVERSAL
+ this.id_block.tag_number = 14; // TIME
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIME.prototype = new in_window.org.pkijs.asn1.UTF8STRING();
+ in_window.org.pkijs.asn1.TIME.constructor = in_window.org.pkijs.asn1.TIME;
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIME.prototype.block_name =
+ function()
+ {
+ /// <summary>Aux function, need to get a block name. Need to have it here for inhiritence</summary>
+
+ return "TIME";
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.TIME.prototype.toJSON =
+ function()
+ {
+ /// <summary>Convertion for the block to JSON object</summary>
+
+ var _object = in_window.org.pkijs.asn1.UTF8STRING.prototype.toJSON.call(this);
+
+ _object.block_name = in_window.org.pkijs.asn1.TIME.prototype.block_name.call(this);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of special ASN.1 schema type CHOICE
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.CHOICE =
+ function()
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.value = in_window.org.pkijs.getValue(arguments[0], "value", new Array()); // Array of ASN.1 types for make a choice from
+ this.optional = in_window.org.pkijs.getValue(arguments[0], "optional", false);
+ }
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of special ASN.1 schema type ANY
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.ANY =
+ function()
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.name = in_window.org.pkijs.getValue(arguments[0], "name", "");
+ this.optional = in_window.org.pkijs.getValue(arguments[0], "optional", false);
+ }
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of special ASN.1 schema type REPEATED
+ //**************************************************************************************
+ in_window.org.pkijs.asn1.REPEATED =
+ function()
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.name = in_window.org.pkijs.getValue(arguments[0], "name", "");
+ this.optional = in_window.org.pkijs.getValue(arguments[0], "optional", false);
+ this.value = in_window.org.pkijs.getValue(arguments[0], "value", new in_window.org.pkijs.asn1.ANY());
+ this.local = in_window.org.pkijs.getValue(arguments[0], "local", false); // Could local or global array to store elements
+ }
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Major ASN.1 BER decoding function
+ //**************************************************************************************
+ function fromBER_raw(input_buffer, input_offset, input_length)
+ {
+ var incoming_offset = input_offset; // Need to store initial offset since "input_offset" is changing in the function
+
+ // #region Local function changing a type for ASN.1 classes
+ function local_change_type(input_object, new_type)
+ {
+ if(input_object instanceof new_type)
+ return input_object;
+
+ var new_object = new new_type();
+ new_object.id_block = input_object.id_block;
+ new_object.len_block = input_object.len_block;
+ new_object.warnings = input_object.warnings;
+ new_object.value_before_decode = util_copybuf(input_object.value_before_decode);
+
+ return new_object;
+ }
+ // #endregion
+
+ // #region Create a basic ASN.1 type since we need to return errors and warnings from the function
+ var return_object = new in_window.org.pkijs.asn1.ASN1_block();
+ // #endregion
+
+ // #region Basic check for parameters
+ if(check_buffer_params(input_buffer, input_offset, input_length) === false)
+ {
+ return_object.error = "Wrong input parameters";
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+ // #endregion
+
+ // #region Getting Uint8Array from ArrayBuffer
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_length);
+ // #endregion
+
+ // #region Initial checks
+ if(int_buffer.length == 0)
+ {
+ this.error = "Zero buffer length";
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+ // #endregion
+
+ // #region Decode indentifcation block of ASN.1 BER structure
+ var result_offset = return_object.id_block.fromBER(input_buffer, input_offset, input_length);
+ return_object.warnings.concat(return_object.id_block.warnings);
+ if(result_offset == (-1))
+ {
+ return_object.error = return_object.id_block.error;
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+
+ input_offset = result_offset;
+ input_length -= return_object.id_block.block_length;
+ // #endregion
+
+ // #region Decode length block of ASN.1 BER structure
+ result_offset = return_object.len_block.fromBER(input_buffer, input_offset, input_length);
+ return_object.warnings.concat(return_object.len_block.warnings);
+ if(result_offset == (-1))
+ {
+ return_object.error = return_object.len_block.error;
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+
+ input_offset = result_offset;
+ input_length -= return_object.len_block.block_length;
+ // #endregion
+
+ // #region Check for usign indefinite length form in encoding for primitive types
+ if((return_object.id_block.is_constructed == false) &&
+ (return_object.len_block.is_indefinite_form == true))
+ {
+ return_object.error = new String("Indefinite length form used for primitive encoding form");
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+ // #endregion
+
+ // #region Switch ASN.1 block type
+ var new_asn1_type = in_window.org.pkijs.asn1.ASN1_block;
+
+ switch(return_object.id_block.tag_class)
+ {
+ // #region UNIVERSAL
+ case 1:
+ // #region Check for reserved tag numbers
+ if((return_object.id_block.tag_number >= 37) &&
+ (return_object.id_block.is_hex_only == false))
+ {
+ return_object.error = "UNIVERSAL 37 and upper tags are reserved by ASN.1 standard";
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+ // #endregion
+
+ switch(return_object.id_block.tag_number)
+ {
+ // #region EOC type
+ case 0:
+ // #region Check for EOC type
+ if((return_object.id_block.is_constructed == true) &&
+ (return_object.len_block.length > 0))
+ {
+ return_object.error = "Type [UNIVERSAL 0] is reserved";
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ }
+ // #endregion
+
+ new_asn1_type = in_window.org.pkijs.asn1.EOC;
+
+ break;
+ // #endregion
+ // #region BOOLEAN type
+ case 1:
+ new_asn1_type = in_window.org.pkijs.asn1.BOOLEAN;
+ break;
+ // #endregion
+ // #region INTEGER type
+ case 2:
+ new_asn1_type = in_window.org.pkijs.asn1.INTEGER;
+ break;
+ // #endregion
+ // #region BITSTRING type
+ case 3:
+ new_asn1_type = in_window.org.pkijs.asn1.BITSTRING;
+ break;
+ // #endregion
+ // #region OCTETSTRING type
+ case 4:
+ new_asn1_type = in_window.org.pkijs.asn1.OCTETSTRING;
+ break;
+ // #endregion
+ // #region NULL type
+ case 5:
+ new_asn1_type = in_window.org.pkijs.asn1.NULL;
+ break;
+ // #endregion
+ // #region OBJECT IDENTIFIER type
+ case 6:
+ new_asn1_type = in_window.org.pkijs.asn1.OID;
+ break;
+ // #endregion
+ // #region ENUMERATED type
+ case 10:
+ new_asn1_type = in_window.org.pkijs.asn1.ENUMERATED;
+ break;
+ // #endregion
+ // #region UTF8STRING type
+ case 12:
+ new_asn1_type = in_window.org.pkijs.asn1.UTF8STRING;
+ break;
+ // #endregion
+ // #region TIME type
+ case 14:
+ new_asn1_type = in_window.org.pkijs.asn1.TIME;
+ break;
+ // #endregion
+ // #region ASN.1 reserved type
+ case 15:
+ return_object.error = "[UNIVERSAL 15] is reserved by ASN.1 standard";
+ return {
+ offset: (-1),
+ result: return_object
+ };
+ break;
+ // #endregion
+ // #region SEQUENCE type
+ case 16:
+ new_asn1_type = in_window.org.pkijs.asn1.SEQUENCE;
+ break;
+ // #endregion
+ // #region SET type
+ case 17:
+ new_asn1_type = in_window.org.pkijs.asn1.SET;
+ break;
+ // #endregion
+ // #region NUMERICSTRING type
+ case 18:
+ new_asn1_type = in_window.org.pkijs.asn1.NUMERICSTRING;
+ break;
+ // #endregion
+ // #region PRINTABLESTRING type
+ case 19:
+ new_asn1_type = in_window.org.pkijs.asn1.PRINTABLESTRING;
+ break;
+ // #endregion
+ // #region TELETEXSTRING type
+ case 20:
+ new_asn1_type = in_window.org.pkijs.asn1.TELETEXSTRING;
+ break;
+ // #endregion
+ // #region VIDEOTEXSTRING type
+ case 21:
+ new_asn1_type = in_window.org.pkijs.asn1.VIDEOTEXSTRING;
+ break;
+ // #endregion
+ // #region IA5STRING type
+ case 22:
+ new_asn1_type = in_window.org.pkijs.asn1.IA5STRING;
+ break;
+ // #endregion
+ // #region UTCTIME type
+ case 23:
+ new_asn1_type = in_window.org.pkijs.asn1.UTCTIME;
+ break;
+ // #endregion
+ // #region GENERALIZEDTIME type
+ case 24:
+ new_asn1_type = in_window.org.pkijs.asn1.GENERALIZEDTIME;
+ break;
+ // #endregion
+ // #region GRAPHICSTRING type
+ case 25:
+ new_asn1_type = in_window.org.pkijs.asn1.GRAPHICSTRING;
+ break;
+ // #endregion
+ // #region VISIBLESTRING type
+ case 26:
+ new_asn1_type = in_window.org.pkijs.asn1.VISIBLESTRING;
+ break;
+ // #endregion
+ // #region GENERALSTRING type
+ case 27:
+ new_asn1_type = in_window.org.pkijs.asn1.GENERALSTRING;
+ break;
+ // #endregion
+ // #region UNIVERSALSTRING type
+ case 28:
+ new_asn1_type = in_window.org.pkijs.asn1.UNIVERSALSTRING;
+ break;
+ // #endregion
+ // #region CHARACTERSTRING type
+ case 29:
+ new_asn1_type = in_window.org.pkijs.asn1.CHARACTERSTRING;
+ break;
+ // #endregion
+ // #region BMPSTRING type
+ case 30:
+ new_asn1_type = in_window.org.pkijs.asn1.BMPSTRING;
+ break;
+ // #endregion
+ // #region DATE type
+ case 31:
+ new_asn1_type = in_window.org.pkijs.asn1.DATE;
+ break;
+ // #endregion
+ // #region TIMEOFDAY type
+ case 32:
+ new_asn1_type = in_window.org.pkijs.asn1.TIMEOFDAY;
+ break;
+ // #endregion
+ // #region DATE-TIME type
+ case 33:
+ new_asn1_type = in_window.org.pkijs.asn1.DATETIME;
+ break;
+ // #endregion
+ // #region DURATION type
+ case 34:
+ new_asn1_type = in_window.org.pkijs.asn1.DURATION;
+ break;
+ // #endregion
+ // #region default
+ default:
+ {
+ var new_object;
+
+ if(return_object.id_block.is_constructed == true)
+ new_object = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED();
+ else
+ new_object = new in_window.org.pkijs.asn1.ASN1_PRIMITIVE();
+
+ new_object.id_block = return_object.id_block;
+ new_object.len_block = return_object.len_block;
+ new_object.warnings = return_object.warnings;
+
+ return_object = new_object;
+
+ result_offset = return_object.fromBER(input_buffer, input_offset, input_length);
+ }
+ // #endregion
+ }
+ break;
+ // #endregion
+ // #region All other tag classes
+ case 2: // APPLICATION
+ case 3: // CONTEXT-SPECIFIC
+ case 4: // PRIVATE
+ default:
+ {
+ if(return_object.id_block.is_constructed == true)
+ new_asn1_type = in_window.org.pkijs.asn1.ASN1_CONSTRUCTED;
+ else
+ new_asn1_type = in_window.org.pkijs.asn1.ASN1_PRIMITIVE;
+ }
+ // #endregion
+ }
+ // #endregion
+
+ // #region Change type and perform BER decoding
+ return_object = local_change_type(return_object, new_asn1_type);
+ result_offset = return_object.fromBER(input_buffer, input_offset, (return_object.len_block.is_indefinite_form == true) ? input_length : return_object.len_block.length);
+ // #endregion
+
+ // #region Coping incoming buffer for entire ASN.1 block
+ return_object.value_before_decode = util_copybuf_offset(input_buffer, incoming_offset, return_object.block_length);
+ // #endregion
+
+ return {
+ offset: result_offset,
+ result: return_object
+ };
+ }
+ //**************************************************************************************
+ in_window.org.pkijs.fromBER =
+ function(input_buffer)
+ {
+ /// <summary>Major function for decoding ASN.1 BER array into internal library structuries</summary>
+ /// <param name="input_buffer" type="ArrayBuffer">ASN.1 BER encoded array of bytes</param>
+
+ if(input_buffer.byteLength == 0)
+ {
+ var result = new in_window.org.pkijs.asn1.ASN1_block();
+ result.error = "Input buffer has zero length";
+
+ return {
+ offset: (-1),
+ result: result
+ };
+ }
+
+ return fromBER_raw(input_buffer, 0, input_buffer.byteLength);
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Major scheme verification function
+ //**************************************************************************************
+ in_window.org.pkijs.compareSchema =
+ function(root, input_asn1_data, input_asn1_schema)
+ {
+ // #region Special case for CHOICE schema element type
+ if(input_asn1_schema instanceof in_window.org.pkijs.asn1.CHOICE)
+ {
+ var choice_result = false;
+
+ for(var j = 0; j < input_asn1_schema.value.length; j++)
+ {
+ var result = in_window.org.pkijs.compareSchema(root, input_asn1_data, input_asn1_schema.value[j]);
+ if(result.verified === true)
+ return {
+ verified: true,
+ result: root
+ };
+ }
+
+ if(choice_result === false)
+ {
+ var _result = {
+ verified: false,
+ result: {
+ error: "Wrong values for CHOICE type"
+ }
+ };
+
+ if(input_asn1_schema.hasOwnProperty('name'))
+ _result.name = input_asn1_schema.name;
+
+ return _result;
+ }
+ }
+ // #endregion
+
+ // #region Special case for ANY schema element type
+ if(input_asn1_schema instanceof in_window.org.pkijs.asn1.ANY)
+ {
+ // #region Add named component of ASN.1 schema
+ if(input_asn1_schema.hasOwnProperty('name'))
+ root[input_asn1_schema.name] = input_asn1_data;
+ // #endregion
+
+ return {
+ verified: true,
+ result: root
+ };
+ }
+ // #endregion
+
+ // #region Initial check
+ if((root instanceof Object) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong root object" }
+ };
+
+ if((input_asn1_data instanceof Object) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 data" }
+ };
+
+ if((input_asn1_schema instanceof Object) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(('id_block' in input_asn1_schema) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+ // #endregion
+
+ // #region Comparing id_block properties in ASN.1 data and ASN.1 schema
+ // #region Encode and decode ASN.1 schema id_block
+ /// <remarks>This encoding/decoding is neccessary because could be an errors in schema definition</remarks>
+ if(('fromBER' in input_asn1_schema.id_block) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(('toBER' in input_asn1_schema.id_block) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ var encoded_id = input_asn1_schema.id_block.toBER(false);
+ if(encoded_id.byteLength === 0)
+ return {
+ verified: false,
+ result: { error: "Error encoding id_block for ASN.1 schema" }
+ };
+
+ var decoded_offset = input_asn1_schema.id_block.fromBER(encoded_id, 0, encoded_id.byteLength);
+ if(decoded_offset === (-1))
+ return {
+ verified: false,
+ result: { error: "Error decoding id_block for ASN.1 schema" }
+ };
+ // #endregion
+
+ // #region tag_class
+ if(input_asn1_schema.id_block.hasOwnProperty('tag_class') === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(input_asn1_schema.id_block.tag_class !== input_asn1_data.id_block.tag_class)
+ return {
+ verified: false,
+ result: root
+ };
+ // #endregion
+ // #region tag_number
+ if(input_asn1_schema.id_block.hasOwnProperty('tag_number') === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(input_asn1_schema.id_block.tag_number !== input_asn1_data.id_block.tag_number)
+ return {
+ verified: false,
+ result: root
+ };
+ // #endregion
+ // #region is_constructed
+ if(input_asn1_schema.id_block.hasOwnProperty('is_constructed') === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(input_asn1_schema.id_block.is_constructed !== input_asn1_data.id_block.is_constructed)
+ return {
+ verified: false,
+ result: root
+ };
+ // #endregion
+ // #region is_hex_only
+ if(('is_hex_only' in input_asn1_schema.id_block) === false) // Since 'is_hex_only' is an inhirited property
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ if(input_asn1_schema.id_block.is_hex_only !== input_asn1_data.id_block.is_hex_only)
+ return {
+ verified: false,
+ result: root
+ };
+ // #endregion
+ // #region value_hex
+ if(input_asn1_schema.id_block.is_hex_only === true)
+ {
+ if(('value_hex' in input_asn1_schema.id_block) === false) // Since 'value_hex' is an inhirited property
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema" }
+ };
+
+ var schema_view = new Uint8Array(input_asn1_schema.id_block.value_hex);
+ var asn1_view = new Uint8Array(input_asn1_data.id_block.value_hex);
+
+ if(schema_view.length !== asn1_view.length)
+ return {
+ verified: false,
+ result: root
+ };
+
+ for(var i = 0; i < schema_view.length; i++)
+ {
+ if(schema_view[i] !== asn1_view[1])
+ return {
+ verified: false,
+ result: root
+ };
+ }
+ }
+ // #endregion
+ // #endregion
+
+ // #region Add named component of ASN.1 schema
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ root[input_asn1_schema.name] = input_asn1_data;
+ }
+ // #endregion
+
+ // #region Getting next ASN.1 block for comparition
+ if(input_asn1_schema.id_block.is_constructed === true)
+ {
+ var admission = 0;
+ var result = { verified: false };
+
+ var max_length = input_asn1_schema.value_block.value.length;
+
+ if(max_length > 0)
+ {
+ if(input_asn1_schema.value_block.value[0] instanceof in_window.org.pkijs.asn1.REPEATED)
+ max_length = input_asn1_data.value_block.value.length;
+ }
+
+ // #region Special case when constructive value has no elements
+ if(max_length === 0)
+ return {
+ verified: true,
+ result: root
+ };
+ // #endregion
+
+ // #region Special case when "input_asn1_data" has no values and "input_asn1_schema" has all optional values
+ if((input_asn1_data.value_block.value.length === 0) &&
+ (input_asn1_schema.value_block.value.length !== 0))
+ {
+ var _optional = true;
+
+ for(var i = 0; i < input_asn1_schema.value_block.value.length; i++)
+ _optional = _optional && (input_asn1_schema.value_block.value[i].optional || false);
+
+ if(_optional === true)
+ {
+ return {
+ verified: true,
+ result: root
+ };
+ }
+ else
+ {
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ delete root[input_asn1_schema.name];
+ }
+ // #endregion
+
+ root.error = "Inconsistent object length";
+
+ return {
+ verified: false,
+ result: root
+ };
+ }
+ }
+ // #endregion
+
+ for(var i = 0; i < max_length; i++)
+ {
+ // #region Special case when there is an "optional" element of ASN.1 schema at the end
+ if((i - admission) >= input_asn1_data.value_block.value.length)
+ {
+ if(input_asn1_schema.value_block.value[i].optional === false)
+ {
+ var _result = {
+ verified: false,
+ result: root
+ };
+
+ root.error = "Inconsistent length between ASN.1 data and schema";
+
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ {
+ delete root[input_asn1_schema.name];
+ _result.name = input_asn1_schema.name;
+ }
+ }
+ // #endregion
+
+ return _result;
+ }
+ }
+ // #endregion
+ else
+ {
+ // #region Special case for REPEATED type of ASN.1 schema element
+ if(input_asn1_schema.value_block.value[0] instanceof in_window.org.pkijs.asn1.REPEATED)
+ {
+ result = in_window.org.pkijs.compareSchema(root, input_asn1_data.value_block.value[i], input_asn1_schema.value_block.value[0].value);
+ if(result.verified === false)
+ {
+ if(input_asn1_schema.value_block.value[0].optional === true)
+ admission++;
+ else
+ {
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ delete root[input_asn1_schema.name];
+ }
+ // #endregion
+
+ return result;
+ }
+ }
+
+ if(("name" in input_asn1_schema.value_block.value[0]) && (input_asn1_schema.value_block.value[0].name.length > 0))
+ {
+ var array_root = {};
+
+ if(("local" in input_asn1_schema.value_block.value[0]) && (input_asn1_schema.value_block.value[0].local === true))
+ array_root = input_asn1_data;
+ else
+ array_root = root;
+
+ if(typeof array_root[input_asn1_schema.value_block.value[0].name] === "undefined")
+ array_root[input_asn1_schema.value_block.value[0].name] = new Array();
+
+ array_root[input_asn1_schema.value_block.value[0].name].push(input_asn1_data.value_block.value[i]);
+ }
+ }
+ // #endregion
+ else
+ {
+ result = in_window.org.pkijs.compareSchema(root, input_asn1_data.value_block.value[i - admission], input_asn1_schema.value_block.value[i]);
+ if(result.verified === false)
+ {
+ if(input_asn1_schema.value_block.value[i].optional === true)
+ admission++;
+ else
+ {
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ delete root[input_asn1_schema.name];
+ }
+ // #endregion
+
+ return result;
+ }
+ }
+ }
+ }
+ }
+
+ if(result.verified === false) // The situation may take place if last element is "optional" and verification failed
+ {
+ var _result = {
+ verified: false,
+ result: root
+ };
+
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ {
+ delete root[input_asn1_schema.name];
+ _result.name = input_asn1_schema.name;
+ }
+ }
+ // #endregion
+
+ return _result;
+ }
+
+ return {
+ verified: true,
+ result: root
+ };
+ }
+ // #endregion
+ // #region Ability to parse internal value for primitive-encoded value (value of OCTETSTRING, for example)
+ else
+ {
+ if( ("primitive_schema" in input_asn1_schema) &&
+ ("value_hex" in input_asn1_data.value_block) )
+ {
+ // #region Decoding of raw ASN.1 data
+ var asn1 = in_window.org.pkijs.fromBER(input_asn1_data.value_block.value_hex);
+ if(asn1.offset === (-1))
+ {
+ var _result = {
+ verified: false,
+ result: asn1.result
+ };
+
+ // #region Delete early added name of block
+ if(input_asn1_schema.hasOwnProperty('name'))
+ {
+ input_asn1_schema.name = input_asn1_schema.name.replace(/^\s+|\s+$/g, '');
+ if(input_asn1_schema.name !== "")
+ {
+ delete root[input_asn1_schema.name];
+ _result.name = input_asn1_schema.name;
+ }
+ }
+ // #endregion
+
+ return _result;
+ }
+ // #endregion
+
+ return in_window.org.pkijs.compareSchema(root, asn1.result, input_asn1_schema.primitive_schema);
+ }
+ else
+ return {
+ verified: true,
+ result: root
+ };
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.verifySchema =
+ function(input_buffer, input_schema)
+ {
+ // #region Initial check
+ if((input_schema instanceof Object) === false)
+ return {
+ verified: false,
+ result: { error: "Wrong ASN.1 schema type" }
+ };
+ // #endregion
+
+ // #region Decoding of raw ASN.1 data
+ var asn1 = in_window.org.pkijs.fromBER(input_buffer);
+ if(asn1.offset === (-1))
+ return {
+ verified: false,
+ result: asn1.result
+ };
+ // #endregion
+
+ // #region Compare ASN.1 struct with input schema
+ return in_window.org.pkijs.compareSchema(asn1.result, asn1.result, input_schema);
+ // #endregion
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Major function converting JSON to ASN.1 objects
+ //**************************************************************************************
+ in_window.org.pkijs.fromJSON =
+ function(json)
+ {
+ /// <summary>Converting from JSON to ASN.1 objects</summary>
+ /// <param name="json" type="String|Object">JSON string or object to convert to ASN.1 objects</param>
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+}
+)(typeof exports !== "undefined" ? exports : window); \ No newline at end of file
diff --git a/dom/webauthn/tests/pkijs/common.js b/dom/webauthn/tests/pkijs/common.js
new file mode 100644
index 0000000000..1bc9eda7f8
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/common.js
@@ -0,0 +1,1542 @@
+/*
+ * Copyright (c) 2014, GMO GlobalSign
+ * Copyright (c) 2015, Peculiar Ventures
+ * All rights reserved.
+ *
+ * Author 2014-2015, Yury Strozhevsky <www.strozhevsky.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ */
+(
+function(in_window)
+{
+ //**************************************************************************************
+ // #region Declaration of global variables
+ //**************************************************************************************
+ // #region "org" namespace
+ if(typeof in_window.org === "undefined")
+ in_window.org = {};
+ else
+ {
+ if(typeof in_window.org !== "object")
+ throw new Error("Name org already exists and it's not an object");
+ }
+ // #endregion
+
+ // #region "org.pkijs" namespace
+ if(typeof in_window.org.pkijs === "undefined")
+ in_window.org.pkijs = {};
+ else
+ {
+ if(typeof in_window.org.pkijs !== "object")
+ throw new Error("Name org.pkijs already exists and it's not an object" + " but " + (typeof in_window.org.pkijs));
+ }
+ // #endregion
+
+ // #region "local" namespace
+ var local = {};
+ // #endregion
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Settings for "crypto engine"
+ //**************************************************************************************
+ local.engine = {
+ name: "none",
+ crypto: null,
+ subtle: null
+ };
+
+ if(typeof window != "undefined")
+ {
+ if("crypto" in window)
+ {
+ var engineName = "webcrypto";
+ var cryptoObject = window.crypto;
+ var subtleObject = null;
+
+ // Apple Safari support
+ if("webkitSubtle" in window.crypto)
+ subtleObject = window.crypto.webkitSubtle;
+
+ if("subtle" in window.crypto)
+ subtleObject = window.crypto.subtle;
+
+ local.engine = {
+ name: engineName,
+ crypto: cryptoObject,
+ subtle: subtleObject
+ };
+ }
+ }
+ //**************************************************************************************
+ in_window.org.pkijs.setEngine =
+ function(name, crypto, subtle)
+ {
+ /// <summary>Setting the global "crypto engine" parameters</summary>
+ /// <param name="name" type="String">Auxiliary name for "crypto engine"</param>
+ /// <param name="crypto" type="Object">Object handling all root cryptographic requests (in fact currently it must handle only "getRandomValues")</param>
+ /// <param name="subtle" type="Object">Object handling all main cryptographic requests</param>
+
+ local.engine = {
+ name: name,
+ crypto: crypto,
+ subtle: subtle
+ };
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getEngine =
+ function()
+ {
+ return local.engine;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Declaration of common functions
+ //**************************************************************************************
+ in_window.org.pkijs.emptyObject =
+ function()
+ {
+ this.toJSON = function()
+ {
+ return {};
+ };
+ this.toSchema = function()
+ {
+ return {};
+ };
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getNames =
+ function(arg)
+ {
+ /// <summary>Get correct "names" array for all "schema" objects</summary>
+
+ var names = {};
+
+ if(arg instanceof Object)
+ names = (arg.names || {});
+
+ return names;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.inheriteObjectFields =
+ function(from)
+ {
+ for(var i in from.prototype)
+ {
+ if(typeof from.prototype[i] === "function")
+ continue;
+
+ this[i] = from.prototype[i];
+ }
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getUTCDate =
+ function(date)
+ {
+ /// <summary>Making UTC date from local date</summary>
+ /// <param name="date" type="Date">Date to convert from</param>
+
+ var current_date = date;
+ return new Date(current_date.getTime() + (current_date.getTimezoneOffset() * 60000));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.padNumber =
+ function(input_number, full_length)
+ {
+ var str = input_number.toString(10);
+ var dif = full_length - str.length;
+
+ var padding = new Array(dif);
+ for(var i = 0; i < dif; i++)
+ padding[i] = '0';
+
+ var padding_string = padding.join('');
+
+ return padding_string.concat(str);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getValue =
+ function(args, item, default_value)
+ {
+ if(item in args)
+ return args[item];
+ else
+ return default_value;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.isEqual_view =
+ function(input_view1, input_view2)
+ {
+ /// <summary>Compare two Uint8Arrays</summary>
+ /// <param name="input_view1" type="Uint8Array">First Uint8Array for comparision</param>
+ /// <param name="input_view2" type="Uint8Array">Second Uint8Array for comparision</param>
+
+ if(input_view1.length !== input_view2.length)
+ return false;
+
+ for(var i = 0; i < input_view1.length; i++)
+ {
+ if(input_view1[i] != input_view2[i])
+ return false;
+ }
+
+ return true;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.isEqual_buffer =
+ function(input_buffer1, input_buffer2)
+ {
+ /// <summary>Compare two array buffers</summary>
+ /// <param name="input_buffer1" type="ArrayBuffer">First ArrayBuffer for comparision</param>
+ /// <param name="input_buffer2" type="ArrayBuffer">Second ArrayBuffer for comparision</param>
+
+ if(input_buffer1.byteLength != input_buffer2.byteLength)
+ return false;
+
+ var view1 = new Uint8Array(input_buffer1);
+ var view2 = new Uint8Array(input_buffer2);
+
+ return in_window.org.pkijs.isEqual_view(view1, view2);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.concat_buffers =
+ function(input_buf1, input_buf2)
+ {
+ /// <summary>Concatenate two ArrayBuffers</summary>
+ /// <param name="input_buf1" type="ArrayBuffer">First ArrayBuffer (first part of concatenated array)</param>
+ /// <param name="input_buf2" type="ArrayBuffer">Second ArrayBuffer (second part of concatenated array)</param>
+
+ var input_view1 = new Uint8Array(input_buf1);
+ var input_view2 = new Uint8Array(input_buf2);
+
+ var ret_buf = new ArrayBuffer(input_buf1.byteLength + input_buf2.byteLength);
+ var ret_view = new Uint8Array(ret_buf);
+
+ for(var i = 0; i < input_buf1.byteLength; i++)
+ ret_view[i] = input_view1[i];
+
+ for(var j = 0; j < input_buf2.byteLength; j++)
+ ret_view[input_buf1.byteLength + j] = input_view2[j];
+
+ return ret_buf;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.copyBuffer =
+ function(input_buffer)
+ {
+ var result = new ArrayBuffer(input_buffer.byteLength);
+
+ var resultView = new Uint8Array(result);
+ var inputView = new Uint8Array(input_buffer);
+
+ for(var i = 0; i < inputView.length; i++)
+ resultView[i] = inputView[i];
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getCrypto =
+ function()
+ {
+ var crypto_temp;
+
+ if(local.engine.subtle !== null)
+ crypto_temp = local.engine.subtle;
+
+ return crypto_temp;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.stringPrep =
+ function(input_string)
+ {
+ /// <summary>String preparation function. In a future here will be realization of algorithm from RFC4518.</summary>
+ /// <param name="input_string" type="String">JavaScript string. As soon as for each ASN.1 string type we have a specific transformation function here we will work with pure JavaScript string</param>
+ /// <returns type="String">Formated string</returns>
+
+ var result = input_string.replace(/^\s+|\s+$/g, ""); // Trim input string
+ result = result.replace(/\s+/g, " "); // Change all sequence of SPACE down to SPACE char
+ result = result.toLowerCase();
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.bufferToHexCodes =
+ function(input_buffer, input_offset, input_lenght)
+ {
+ var result = "";
+
+ var int_buffer = new Uint8Array(input_buffer, input_offset, input_lenght);
+
+ for(var i = 0; i < int_buffer.length; i++)
+ {
+ var str = int_buffer[i].toString(16).toUpperCase();
+ result = result + ((str.length === 1) ? "0" : "") + str;
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.bufferFromHexCodes =
+ function(hexString)
+ {
+ /// <summary>Create an ArrayBuffer from string having hexdecimal codes</summary>
+ /// <param name="hexString" type="String">String to create ArrayBuffer from</param>
+
+ // #region Initial variables
+ var stringLength = hexString.length;
+
+ var resultBuffer = new ArrayBuffer(stringLength >> 1);
+ var resultView = new Uint8Array(resultBuffer);
+
+ var hex_map = {};
+
+ hex_map['0'] = 0x00;
+ hex_map['1'] = 0x01;
+ hex_map['2'] = 0x02;
+ hex_map['3'] = 0x03;
+ hex_map['4'] = 0x04;
+ hex_map['5'] = 0x05;
+ hex_map['6'] = 0x06;
+ hex_map['7'] = 0x07;
+ hex_map['8'] = 0x08;
+ hex_map['9'] = 0x09;
+ hex_map['A'] = 0x0A;
+ hex_map['a'] = 0x0A;
+ hex_map['B'] = 0x0B;
+ hex_map['b'] = 0x0B;
+ hex_map['C'] = 0x0C;
+ hex_map['c'] = 0x0C;
+ hex_map['D'] = 0x0D;
+ hex_map['d'] = 0x0D;
+ hex_map['E'] = 0x0E;
+ hex_map['e'] = 0x0E;
+ hex_map['F'] = 0x0F;
+ hex_map['f'] = 0x0F;
+
+ var j = 0;
+ var temp = 0x00;
+ // #endregion
+
+ // #region Convert char-by-char
+ for(var i = 0; i < stringLength; i++)
+ {
+ if(!(i % 2))
+ temp = hex_map[hexString.charAt(i)] << 4;
+ else
+ {
+ temp |= hex_map[hexString.charAt(i)];
+
+ resultView[j] = temp;
+ j++;
+ }
+ }
+ // #endregion
+
+ return resultBuffer;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getRandomValues =
+ function(view)
+ {
+ /// <param name="view" type="Uint8Array">New array which gives a length for random value</param>
+
+ if(local.engine.crypto !== null)
+ return local.engine.crypto.getRandomValues(view);
+ else
+ throw new Error("No support for Web Cryptography API");
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getAlgorithmParameters =
+ function(algorithmName, operation)
+ {
+ /// <param name="algorithmName" type="String">Algorithm name to get common parameters for</param>
+ /// <param name="operation" type="String">Kind of operation: "sign", "encrypt", "generatekey", "importkey", "exportkey", "verify"</param>
+
+ var result = {
+ algorithm: {},
+ usages: []
+ };
+
+ switch(algorithmName.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ switch(operation.toLowerCase())
+ {
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "verify":
+ case "sign":
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "RSA-PSS":
+ switch(operation.toLowerCase())
+ {
+ case "sign":
+ case "verify":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ hash: {
+ name: "SHA-1"
+ },
+ saltLength: 20
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-1"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ hash: {
+ name: "SHA-1"
+ }
+ },
+ usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSA-PSS"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "RSA-OAEP":
+ switch(operation.toLowerCase())
+ {
+ case "encrypt":
+ case "decrypt":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP"
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ break;
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8"
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSA-OAEP"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "ECDSA":
+ switch(operation.toLowerCase())
+ {
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ namedCurve: "P-256"
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ namedCurve: "P-256"
+ },
+ usages: ["verify"] // "sign" for "pkcs8"
+ };
+ break;
+ case "verify":
+ case "sign":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "ECDSA"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "ECDH":
+ switch(operation.toLowerCase())
+ {
+ case "exportkey":
+ case "importkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "ECDH",
+ namedCurve: "P-256"
+ },
+ usages: ["deriveKey", "deriveBits"]
+ };
+ break;
+ case "derivekey":
+ case "derivebits":
+ result = {
+ algorithm: {
+ name: "ECDH",
+ namedCurve: "P-256",
+ public: [] // Must be a "publicKey"
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "ECDH"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-CTR":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-CTR",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-CTR",
+ counter: new Uint8Array(16),
+ length: 10
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-CTR"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-CBC":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-CBC",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-CBC",
+ iv: in_window.org.pkijs.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-CBC"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-GCM":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-GCM",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-GCM",
+ iv: in_window.org.pkijs.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-GCM"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-KW":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ case "wrapkey":
+ case "unwrapkey":
+ result = {
+ algorithm: {
+ name: "AES-KW",
+ length: 256
+ },
+ usages: ["wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-KW"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "HMAC":
+ switch(operation.toLowerCase())
+ {
+ case "sign":
+ case "verify":
+ result = {
+ algorithm: {
+ name: "HMAC"
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "HMAC",
+ length: 32,
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "HMAC"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "HKDF":
+ switch(operation.toLowerCase())
+ {
+ case "derivekey":
+ result = {
+ algorithm: {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array(),
+ info: new Uint8Array()
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "HKDF"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "PBKDF2":
+ switch(operation.toLowerCase())
+ {
+ case "derivekey":
+ result = {
+ algorithm: {
+ name: "PBKDF2",
+ hash: { name: "SHA-256" },
+ salt: new Uint8Array(),
+ iterations: 1000
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "PBKDF2"
+ },
+ usages: []
+ };
+ }
+ break;
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getOIDByAlgorithm =
+ function(algorithm)
+ {
+ /// <summary>Get OID for each specific WebCrypto algorithm</summary>
+ /// <param name="algorithm" type="Object">WebCrypto algorithm</param>
+
+ var result = "";
+
+ switch(algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.113549.1.1.5";
+ break;
+ case "SHA-256":
+ result = "1.2.840.113549.1.1.11";
+ break;
+ case "SHA-384":
+ result = "1.2.840.113549.1.1.12";
+ break;
+ case "SHA-512":
+ result = "1.2.840.113549.1.1.13";
+ break;
+ default:
+ }
+ break;
+ case "RSA-PSS":
+ result = "1.2.840.113549.1.1.10";
+ break;
+ case "RSA-OAEP":
+ result = "1.2.840.113549.1.1.7";
+ break;
+ case "ECDSA":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.10045.4.1";
+ break;
+ case "SHA-256":
+ result = "1.2.840.10045.4.3.2";
+ break;
+ case "SHA-384":
+ result = "1.2.840.10045.4.3.3";
+ break;
+ case "SHA-512":
+ result = "1.2.840.10045.4.3.4";
+ break;
+ default:
+ }
+ break;
+ case "ECDH":
+ switch(algorithm.kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function
+ {
+ case "SHA-1":
+ result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme
+ break;
+ case "SHA-256":
+ result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme
+ break;
+ case "SHA-384":
+ result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme
+ break;
+ case "SHA-512":
+ result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme
+ break;
+ default:
+ }
+ break;
+ case "AES-CTR":
+ break;
+ case "AES-CBC":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.2";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.22";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.42";
+ break;
+ default:
+ }
+ break;
+ case "AES-CMAC":
+ break;
+ case "AES-GCM":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.6";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.26";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.46";
+ break;
+ default:
+ }
+ break;
+ case "AES-CFB":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.4";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.24";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.44";
+ break;
+ default:
+ }
+ break;
+ case "AES-KW":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.5";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.25";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.45";
+ break;
+ default:
+ }
+ break;
+ case "HMAC":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.113549.2.7";
+ break;
+ case "SHA-256":
+ result = "1.2.840.113549.2.9";
+ break;
+ case "SHA-384":
+ result = "1.2.840.113549.2.10";
+ break;
+ case "SHA-512":
+ result = "1.2.840.113549.2.11";
+ break;
+ default:
+ }
+ break;
+ case "DH":
+ result = "1.2.840.113549.1.9.16.3.5";
+ break;
+ case "SHA-1":
+ result = "1.3.14.3.2.26";
+ break;
+ case "SHA-256":
+ result = "2.16.840.1.101.3.4.2.1";
+ break;
+ case "SHA-384":
+ result = "2.16.840.1.101.3.4.2.2";
+ break;
+ case "SHA-512":
+ result = "2.16.840.1.101.3.4.2.3";
+ break;
+ case "CONCAT":
+ break;
+ case "HKDF":
+ break;
+ case "PBKDF2":
+ result = "1.2.840.113549.1.5.12";
+ break;
+ // #region Special case - OIDs for ECC curves
+ case "P-256":
+ result = "1.2.840.10045.3.1.7";
+ break;
+ case "P-384":
+ result = "1.3.132.0.34";
+ break;
+ case "P-521":
+ result = "1.3.132.0.35";
+ break;
+ // #endregion
+
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getAlgorithmByOID =
+ function(oid)
+ {
+ /// <summary>Get WebCrypto algorithm by wel-known OID</summary>
+ /// <param name="oid" type="String">Wel-known OID to search for</param>
+
+ var result = {};
+
+ switch(oid)
+ {
+ case "1.2.840.113549.1.1.5":
+ result = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ break;
+ case "1.2.840.113549.1.1.11":
+ result = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ break;
+ case "1.2.840.113549.1.1.12":
+ result = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ break;
+ case "1.2.840.113549.1.1.13":
+ result = {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ break;
+ case "1.2.840.113549.1.1.10":
+ result = {
+ name: "RSA-PSS"
+ };
+ break;
+ case "1.2.840.113549.1.1.7":
+ result = {
+ name: "RSA-OAEP"
+ };
+ break;
+ case "1.2.840.10045.4.1":
+ result = {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ break;
+ case "1.2.840.10045.4.3.2":
+ result = {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ break;
+ case "1.2.840.10045.4.3.3":
+ result = {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ break;
+ case "1.2.840.10045.4.3.4":
+ result = {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ break;
+ case "1.3.133.16.840.63.0.2":
+ result = {
+ name: "ECDH",
+ kdf: "SHA-1"
+ };
+ break;
+ case "1.3.132.1.11.1":
+ result = {
+ name: "ECDH",
+ kdf: "SHA-256"
+ };
+ break;
+ case "1.3.132.1.11.2":
+ result = {
+ name: "ECDH",
+ kdf: "SHA-384"
+ };
+ break;
+ case "1.3.132.1.11.3":
+ result = {
+ name: "ECDH",
+ kdf: "SHA-512"
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.2":
+ result = {
+ name: "AES-CBC",
+ length: 128
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.22":
+ result = {
+ name: "AES-CBC",
+ length: 192
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.42":
+ result = {
+ name: "AES-CBC",
+ length: 256
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.6":
+ result = {
+ name: "AES-GCM",
+ length: 128
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.26":
+ result = {
+ name: "AES-GCM",
+ length: 192
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.46":
+ result = {
+ name: "AES-GCM",
+ length: 256
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.4":
+ result = {
+ name: "AES-CFB",
+ length: 128
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.24":
+ result = {
+ name: "AES-CFB",
+ length: 192
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.44":
+ result = {
+ name: "AES-CFB",
+ length: 256
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.5":
+ result = {
+ name: "AES-KW",
+ length: 128
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.25":
+ result = {
+ name: "AES-KW",
+ length: 192
+ };
+ break;
+ case "2.16.840.1.101.3.4.1.45":
+ result = {
+ name: "AES-KW",
+ length: 256
+ };
+ break;
+ case "1.2.840.113549.2.7":
+ result = {
+ name: "HMAC",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ break;
+ case "1.2.840.113549.2.9":
+ result = {
+ name: "HMAC",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ break;
+ case "1.2.840.113549.2.10":
+ result = {
+ name: "HMAC",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ break;
+ case "1.2.840.113549.2.11":
+ result = {
+ name: "HMAC",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ break;
+ case "1.2.840.113549.1.9.16.3.5":
+ result = {
+ name: "DH"
+ };
+ break;
+ case "1.3.14.3.2.26":
+ result = {
+ name: "SHA-1"
+ };
+ break;
+ case "2.16.840.1.101.3.4.2.1":
+ result = {
+ name: "SHA-256"
+ };
+ break;
+ case "2.16.840.1.101.3.4.2.2":
+ result = {
+ name: "SHA-384"
+ };
+ break;
+ case "2.16.840.1.101.3.4.2.3":
+ result = {
+ name: "SHA-512"
+ };
+ break;
+ case "1.2.840.113549.1.5.12":
+ result = {
+ name: "PBKDF2"
+ };
+ break;
+ // #region Special case - OIDs for ECC curves
+ case "1.2.840.10045.3.1.7":
+ result = {
+ name: "P-256"
+ };
+ break;
+ case "1.3.132.0.34":
+ result = {
+ name: "P-384"
+ };
+ break;
+ case "1.3.132.0.35":
+ result = {
+ name: "P-521"
+ };
+ break;
+ // #endregion
+
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getHashAlgorithm =
+ function(signatureAlgorithm)
+ {
+ /// <summary>Getting hash algorithm by signature algorithm</summary>
+ /// <param name="signatureAlgorithm" type="in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER">Signature algorithm</param>
+
+ var result = "";
+
+ switch(signatureAlgorithm.algorithm_id)
+ {
+ case "1.2.840.10045.4.1": // ecdsa-with-SHA1
+ case "1.2.840.113549.1.1.5":
+ result = "SHA-1";
+ break;
+ case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
+ case "1.2.840.113549.1.1.11":
+ result = "SHA-256";
+ break;
+ case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
+ case "1.2.840.113549.1.1.12":
+ result = "SHA-384";
+ break;
+ case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
+ case "1.2.840.113549.1.1.13":
+ result = "SHA-512";
+ break;
+ case "1.2.840.113549.1.1.10": // RSA-PSS
+ {
+ var params;
+
+ try
+ {
+ params = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params({ schema: signatureAlgorithm.algorithm_params });
+ if("hashAlgorithm" in params)
+ {
+ var algorithm = in_window.org.pkijs.getAlgorithmByOID(params.hashAlgorithm.algorithm_id);
+ if(("name" in algorithm) === false)
+ return "";
+
+ result = algorithm.name;
+ }
+ else
+ result = "SHA-1";
+ }
+ catch(ex)
+ {
+ }
+ }
+ break;
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.createCMSECDSASignature =
+ function(signatureBuffer)
+ {
+ /// <summary>Create CMS ECDSA signature from WebCrypto ECDSA signature</summary>
+ /// <param name="signatureBuffer" type="ArrayBuffer">WebCrypto result of "sign" function</param>
+
+ // #region Initial check for correct length
+ if((signatureBuffer.byteLength % 2) != 0)
+ return new ArrayBuffer(0);
+ // #endregion
+
+ // #region Initial variables
+ var i = 0;
+ var length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer
+
+ var signatureView = new Uint8Array(signatureBuffer);
+
+ var r_buffer = new ArrayBuffer(length);
+ var r_view = new Uint8Array(r_buffer);
+ var r_corrected_buffer;
+ var r_corrected_view;
+
+ var s_buffer = new ArrayBuffer(length);
+ var s_view = new Uint8Array(s_buffer);
+ var s_corrected_buffer;
+ var s_corrected_view;
+ // #endregion
+
+ // #region Get "r" part of ECDSA signature
+ for(; i < length; i++)
+ r_view[i] = signatureView[i];
+
+ if(r_view[0] & 0x80)
+ {
+ r_corrected_buffer = new ArrayBuffer(length + 1);
+ r_corrected_view = new Uint8Array(r_corrected_buffer);
+
+ r_corrected_view[0] = 0x00;
+
+ for(var j = 0; j < length; j++)
+ r_corrected_view[j + 1] = r_view[j];
+ }
+ else
+ {
+ r_corrected_buffer = r_buffer;
+ r_corrected_view = r_view;
+ }
+ // #endregion
+
+ // #region Get "s" part of ECDSA signature
+ for(; i < signatureBuffer.byteLength; i++)
+ s_view[i - length] = signatureView[i];
+
+
+ if(s_view[0] & 0x80)
+ {
+ s_corrected_buffer = new ArrayBuffer(length + 1);
+ s_corrected_view = new Uint8Array(s_corrected_buffer);
+
+ s_corrected_view[0] = 0x00;
+
+ for(var j = 0; j < length; j++)
+ s_corrected_view[j + 1] = s_view[j];
+ }
+ else
+ {
+ s_corrected_buffer = s_buffer;
+ s_corrected_view = s_view;
+ }
+ // #endregion
+
+ // #region Create ASN.1 structure of CMS ECDSA signature
+ var r_integer = new in_window.org.pkijs.asn1.INTEGER();
+ r_integer.value_block.is_hex_only = true;
+ r_integer.value_block.value_hex = in_window.org.pkijs.copyBuffer(r_corrected_buffer);
+
+ var s_integer = new in_window.org.pkijs.asn1.INTEGER();
+ s_integer.value_block.is_hex_only = true;
+ s_integer.value_block.value_hex = in_window.org.pkijs.copyBuffer(s_corrected_buffer);
+
+ var asn1 = new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ r_integer,
+ s_integer
+ ]
+ });
+ // #endregion
+
+ return asn1.toBER(false);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.createECDSASignatureFromCMS =
+ function(cmsSignature)
+ {
+ /// <summary>Create a single ArrayBuffer from CMS ECDSA signature</summary>
+ /// <param name="cmsSignature" type="in_window.org.pkijs.asn1.SEQUENCE">ASN.1 SEQUENCE contains CMS ECDSA signature</param>
+
+ // #region Initial variables
+ var length = 0;
+
+ var r_start = 0;
+ var s_start = 0;
+
+ var r_length = cmsSignature.value_block.value[0].value_block.value_hex.byteLength;
+ var s_length = cmsSignature.value_block.value[1].value_block.value_hex.byteLength;
+ // #endregion
+
+ // #region Get length of final "ArrayBuffer"
+ var r_view = new Uint8Array(cmsSignature.value_block.value[0].value_block.value_hex);
+ if((r_view[0] === 0x00) && (r_view[1] & 0x80))
+ {
+ length = r_length - 1;
+ r_start = 1;
+ }
+ else
+ length = r_length;
+
+ var s_view = new Uint8Array(cmsSignature.value_block.value[1].value_block.value_hex);
+ if((s_view[0] === 0x00) && (s_view[1] & 0x80))
+ {
+ length += s_length - 1;
+ s_start = 1;
+ }
+ else
+ length += s_length;
+ // #endregion
+
+ // #region Copy values from CMS ECDSA signature
+ var result = new ArrayBuffer(length);
+ var result_view = new Uint8Array(result);
+
+ for(var i = r_start; i < r_length; i++)
+ result_view[i - r_start] = r_view[i];
+
+ for(var i = s_start; i < s_length; i++)
+ result_view[i - s_start + r_length - r_start] = s_view[i];
+ // #endregion
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getEncryptionAlgorithm =
+ function(algorithm)
+ {
+ /// <summary>Get encryption algorithm OID by WebCrypto algorithm's object</summary>
+ /// <param name="algorithm" type="WebCryptoAlgorithm">WebCrypto algorithm object</param>
+
+ var result = "";
+
+ switch(algorithm.name.toUpperCase())
+ {
+ case "AES-CBC":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.2";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.22";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.42";
+ break;
+ default:
+ }
+ break;
+ case "AES-GCM":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.6";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.26";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.46";
+ break;
+ default:
+ }
+ break;
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.getAlgorithmByEncryptionOID =
+ function(oid)
+ {
+ /// <summary>Get encryption algorithm name by OID</summary>
+ /// <param name="oid" type="String">OID of encryption algorithm</param>
+
+ var result = "";
+
+ switch(oid)
+ {
+ case "2.16.840.1.101.3.4.1.2":
+ case "2.16.840.1.101.3.4.1.22":
+ case "2.16.840.1.101.3.4.1.42":
+ result = "AES-CBC";
+ break;
+ case "2.16.840.1.101.3.4.1.6":
+ case "2.16.840.1.101.3.4.1.26":
+ case "2.16.840.1.101.3.4.1.46":
+ result = "AES-GCM";
+ break;
+ default:
+ }
+
+ return result;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+}
+)(typeof exports !== "undefined" ? exports : window); \ No newline at end of file
diff --git a/dom/webauthn/tests/pkijs/x509_schema.js b/dom/webauthn/tests/pkijs/x509_schema.js
new file mode 100644
index 0000000000..15b0929bb5
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/x509_schema.js
@@ -0,0 +1,1889 @@
+/*
+ * Copyright (c) 2014, GMO GlobalSign
+ * Copyright (c) 2015, Peculiar Ventures
+ * All rights reserved.
+ *
+ * Author 2014-2015, Yury Strozhevsky <www.strozhevsky.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ */
+(
+function(in_window)
+{
+ //**************************************************************************************
+ // #region Declaration of global variables
+ //**************************************************************************************
+ // #region "org" namespace
+ if(typeof in_window.org === "undefined")
+ in_window.org = {};
+ else
+ {
+ if(typeof in_window.org !== "object")
+ throw new Error("Name org already exists and it's not an object");
+ }
+ // #endregion
+
+ // #region "org.pkijs" namespace
+ if(typeof in_window.org.pkijs === "undefined")
+ in_window.org.pkijs = {};
+ else
+ {
+ if(typeof in_window.org.pkijs !== "object")
+ throw new Error("Name org.pkijs already exists and it's not an object" + " but " + (typeof in_window.org.pkijs));
+ }
+ // #endregion
+
+ // #region "org.pkijs.schema" namespace
+ if(typeof in_window.org.pkijs.schema === "undefined")
+ in_window.org.pkijs.schema = {};
+ else
+ {
+ if(typeof in_window.org.pkijs.schema !== "object")
+ throw new Error("Name org.pkijs.schema already exists and it's not an object" + " but " + (typeof in_window.org.pkijs.schema));
+ }
+ // #endregion
+
+ // #region "org.pkijs.schema.x509" namespace
+ if(typeof in_window.org.pkijs.schema.x509 === "undefined")
+ in_window.org.pkijs.schema.x509 = {};
+ else
+ {
+ if(typeof in_window.org.pkijs.schema.x509 !== "object")
+ throw new Error("Name org.pkijs.schema.x509 already exists and it's not an object" + " but " + (typeof in_window.org.pkijs.schema.x509));
+ }
+ // #endregion
+
+ // #region "local" namespace
+ var local = {};
+ // #endregion
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "Time" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.TIME =
+ function(input_names, input_optional)
+ {
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+ var optional = (input_optional || false);
+
+ return (new in_window.org.pkijs.asn1.CHOICE({
+ optional: optional,
+ value: [
+ new in_window.org.pkijs.asn1.UTCTIME({ name: (names.utcTimeName || "") }),
+ new in_window.org.pkijs.asn1.GENERALIZEDTIME({ name: (names.generalTimeName || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for X.509 v3 certificate (RFC5280)
+ //**************************************************************************************
+ local.tbsCertificate =
+ function()
+ {
+ //TBSCertificate ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version MUST be v2 or v3
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version MUST be v2 or v3
+ // extensions [3] EXPLICIT Extensions OPTIONAL
+ // -- If present, version MUST be v3
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || "tbsCertificate"),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.tbsCertificate_version || "tbsCertificate.version") }) // EXPLICIT integer value
+ ]
+ }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.tbsCertificate_serialNumber || "tbsCertificate.serialNumber") }),
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.signature || {
+ names: {
+ block_name: "tbsCertificate.signature"
+ }
+ }),
+ in_window.org.pkijs.schema.RDN(names.issuer || {
+ names: {
+ block_name: "tbsCertificate.issuer"
+ }
+ }),
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.tbsCertificate_validity || "tbsCertificate.validity"),
+ value: [
+ in_window.org.pkijs.schema.TIME(names.not_before || {
+ names: {
+ utcTimeName: "tbsCertificate.notBefore",
+ generalTimeName: "tbsCertificate.notBefore"
+ }
+ }),
+ in_window.org.pkijs.schema.TIME(names.not_after || {
+ names: {
+ utcTimeName: "tbsCertificate.notAfter",
+ generalTimeName: "tbsCertificate.notAfter"
+ }
+ })
+ ]
+ }),
+ in_window.org.pkijs.schema.RDN(names.subject || {
+ names: {
+ block_name: "tbsCertificate.subject"
+ }
+ }),
+ in_window.org.pkijs.schema.PUBLIC_KEY_INFO(names.subjectPublicKeyInfo || {
+ names: {
+ block_name: "tbsCertificate.subjectPublicKeyInfo"
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.tbsCertificate_issuerUniqueID ||"tbsCertificate.issuerUniqueID"),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ }), // IMPLICIT bistring value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.tbsCertificate_subjectUniqueID ||"tbsCertificate.subjectUniqueID"),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ }
+ }), // IMPLICIT bistring value
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ value: [in_window.org.pkijs.schema.EXTENSIONS(names.extensions || {
+ names: {
+ block_name: "tbsCertificate.extensions"
+ }
+ })]
+ }) // EXPLICIT SEQUENCE value
+ ]
+ }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.schema.CERT =
+ function()
+ {
+ //Certificate ::= SEQUENCE {
+ // tbsCertificate TBSCertificate,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signatureValue BIT STRING }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ local.tbsCertificate(names.tbsCertificate),
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.signatureAlgorithm || {
+ names: {
+ block_name: "signatureAlgorithm"
+ }
+ }),
+ new in_window.org.pkijs.asn1.BITSTRING({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for X.509 CRL (Certificate Revocation List)(RFC5280)
+ //**************************************************************************************
+ local.tbsCertList =
+ function()
+ {
+ //TBSCertList ::= SEQUENCE {
+ // version Version OPTIONAL,
+ // -- if present, MUST be v2
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // thisUpdate Time,
+ // nextUpdate Time OPTIONAL,
+ // revokedCertificates SEQUENCE OF SEQUENCE {
+ // userCertificate CertificateSerialNumber,
+ // revocationDate Time,
+ // crlEntryExtensions Extensions OPTIONAL
+ // -- if present, version MUST be v2
+ // } OPTIONAL,
+ // crlExtensions [0] EXPLICIT Extensions OPTIONAL
+ // -- if present, version MUST be v2
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || "tbsCertList"),
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({
+ optional: true,
+ name: (names.tbsCertList_version || "tbsCertList.version"),
+ value: 2
+ }), // EXPLICIT integer value (v2)
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.signature || {
+ names: {
+ block_name: "tbsCertList.signature"
+ }
+ }),
+ in_window.org.pkijs.schema.RDN(names.issuer || {
+ names: {
+ block_name: "tbsCertList.issuer"
+ }
+ }),
+ in_window.org.pkijs.schema.TIME(names.tbsCertList_thisUpdate || {
+ names: {
+ utcTimeName: "tbsCertList.thisUpdate",
+ generalTimeName: "tbsCertList.thisUpdate"
+ }
+ }),
+ in_window.org.pkijs.schema.TIME(names.tbsCertList_thisUpdate || {
+ names: {
+ utcTimeName: "tbsCertList.nextUpdate",
+ generalTimeName: "tbsCertList.nextUpdate"
+ }
+ }, true),
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: true,
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.tbsCertList_revokedCertificates || "tbsCertList.revokedCertificates"),
+ value: new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER(),
+ in_window.org.pkijs.schema.TIME(),
+ in_window.org.pkijs.schema.EXTENSIONS({}, true)
+ ]
+ })
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [in_window.org.pkijs.schema.EXTENSIONS(names.crlExtensions || {
+ names: {
+ block_name: "tbsCertList.extensions"
+ }
+ })]
+ }) // EXPLICIT SEQUENCE value
+ ]
+ }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.schema.CRL =
+ function()
+ {
+ //CertificateList ::= SEQUENCE {
+ // tbsCertList TBSCertList,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signatureValue BIT STRING }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || "CertificateList"),
+ value: [
+ local.tbsCertList(arguments[0]),
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.signatureAlgorithm || {
+ names: {
+ block_name: "signatureAlgorithm"
+ }
+ }),
+ new in_window.org.pkijs.asn1.BITSTRING({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for PKCS#10 certificate request
+ //**************************************************************************************
+ local.CertificationRequestInfo =
+ function()
+ {
+ //CertificationRequestInfo ::= SEQUENCE {
+ // version INTEGER { v1(0) } (v1,...),
+ // subject Name,
+ // subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ // attributes [0] Attributes{{ CRIAttributes }}
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.CertificationRequestInfo || "CertificationRequestInfo"),
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.CertificationRequestInfo_version || "CertificationRequestInfo.version") }),
+ new in_window.org.pkijs.schema.RDN(names.subject || {
+ names: {
+ block_name: "CertificationRequestInfo.subject"
+ }
+ }),
+ new in_window.org.pkijs.schema.PUBLIC_KEY_INFO({
+ names: {
+ block_name: "CertificationRequestInfo.subjectPublicKeyInfo"
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ optional: true, // Because OpenSSL makes wrong "attributes" field
+ name: (names.CertificationRequestInfo_attributes || "CertificationRequestInfo.attributes"),
+ value: in_window.org.pkijs.schema.ATTRIBUTE(names.attributes || {})
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.schema.PKCS10 =
+ function()
+ {
+ //CertificationRequest ::= SEQUENCE {
+ // certificationRequestInfo CertificationRequestInfo,
+ // signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
+ // signature BIT STRING
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ local.CertificationRequestInfo(names.certificationRequestInfo || {}),
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.signatureAlgorithm || "signatureAlgorithm"),
+ value: [
+ new in_window.org.pkijs.asn1.OID(),
+ new in_window.org.pkijs.asn1.ANY({ optional: true })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.BITSTRING({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for PKCS#8 private key bag
+ //**************************************************************************************
+ in_window.org.pkijs.schema.PKCS8 =
+ function()
+ {
+ //PrivateKeyInfo ::= SEQUENCE {
+ // version Version,
+ // privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
+ // privateKey PrivateKey,
+ // attributes [0] Attributes OPTIONAL }
+ //
+ //Version ::= INTEGER {v1(0)} (v1,...)
+ //
+ //PrivateKey ::= OCTET STRING
+ //
+ //Attributes ::= SET OF Attribute
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.version || "") }),
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.privateKeyAlgorithm || ""),
+ new in_window.org.pkijs.asn1.OCTETSTRING({ name: (names.privateKey || "") }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.attributes || ""),
+ value: in_window.org.pkijs.schema.ATTRIBUTE()
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "GeneralName" type
+ //**************************************************************************************
+ local.BuiltInStandardAttributes =
+ function(optional_flag)
+ {
+ //BuiltInStandardAttributes ::= SEQUENCE {
+ // country-name CountryName OPTIONAL,
+ // administration-domain-name AdministrationDomainName OPTIONAL,
+ // network-address [0] IMPLICIT NetworkAddress OPTIONAL,
+ // terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL,
+ // private-domain-name [2] PrivateDomainName OPTIONAL,
+ // organization-name [3] IMPLICIT OrganizationName OPTIONAL,
+ // numeric-user-identifier [4] IMPLICIT NumericUserIdentifier OPTIONAL,
+ // personal-name [5] IMPLICIT PersonalName OPTIONAL,
+ // organizational-unit-names [6] IMPLICIT OrganizationalUnitNames OPTIONAL }
+
+ if(typeof optional_flag === "undefined")
+ optional_flag = false;
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: optional_flag,
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 2, // APPLICATION-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ name: (names.country_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.NUMERICSTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING()
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 2, // APPLICATION-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ name: (names.administration_domain_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.NUMERICSTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING()
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ name: (names.network_address || ""),
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ name: (names.terminal_identifier || ""),
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ name: (names.private_domain_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.NUMERICSTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING()
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ name: (names.organization_name || ""),
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ name: (names.numeric_user_identifier || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 4 // [4]
+ },
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ name: (names.personal_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 5 // [5]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ is_hex_only: true
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ name: (names.organizational_unit_names || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 6 // [6]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ value: new in_window.org.pkijs.asn1.PRINTABLESTRING()
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ local.BuiltInDomainDefinedAttributes =
+ function(optional_flag)
+ {
+ if(typeof optional_flag === "undefined")
+ optional_flag = false;
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: optional_flag,
+ value: [
+ new in_window.org.pkijs.asn1.PRINTABLESTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING()
+ ]
+ }));
+ };
+ //**************************************************************************************
+ local.ExtensionAttributes =
+ function(optional_flag)
+ {
+ if(typeof optional_flag === "undefined")
+ optional_flag = false;
+
+ return (new in_window.org.pkijs.asn1.SET({
+ optional: optional_flag,
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ is_hex_only: true
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [new in_window.org.pkijs.asn1.ANY()]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.schema.GENERAL_NAME =
+ function()
+ {
+ /// <remarks>By passing "names" array as an argument you can name each element of "GENERAL NAME" choice</remarks>
+
+ //GeneralName ::= CHOICE {
+ // otherName [0] OtherName,
+ // rfc822Name [1] IA5String,
+ // dNSName [2] IA5String,
+ // x400Address [3] ORAddress,
+ // directoryName [4] Name,
+ // ediPartyName [5] EDIPartyName,
+ // uniformResourceIdentifier [6] IA5String,
+ // iPAddress [7] OCTET STRING,
+ // registeredID [8] OBJECT IDENTIFIER }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID(),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [new in_window.org.pkijs.asn1.ANY()]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.block_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.block_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ name: (names.block_name || ""),
+ value: [
+ local.BuiltInStandardAttributes(false),
+ local.BuiltInDomainDefinedAttributes(true),
+ local.ExtensionAttributes(true)
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 4 // [4]
+ },
+ name: (names.block_name || ""),
+ value: [in_window.org.pkijs.schema.RDN(names.directoryName || {})]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 5 // [5]
+ },
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.TELETEXSTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING(),
+ new in_window.org.pkijs.asn1.UNIVERSALSTRING(),
+ new in_window.org.pkijs.asn1.UTF8STRING(),
+ new in_window.org.pkijs.asn1.BMPSTRING()
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.TELETEXSTRING(),
+ new in_window.org.pkijs.asn1.PRINTABLESTRING(),
+ new in_window.org.pkijs.asn1.UNIVERSALSTRING(),
+ new in_window.org.pkijs.asn1.UTF8STRING(),
+ new in_window.org.pkijs.asn1.BMPSTRING()
+ ]
+ })
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.block_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 6 // [6]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.block_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 7 // [7]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.block_name || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 8 // [8]
+ }
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "AlgorithmIdentifier" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER =
+ function()
+ {
+ //AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ optional: (names.optional || false),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.algorithmIdentifier || "") }),
+ new in_window.org.pkijs.asn1.ANY({ name: (names.algorithmParams || ""), optional: true })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "RSAPublicKey" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.RSAPublicKey =
+ function()
+ {
+ //RSAPublicKey ::= SEQUENCE {
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER -- e
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.modulus || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.publicExponent || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "OtherPrimeInfo" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.OtherPrimeInfo =
+ function()
+ {
+ //OtherPrimeInfo ::= SEQUENCE {
+ // prime INTEGER, -- ri
+ // exponent INTEGER, -- di
+ // coefficient INTEGER -- ti
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.prime || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.exponent || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.coefficient || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "RSAPrivateKey" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.RSAPrivateKey =
+ function()
+ {
+ //RSAPrivateKey ::= SEQUENCE {
+ // version Version,
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER, -- e
+ // privateExponent INTEGER, -- d
+ // prime1 INTEGER, -- p
+ // prime2 INTEGER, -- q
+ // exponent1 INTEGER, -- d mod (p-1)
+ // exponent2 INTEGER, -- d mod (q-1)
+ // coefficient INTEGER, -- (inverse of q) mod p
+ // otherPrimeInfos OtherPrimeInfos OPTIONAL
+ //}
+ //
+ //OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.version || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.modulus || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.publicExponent || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.privateExponent || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.prime1 || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.prime2 || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.exponent1 || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.exponent2 || "") }),
+ new in_window.org.pkijs.asn1.INTEGER({ name: (names.coefficient || "") }),
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: true,
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.otherPrimeInfos || ""),
+ value: in_window.org.pkijs.schema.x509.OtherPrimeInfo(names.otherPrimeInfo || {})
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "RSASSA-PSS-params" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.RSASSA_PSS_params =
+ function()
+ {
+ //RSASSA-PSS-params ::= SEQUENCE {
+ // hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier,
+ // maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1Identifier,
+ // saltLength [2] INTEGER DEFAULT 20,
+ // trailerField [3] INTEGER DEFAULT 1 }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ optional: true,
+ value: [in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.hashAlgorithm || {})]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ optional: true,
+ value: [in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.maskGenAlgorithm || {})]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ optional: true,
+ value: [new in_window.org.pkijs.asn1.INTEGER({ name: (names.saltLength || "") })]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ optional: true,
+ value: [new in_window.org.pkijs.asn1.INTEGER({ name: (names.trailerField || "") })]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "SubjectPublicKeyInfo" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.PUBLIC_KEY_INFO =
+ function()
+ {
+ //SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER(names.algorithm || {}),
+ new in_window.org.pkijs.asn1.BITSTRING({ name: (names.subjectPublicKey || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "Attribute" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.ATTRIBUTE =
+ function()
+ {
+ // Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ // type ATTRIBUTE.&id({IOSet}),
+ // values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.type || "") }),
+ new in_window.org.pkijs.asn1.SET({
+ name: (names.set_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.values || ""),
+ value: new in_window.org.pkijs.asn1.ANY()
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "AttributeTypeAndValue" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.ATTR_TYPE_AND_VALUE =
+ function()
+ {
+ //AttributeTypeAndValue ::= SEQUENCE {
+ // type AttributeType,
+ // value AttributeValue }
+ //
+ //AttributeType ::= OBJECT IDENTIFIER
+ //
+ //AttributeValue ::= ANY -- DEFINED BY AttributeType
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.type || "") }),
+ new in_window.org.pkijs.asn1.ANY({ name: (names.value || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "RelativeDistinguishedName" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.RDN =
+ function()
+ {
+ //RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ //
+ //RelativeDistinguishedName ::=
+ //SET SIZE (1..MAX) OF AttributeTypeAndValue
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.repeated_sequence || ""),
+ value: new in_window.org.pkijs.asn1.SET({
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.repeated_set || ""),
+ value: in_window.org.pkijs.schema.ATTR_TYPE_AND_VALUE(names.attr_type_and_value || {})
+ })
+ ]
+ })
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "Extension" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.EXTENSION =
+ function()
+ {
+ //Extension ::= SEQUENCE {
+ // extnID OBJECT IDENTIFIER,
+ // critical BOOLEAN DEFAULT FALSE,
+ // extnValue OCTET STRING
+ //}
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.extnID || "") }),
+ new in_window.org.pkijs.asn1.BOOLEAN({
+ name: (names.critical || ""),
+ optional: true
+ }),
+ new in_window.org.pkijs.asn1.OCTETSTRING({ name: (names.extnValue || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "Extensions" type (sequence of many Extension)
+ //**************************************************************************************
+ in_window.org.pkijs.schema.EXTENSIONS =
+ function(input_names, input_optional)
+ {
+ //Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+ var optional = input_optional || false;
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: optional,
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.extensions || ""),
+ value: in_window.org.pkijs.schema.EXTENSION(names.extension || {})
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "AuthorityKeyIdentifier" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.AuthorityKeyIdentifier =
+ function()
+ {
+ // AuthorityKeyIdentifier OID ::= 2.5.29.35
+ //
+ //AuthorityKeyIdentifier ::= SEQUENCE {
+ // keyIdentifier [0] KeyIdentifier OPTIONAL,
+ // authorityCertIssuer [1] GeneralNames OPTIONAL,
+ // authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
+ //
+ //KeyIdentifier ::= OCTET STRING
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.keyIdentifier || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.authorityCertIssuer || ""),
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.authorityCertSerialNumber || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ }
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PrivateKeyUsagePeriod" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PrivateKeyUsagePeriod =
+ function()
+ {
+ // PrivateKeyUsagePeriod OID ::= 2.5.29.16
+ //
+ //PrivateKeyUsagePeriod ::= SEQUENCE {
+ // notBefore [0] GeneralizedTime OPTIONAL,
+ // notAfter [1] GeneralizedTime OPTIONAL }
+ //-- either notBefore or notAfter MUST be present
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.notBefore || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ }
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.notAfter || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "IssuerAltName" and "SubjectAltName" types of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.AltName =
+ function()
+ {
+ // SubjectAltName OID ::= 2.5.29.17
+ // IssuerAltName OID ::= 2.5.29.18
+ //
+ // AltName ::= GeneralNames
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.altNames || ""),
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "SubjectDirectoryAttributes" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.SubjectDirectoryAttributes =
+ function()
+ {
+ // SubjectDirectoryAttributes OID ::= 2.5.29.9
+ //
+ //SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.attributes || ""),
+ value: in_window.org.pkijs.schema.ATTRIBUTE()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "GeneralSubtree" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.GeneralSubtree =
+ function()
+ {
+ //GeneralSubtree ::= SEQUENCE {
+ // base GeneralName,
+ // minimum [0] BaseDistance DEFAULT 0,
+ // maximum [1] BaseDistance OPTIONAL }
+ //
+ //BaseDistance ::= INTEGER (0..MAX)
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ in_window.org.pkijs.schema.GENERAL_NAME(names.base || ""),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [new in_window.org.pkijs.asn1.INTEGER({ name: (names.minimum || "") })]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [new in_window.org.pkijs.asn1.INTEGER({ name: (names.maximum || "") })]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "NameConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.NameConstraints =
+ function()
+ {
+ // NameConstraints OID ::= 2.5.29.30
+ //
+ //NameConstraints ::= SEQUENCE {
+ // permittedSubtrees [0] GeneralSubtrees OPTIONAL,
+ // excludedSubtrees [1] GeneralSubtrees OPTIONAL }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.permittedSubtrees || ""),
+ value: in_window.org.pkijs.schema.x509.GeneralSubtree()
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.excludedSubtrees || ""),
+ value: in_window.org.pkijs.schema.x509.GeneralSubtree()
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "BasicConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.BasicConstraints =
+ function()
+ {
+ // BasicConstraints OID ::= 2.5.29.19
+ //
+ //BasicConstraints ::= SEQUENCE {
+ // cA BOOLEAN DEFAULT FALSE,
+ // pathLenConstraint INTEGER (0..MAX) OPTIONAL }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.BOOLEAN({
+ optional: true,
+ name: (names.cA || "")
+ }),
+ new in_window.org.pkijs.asn1.INTEGER({
+ optional: true,
+ name: (names.pathLenConstraint || "")
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PolicyQualifierInfo" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PolicyQualifierInfo =
+ function()
+ {
+ //PolicyQualifierInfo ::= SEQUENCE {
+ // policyQualifierId PolicyQualifierId,
+ // qualifier ANY DEFINED BY policyQualifierId }
+ //
+ //id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
+ //id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
+ //id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
+ //
+ //PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.policyQualifierId || "") }),
+ new in_window.org.pkijs.asn1.ANY({ name: (names.qualifier || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PolicyInformation" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PolicyInformation =
+ function()
+ {
+ //PolicyInformation ::= SEQUENCE {
+ // policyIdentifier CertPolicyId,
+ // policyQualifiers SEQUENCE SIZE (1..MAX) OF
+ // PolicyQualifierInfo OPTIONAL }
+ //
+ //CertPolicyId ::= OBJECT IDENTIFIER
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.policyIdentifier || "") }),
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ optional: true,
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.policyQualifiers || ""),
+ value: in_window.org.pkijs.schema.x509.PolicyQualifierInfo()
+ })
+ ]
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "CertificatePolicies" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.CertificatePolicies =
+ function()
+ {
+ // CertificatePolicies OID ::= 2.5.29.32
+ //
+ //certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.certificatePolicies || ""),
+ value: in_window.org.pkijs.schema.x509.PolicyInformation()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PolicyMapping" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PolicyMapping =
+ function()
+ {
+ //PolicyMapping ::= SEQUENCE {
+ // issuerDomainPolicy CertPolicyId,
+ // subjectDomainPolicy CertPolicyId }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.issuerDomainPolicy || "") }),
+ new in_window.org.pkijs.asn1.OID({ name: (names.subjectDomainPolicy || "") })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PolicyMappings" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PolicyMappings =
+ function()
+ {
+ // PolicyMappings OID ::= 2.5.29.33
+ //
+ //PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF PolicyMapping
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.mappings || ""),
+ value: in_window.org.pkijs.schema.x509.PolicyMapping()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "PolicyConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.PolicyConstraints =
+ function()
+ {
+ // PolicyMappings OID ::= 2.5.29.36
+ //
+ //PolicyConstraints ::= SEQUENCE {
+ // requireExplicitPolicy [0] SkipCerts OPTIONAL,
+ // inhibitPolicyMapping [1] SkipCerts OPTIONAL }
+ //
+ //SkipCerts ::= INTEGER (0..MAX)
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.requireExplicitPolicy || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ }
+ }), // IMPLICIT integer value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.inhibitPolicyMapping || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ }) // IMPLICIT integer value
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "ExtKeyUsage" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.ExtKeyUsage =
+ function()
+ {
+ // ExtKeyUsage OID ::= 2.5.29.37
+ //
+ // ExtKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+
+ // KeyPurposeId ::= OBJECT IDENTIFIER
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.keyPurposes || ""),
+ value: new in_window.org.pkijs.asn1.OID()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "DistributionPoint" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.DistributionPoint =
+ function()
+ {
+ //DistributionPoint ::= SEQUENCE {
+ // distributionPoint [0] DistributionPointName OPTIONAL,
+ // reasons [1] ReasonFlags OPTIONAL,
+ // cRLIssuer [2] GeneralNames OPTIONAL }
+ //
+ //DistributionPointName ::= CHOICE {
+ // fullName [0] GeneralNames,
+ // nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
+ //
+ //ReasonFlags ::= BIT STRING {
+ // unused (0),
+ // keyCompromise (1),
+ // cACompromise (2),
+ // affiliationChanged (3),
+ // superseded (4),
+ // cessationOfOperation (5),
+ // certificateHold (6),
+ // privilegeWithdrawn (7),
+ // aACompromise (8) }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ name: (names.distributionPoint || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.distributionPoint_names || ""),
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ name: (names.distributionPoint || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: in_window.org.pkijs.schema.RDN().value_block.value
+ })
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.reasons || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ }), // IMPLICIT bitstring value
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ name: (names.cRLIssuer || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.cRLIssuer_names || ""),
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ }) // IMPLICIT bitstring value
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "CRLDistributionPoints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.CRLDistributionPoints =
+ function()
+ {
+ // CRLDistributionPoints OID ::= 2.5.29.31
+ //
+ //CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.distributionPoints || ""),
+ value: in_window.org.pkijs.schema.x509.DistributionPoint()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "AccessDescription" type
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.AccessDescription =
+ function()
+ {
+ //AccessDescription ::= SEQUENCE {
+ // accessMethod OBJECT IDENTIFIER,
+ // accessLocation GeneralName }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.OID({ name: (names.accessMethod || "") }),
+ in_window.org.pkijs.schema.GENERAL_NAME(names.accessLocation || "")
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "AuthorityInfoAccess" and "SubjectInfoAccess" types of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.InfoAccess =
+ function()
+ {
+ // AuthorityInfoAccess OID ::= 1.3.6.1.5.5.7.1.1
+ // SubjectInfoAccess OID ::= 1.3.6.1.5.5.7.1.11
+ //
+ //AuthorityInfoAccessSyntax ::=
+ //SEQUENCE SIZE (1..MAX) OF AccessDescription
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.accessDescriptions || ""),
+ value: in_window.org.pkijs.schema.x509.AccessDescription()
+ })
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region ASN.1 schema definition for "IssuingDistributionPoint" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.schema.x509.IssuingDistributionPoint =
+ function()
+ {
+ // IssuingDistributionPoint OID ::= 2.5.29.28
+ //
+ //IssuingDistributionPoint ::= SEQUENCE {
+ // distributionPoint [0] DistributionPointName OPTIONAL,
+ // onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
+ // onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
+ // onlySomeReasons [3] ReasonFlags OPTIONAL,
+ // indirectCRL [4] BOOLEAN DEFAULT FALSE,
+ // onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
+ //
+ //ReasonFlags ::= BIT STRING {
+ // unused (0),
+ // keyCompromise (1),
+ // cACompromise (2),
+ // affiliationChanged (3),
+ // superseded (4),
+ // cessationOfOperation (5),
+ // certificateHold (6),
+ // privilegeWithdrawn (7),
+ // aACompromise (8) }
+
+ var names = in_window.org.pkijs.getNames(arguments[0]);
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ name: (names.block_name || ""),
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.CHOICE({
+ value: [
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ name: (names.distributionPoint || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: (names.distributionPoint_names || ""),
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ name: (names.distributionPoint || ""),
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: in_window.org.pkijs.schema.RDN().value_block.value
+ })
+ ]
+ })
+ ]
+ }),
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.onlyContainsUserCerts || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ }
+ }), // IMPLICIT boolean value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.onlyContainsCACerts || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ }
+ }), // IMPLICIT boolean value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.onlySomeReasons || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ }
+ }), // IMPLICIT bitstring value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.indirectCRL || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 4 // [4]
+ }
+ }), // IMPLICIT boolean value
+ new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ name: (names.onlyContainsAttributeCerts || ""),
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 5 // [5]
+ }
+ }) // IMPLICIT boolean value
+ ]
+ }));
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+}
+)(typeof exports !== "undefined" ? exports : window); \ No newline at end of file
diff --git a/dom/webauthn/tests/pkijs/x509_simpl.js b/dom/webauthn/tests/pkijs/x509_simpl.js
new file mode 100644
index 0000000000..c00e79d55b
--- /dev/null
+++ b/dom/webauthn/tests/pkijs/x509_simpl.js
@@ -0,0 +1,7239 @@
+/*
+ * Copyright (c) 2014, GMO GlobalSign
+ * Copyright (c) 2015, Peculiar Ventures
+ * All rights reserved.
+ *
+ * Author 2014-2015, Yury Strozhevsky <www.strozhevsky.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ */
+(
+function(in_window)
+{
+ //**************************************************************************************
+ // #region Declaration of global variables
+ //**************************************************************************************
+ // #region "org" namespace
+ if(typeof in_window.org === "undefined")
+ in_window.org = {};
+ else
+ {
+ if(typeof in_window.org !== "object")
+ throw new Error("Name org already exists and it's not an object");
+ }
+ // #endregion
+
+ // #region "org.pkijs" namespace
+ if(typeof in_window.org.pkijs === "undefined")
+ in_window.org.pkijs = {};
+ else
+ {
+ if(typeof in_window.org.pkijs !== "object")
+ throw new Error("Name org.pkijs already exists and it's not an object" + " but " + (typeof in_window.org.pkijs));
+ }
+ // #endregion
+
+ // #region "org.pkijs.simpl" namespace
+ if(typeof in_window.org.pkijs.simpl === "undefined")
+ in_window.org.pkijs.simpl = {};
+ else
+ {
+ if(typeof in_window.org.pkijs.simpl !== "object")
+ throw new Error("Name org.pkijs.simpl already exists and it's not an object" + " but " + (typeof in_window.org.pkijs.simpl));
+ }
+ // #endregion
+
+ // #region "org.pkijs.simpl.x509" namespace
+ if(typeof in_window.org.pkijs.simpl.x509 === "undefined")
+ in_window.org.pkijs.simpl.x509 = {};
+ else
+ {
+ if(typeof in_window.org.pkijs.simpl.x509 !== "object")
+ throw new Error("Name org.pkijs.simpl.x509 already exists and it's not an object" + " but " + (typeof in_window.org.pkijs.simpl.x509));
+ }
+ // #endregion
+
+ // #region "local" namespace
+ var local = {};
+ // #endregion
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "Time" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.TIME =
+ function()
+ {
+ // #region Internal properties of the object
+ this.type = 0; // 0 - UTCTime; 1 - GeneralizedTime; 2 - empty value
+ this.value = new Date(0, 0, 0);
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.TIME.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.type = (arguments[0].type || 0);
+ this.value = (arguments[0].value || (new Date(0, 0, 0)));
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.TIME.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.TIME({
+ names: {
+ utcTimeName: "utcTimeName",
+ generalTimeName: "generalTimeName"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for TIME");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("utcTimeName" in asn1.result)
+ {
+ this.type = 0;
+ this.value = asn1.result.utcTimeName.toDate();
+ }
+ if("generalTimeName" in asn1.result)
+ {
+ this.type = 1;
+ this.value = asn1.result.generalTimeName.toDate();
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.TIME.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ var result = {};
+
+ if(this.type === 0)
+ result = new in_window.org.pkijs.asn1.UTCTIME({ value_date: this.value });
+ if(this.type === 1)
+ result = new in_window.org.pkijs.asn1.GENERALIZEDTIME({ value_date: this.value });
+
+ return result;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.TIME.prototype.toJSON =
+ function()
+ {
+ return {
+ type: this.type,
+ value: this.value
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "GeneralName" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAME =
+ function()
+ {
+ // #region Internal properties of the object
+ this.NameType = 9; // Name type - from a tagged value (0 for "otherName", 1 for "rfc822Name" etc.)
+ this.Name = {};
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.GENERAL_NAME.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.NameType = arguments[0].NameType || 9;
+ this.Name = arguments[0].Name || {};
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAME.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.GENERAL_NAME({
+ names: {
+ block_name: "block_name",
+ otherName: "otherName",
+ rfc822Name: "rfc822Name",
+ dNSName: "dNSName",
+ x400Address: "x400Address",
+ directoryName: {
+ names: {
+ block_name: "directoryName"
+ }
+ },
+ ediPartyName: "ediPartyName",
+ uniformResourceIdentifier: "uniformResourceIdentifier",
+ iPAddress: "iPAddress",
+ registeredID: "registeredID"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for GENERAL_NAME");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.NameType = asn1.result["block_name"].id_block.tag_number;
+
+ switch(this.NameType)
+ {
+ case 0: // otherName
+ this.Name = asn1.result["block_name"];
+ break;
+ case 1: // rfc822Name + dNSName + uniformResourceIdentifier
+ case 2:
+ case 6:
+ {
+ var value = asn1.result["block_name"];
+
+ value.id_block.tag_class = 1; // UNIVERSAL
+ value.id_block.tag_number = 22; // IA5STRING
+
+ var value_ber = value.toBER(false);
+
+ this.Name = in_window.org.pkijs.fromBER(value_ber).result.value_block.value;
+ }
+ break;
+ case 3: // x400Address
+ this.Name = asn1.result["block_name"];
+ break;
+ case 4: // directoryName
+ this.Name = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["directoryName"] });
+ break;
+ case 5: // ediPartyName
+ this.Name = asn1.result["ediPartyName"];
+ break;
+ case 7: // iPAddress
+ this.Name = new in_window.org.pkijs.asn1.OCTETSTRING({ value_hex: asn1.result["block_name"].value_block.value_hex });
+ break;
+ case 8: // registeredID
+ {
+ var value = asn1.result["block_name"];
+
+ value.id_block.tag_class = 1; // UNIVERSAL
+ value.id_block.tag_number = 6; // OID
+
+ var value_ber = value.toBER(false);
+
+ this.Name = in_window.org.pkijs.fromBER(value_ber).result.value_block.toString(); // Getting a string representation of the OID
+ }
+ break;
+ default:
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAME.prototype.toSchema =
+ function(schema)
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ switch(this.NameType)
+ {
+ case 0:
+ case 3:
+ case 5:
+ return new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: this.NameType
+ },
+ value: [
+ this.Name
+ ]
+ });
+
+ break;
+ case 1:
+ case 2:
+ case 6:
+ {
+ var value = new in_window.org.pkijs.asn1.IA5STRING({ value: this.Name });
+
+ value.id_block.tag_class = 3;
+ value.id_block.tag_number = this.NameType;
+
+ return value;
+ }
+ break;
+ case 4:
+ return new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 4
+ },
+ value: [this.Name.toSchema()]
+ });
+ break;
+ case 7:
+ {
+ var value = this.Name;
+
+ value.id_block.tag_class = 3;
+ value.id_block.tag_number = this.NameType;
+
+ return value;
+ }
+ break;
+ case 8:
+ {
+ var value = new in_window.org.pkijs.asn1.OID({ value: this.Name });
+
+ value.id_block.tag_class = 3;
+ value.id_block.tag_number = this.NameType;
+
+ return value;
+ }
+ break;
+ default:
+ return in_window.org.pkijs.schema.GENERAL_NAME();
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAME.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ NameType: this.NameType
+ };
+
+ if((typeof this.Name) === "string")
+ _object.Name = this.Name;
+ else
+ _object.Name = this.Name.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "GeneralNames" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAMES =
+ function()
+ {
+ // #region Internal properties of the object
+ this.names = new Array(); // Array of "org.pkijs.simpl.GENERAL_NAME"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.GENERAL_NAMES.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.names = arguments[0].names || new Array(); // Array of "org.pkijs.simpl.GENERAL_NAME"
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAMES.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.REPEATED({
+ name: "names",
+ value: in_window.org.pkijs.schema.GENERAL_NAME()
+ })
+ ]
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for GENERAL_NAMES");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var n = asn1.result["names"];
+
+ for(var i = 0; i < n.length; i++)
+ this.names.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: n[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAMES.prototype.toSchema =
+ function(schema)
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ var output_array = new Array();
+
+ for(var i = 0; i < this.names.length; i++)
+ output_array.push(this.names[i].toSchema());
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.GENERAL_NAMES.prototype.toJSON =
+ function()
+ {
+ var _names = new Array();
+
+ for(var i = 0; i < this.names.length; i++)
+ _names.push(this.names[i].toJSON());
+
+ return {
+ names: _names
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "AlgorithmIdentifier" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER =
+ function()
+ {
+ // #region Internal properties of the object
+ this.algorithm_id = "";
+ // OPTIONAL this.algorithm_params = new in_window.org.pkijs.asn1.NULL();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.algorithm_id = arguments[0].algorithm_id || "";
+ if("algorithm_params" in arguments[0])
+ this.algorithm_params = arguments[0].algorithm_params;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.ALGORITHM_IDENTIFIER({
+ names: {
+ algorithmIdentifier: "algorithm",
+ algorithmParams: "params"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ALGORITHM_IDENTIFIER");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.algorithm_id = asn1.result.algorithm.value_block.toString();
+ if("params" in asn1.result)
+ this.algorithm_params = asn1.result.params;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.OID({ value: this.algorithm_id }));
+ if("algorithm_params" in this)
+ output_array.push(this.algorithm_params);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.getCommonName =
+ function()
+ {
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ algorithm_id: this.algorithm_id
+ };
+
+ if("algorithm_params" in this)
+ _object.algorithm_params = this.algorithm_params.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER.prototype.isEqual =
+ function(algorithmIdentifier)
+ {
+ /// <summary>Check that two "ALGORITHM_IDENTIFIERs" are equal</summary>
+ /// <param name="algorithmIdentifier" type="in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER">The algorithm identifier to compare with</param>
+
+ // #region Check input type
+ if((algorithmIdentifier instanceof in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER) == false)
+ return false;
+ // #endregion
+
+ // #region Check "algorithm_id"
+ if(this.algorithm_id != algorithmIdentifier.algorithm_id)
+ return false;
+ // #endregion
+
+ // #region Check "algorithm_params"
+ if("algorithm_params" in this)
+ {
+ if("algorithm_params" in algorithmIdentifier)
+ {
+ return JSON.stringify(this.algorithm_params) == JSON.stringify(algorithmIdentifier.algorithm_params);
+ }
+ else
+ return false;
+ }
+ else
+ {
+ if("algorithm_params" in algorithmIdentifier)
+ return false;
+ }
+ // #endregion
+
+ return true;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "RSAPublicKey" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPublicKey =
+ function()
+ {
+ // #region Internal properties of the object
+ this.modulus = new in_window.org.pkijs.asn1.INTEGER();
+ this.publicExponent = new in_window.org.pkijs.asn1.INTEGER();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.RSAPublicKey.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.modulus = arguments[0].modulus || new in_window.org.pkijs.asn1.INTEGER();
+ this.publicExponent = arguments[0].publicExponent || new in_window.org.pkijs.asn1.INTEGER();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPublicKey.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.RSAPublicKey({
+ names: {
+ modulus: "modulus",
+ publicExponent: "publicExponent"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSAPublicKey");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.modulus = asn1.result["modulus"];
+ this.publicExponent = asn1.result["publicExponent"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPublicKey.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ this.modulus,
+ this.publicExponent
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPublicKey.prototype.toJSON =
+ function()
+ {
+ return {
+ modulus: this.modulus.toJSON(),
+ publicExponent: this.publicExponent.toJSON()
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "OtherPrimeInfo" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.OtherPrimeInfo =
+ function()
+ {
+ // #region Internal properties of the object
+ this.prime = new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent = new in_window.org.pkijs.asn1.INTEGER();
+ this.coefficient = new in_window.org.pkijs.asn1.INTEGER();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.OtherPrimeInfo.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.prime = arguments[0].prime || new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent = arguments[0].exponent || new in_window.org.pkijs.asn1.INTEGER();
+ this.coefficient = arguments[0].coefficient || new in_window.org.pkijs.asn1.INTEGER();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.OtherPrimeInfo.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.OtherPrimeInfo({
+ names: {
+ prime: "prime",
+ exponent: "exponent",
+ coefficient: "coefficient"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherPrimeInfo");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.prime = asn1.result["prime"];
+ this.exponent = asn1.result["exponent"];
+ this.coefficient = asn1.result["coefficient"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.OtherPrimeInfo.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ this.prime,
+ this.exponent,
+ this.coefficient
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.OtherPrimeInfo.prototype.toJSON =
+ function()
+ {
+ return {
+ prime: this.prime.toJSON(),
+ exponent: this.exponent.toJSON(),
+ coefficient: this.coefficient.toJSON()
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "RSAPrivateKey" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPrivateKey =
+ function()
+ {
+ // #region Internal properties of the object
+ this.version = 0;
+ this.modulus = new in_window.org.pkijs.asn1.INTEGER();
+ this.publicExponent = new in_window.org.pkijs.asn1.INTEGER();
+ this.privateExponent = new in_window.org.pkijs.asn1.INTEGER();
+ this.prime1 = new in_window.org.pkijs.asn1.INTEGER();
+ this.prime2 = new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent1 = new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent2 = new in_window.org.pkijs.asn1.INTEGER();
+ this.coefficient = new in_window.org.pkijs.asn1.INTEGER();
+ // OPTIONAL this.otherPrimeInfos = new Array(); // Array of "OtherPrimeInfo"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.RSAPrivateKey.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.version = arguments[0].version || 0;
+ this.modulus = arguments[0].modulus || new in_window.org.pkijs.asn1.INTEGER();
+ this.publicExponent = arguments[0].publicExponent || new in_window.org.pkijs.asn1.INTEGER();
+ this.privateExponent = arguments[0].privateExponent || new in_window.org.pkijs.asn1.INTEGER();
+ this.prime1 = arguments[0].prime1 || new in_window.org.pkijs.asn1.INTEGER();
+ this.prime2 = arguments[0].prime2 || new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent1 = arguments[0].exponent1 || new in_window.org.pkijs.asn1.INTEGER();
+ this.exponent2 = arguments[0].exponent2 || new in_window.org.pkijs.asn1.INTEGER();
+ this.coefficient = arguments[0].coefficient || new in_window.org.pkijs.asn1.INTEGER();
+ if("otherPrimeInfos" in arguments[0])
+ this.otherPrimeInfos = arguments[0].otherPrimeInfos || new Array();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPrivateKey.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.RSAPrivateKey({
+ names: {
+ version: "version",
+ modulus: "modulus",
+ publicExponent: "publicExponent",
+ privateExponent: "privateExponent",
+ prime1: "prime1",
+ prime2: "prime2",
+ exponent1: "exponent1",
+ exponent2: "exponent2",
+ coefficient: "coefficient",
+ otherPrimeInfos: "otherPrimeInfos"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSAPrivateKey");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.version = asn1.result["version"].value_block.value_dec;
+ this.modulus = asn1.result["modulus"];
+ this.publicExponent = asn1.result["publicExponent"];
+ this.privateExponent = asn1.result["privateExponent"];
+ this.prime1 = asn1.result["prime1"];
+ this.prime2 = asn1.result["prime2"];
+ this.exponent1 = asn1.result["exponent1"];
+ this.exponent2 = asn1.result["exponent2"];
+ this.coefficient = asn1.result["coefficient"];
+
+ if("otherPrimeInfos" in asn1.result)
+ {
+ var otherPrimeInfos_array = asn1.result["otherPrimeInfos"];
+
+ for(var i = 0; i < otherPrimeInfos_array.length; i++)
+ this.otherPrimeInfos.push(new in_window.org.pkijs.simpl.x509.OtherPrimeInfo({ schema: otherPrimeInfos_array[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPrivateKey.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.INTEGER({ value: this.version }));
+ output_array.push(this.modulus);
+ output_array.push(this.publicExponent);
+ output_array.push(this.privateExponent);
+ output_array.push(this.prime1);
+ output_array.push(this.prime2);
+ output_array.push(this.exponent1);
+ output_array.push(this.exponent2);
+ output_array.push(this.coefficient);
+
+ if("otherPrimeInfos" in this)
+ {
+ var otherPrimeInfos_array = new Array();
+
+ for(var i = 0; i < this.otherPrimeInfos.length; i++)
+ otherPrimeInfos_array.push(this.otherPrimeInfos[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.SEQUENCE({ value: otherPrimeInfos_array }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSAPrivateKey.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ version: this.version,
+ modulus: this.modulus.toJSON(),
+ publicExponent: this.publicExponent.toJSON(),
+ privateExponent: this.privateExponent.toJSON(),
+ prime1: this.prime1.toJSON(),
+ prime2: this.prime2.toJSON(),
+ exponent1: this.exponent1.toJSON(),
+ exponent2: this.exponent2.toJSON(),
+ coefficient: this.coefficient.toJSON()
+ };
+
+ if("otherPrimeInfos" in this)
+ {
+ _object.otherPrimeInfos = new Array();
+
+ for(var i = 0; i < this.otherPrimeInfos.length; i++)
+ _object.otherPrimeInfos.push(this.otherPrimeInfos[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "RSASSA_PSS_params" type (RFC3447)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSASSA_PSS_params =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.hashAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ // OPTIONAL this.maskGenAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ // OPTIONAL this.saltLength = 20; // new in_window.org.pkijs.asn1.INTEGER();
+ // OPTIONAL this.trailerField = 1; // new in_window.org.pkijs.asn1.INTEGER();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.RSASSA_PSS_params.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("hashAlgorithm" in arguments[0])
+ this.hashAlgorithm = arguments[0].hashAlgorithm;
+
+ if("maskGenAlgorithm" in arguments[0])
+ this.maskGenAlgorithm = arguments[0].maskGenAlgorithm;
+
+ if("saltLength" in arguments[0])
+ this.saltLength = arguments[0].saltLength;
+
+ if("trailerField" in arguments[0])
+ this.trailerField = arguments[0].trailerField;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSASSA_PSS_params.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.RSASSA_PSS_params({
+ names: {
+ hashAlgorithm: {
+ names: {
+ block_name: "hashAlgorithm"
+ }
+ },
+ maskGenAlgorithm: {
+ names: {
+ block_name: "maskGenAlgorithm"
+ }
+ },
+ saltLength: "saltLength",
+ trailerField: "trailerField"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSASSA_PSS_params");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("hashAlgorithm" in asn1.result)
+ this.hashAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["hashAlgorithm"] });
+
+ if("maskGenAlgorithm" in asn1.result)
+ this.maskGenAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["maskGenAlgorithm"] });
+
+ if("saltLength" in asn1.result)
+ this.saltLength = asn1.result["saltLength"].value_block.value_dec;
+
+ if("trailerField" in asn1.result)
+ this.trailerField = asn1.result["trailerField"].value_block.value_dec;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSASSA_PSS_params.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("hashAlgorithm" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [this.hashAlgorithm.toSchema()]
+ }));
+
+ if("maskGenAlgorithm" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [this.maskGenAlgorithm.toSchema()]
+ }));
+
+ if("saltLength" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ value: [new in_window.org.pkijs.asn1.INTEGER({ value: this.saltLength })]
+ }));
+
+ if("trailerField" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ value: [new in_window.org.pkijs.asn1.INTEGER({ value: this.trailerField })]
+ }));
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.RSASSA_PSS_params.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("hashAlgorithm" in this)
+ _object.hashAlgorithm = this.hashAlgorithm.toJSON();
+
+ if("maskGenAlgorithm" in this)
+ _object.maskGenAlgorithm = this.maskGenAlgorithm.toJSON();
+
+ if("saltLength" in this)
+ _object.saltLength = this.saltLength.toJSON();
+
+ if("trailerField" in this)
+ _object.trailerField = this.trailerField.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "SubjectPublicKeyInfo" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO =
+ function()
+ {
+ // #region Internal properties of the object
+ this.algorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.subjectPublicKey = new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.algorithm = (arguments[0].algorithm || (new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER()));
+ this.subjectPublicKey = (arguments[0].subjectPublicKey || (new in_window.org.pkijs.asn1.BITSTRING()));
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.PUBLIC_KEY_INFO({
+ names: {
+ algorithm: {
+ names: {
+ block_name: "algorithm"
+ }
+ },
+ subjectPublicKey: "subjectPublicKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PUBLIC_KEY_INFO");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.algorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result.algorithm });
+ this.subjectPublicKey = asn1.result.subjectPublicKey;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ this.algorithm.toSchema(),
+ this.subjectPublicKey
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.importKey =
+ function(publicKey)
+ {
+ /// <param name="publicKey" type="Key">Public key to work with</param>
+
+ // #region Initial variables
+ var sequence = Promise.resolve();
+ var _this = this;
+ // #endregion
+
+ // #region Initial check
+ if(typeof publicKey === "undefined")
+ return new Promise(function(resolve, reject) { reject("Need to provide publicKey input parameter"); });
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Export public key
+ sequence = sequence.then(
+ function()
+ {
+ return crypto.exportKey("spki", publicKey);
+ }
+ );
+ // #endregion
+
+ // #region Initialize internal variables by parsing exported value
+ sequence = sequence.then(
+ function(exportedKey)
+ {
+ var asn1 = in_window.org.pkijs.fromBER(exportedKey);
+ try
+ {
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.fromSchema.call(_this, asn1.result);
+ }
+ catch(exception)
+ {
+ return new Promise(function(resolve, reject) { reject("Error during initializing object from schema"); });
+ }
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject) { reject("Error during exporting public key: " + error); });
+ }
+ );
+ // #endregion
+
+ return sequence;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PUBLIC_KEY_INFO.prototype.toJSON =
+ function()
+ {
+ return {
+ algorithm: this.algorithm.toJSON(),
+ subjectPublicKey: this.subjectPublicKey.toJSON()
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "AttributeTypeAndValue" type (part of RelativeDistinguishedName)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE =
+ function()
+ {
+ // #region Internal properties of the object
+ this.type = "";
+ this.value = {}; // ANY -- DEFINED BY AttributeType
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.type = (arguments[0].type || "");
+ this.value = (arguments[0].value || {});
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.ATTR_TYPE_AND_VALUE({
+ names: {
+ type: "type",
+ value: "typeValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ATTR_TYPE_AND_VALUE");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.type = asn1.result.type.value_block.toString();
+ this.value = asn1.result.typeValue;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.OID({ value: this.type }),
+ this.value
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE.prototype.isEqual =
+ function()
+ {
+ if(arguments[0] instanceof in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE)
+ {
+ if(this.type !== arguments[0].type)
+ return false;
+
+ if(((this.value instanceof in_window.org.pkijs.asn1.UTF8STRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.UTF8STRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.BMPSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.BMPSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.UNIVERSALSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.UNIVERSALSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.NUMERICSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.NUMERICSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.PRINTABLESTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.PRINTABLESTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.TELETEXSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.TELETEXSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.VIDEOTEXSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.VIDEOTEXSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.IA5STRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.IA5STRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.GRAPHICSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.GRAPHICSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.VISIBLESTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.VISIBLESTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.GENERALSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.GENERALSTRING)) ||
+ ((this.value instanceof in_window.org.pkijs.asn1.CHARACTERSTRING) && (arguments[0].value instanceof in_window.org.pkijs.asn1.CHARACTERSTRING)))
+ {
+ var value1 = in_window.org.pkijs.stringPrep(this.value.value_block.value);
+ var value2 = in_window.org.pkijs.stringPrep(arguments[0].value.value_block.value);
+
+ if(value1.localeCompare(value2) !== 0)
+ return false;
+ }
+ else // Comparing as two ArrayBuffers
+ {
+ if(in_window.org.pkijs.isEqual_buffer(this.value.value_before_decode, arguments[0].value.value_before_decode) === false)
+ return false;
+ }
+
+ return true;
+ }
+ else
+ return false;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ type: this.type
+ };
+
+ if(Object.keys(this.value).length !== 0)
+ _object.value = this.value.toJSON();
+ else
+ _object.value = this.value;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "RelativeDistinguishedName" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.RDN =
+ function()
+ {
+ // #region Internal properties of the object
+ /// <field name="types_and_values" type="Array" elementType="in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE">Array of "type and value" objects</field>
+ this.types_and_values = new Array();
+ /// <field name="value_before_decode" type="ArrayBuffer">Value of the RDN before decoding from schema</field>
+ this.value_before_decode = new ArrayBuffer(0);
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.RDN.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.types_and_values = (arguments[0].types_and_values || (new Array()));
+ this.value_before_decode = arguments[0].value_before_decode || new ArrayBuffer(0);
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.RDN.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.RDN({
+ names: {
+ block_name: "RDN",
+ repeated_set: "types_and_values"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RDN");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("types_and_values" in asn1.result) // Could be a case when there is no "types and values"
+ {
+ var types_and_values_array = asn1.result.types_and_values;
+ for(var i = 0; i < types_and_values_array.length; i++)
+ this.types_and_values.push(new in_window.org.pkijs.simpl.ATTR_TYPE_AND_VALUE({ schema: types_and_values_array[i] }));
+ }
+
+ this.value_before_decode = asn1.result.RDN.value_before_decode;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.RDN.prototype.toSchema =
+ function()
+ {
+ // #region Decode stored TBS value
+ if(this.value_before_decode.byteLength === 0) // No stored encoded array, create "from scratch"
+ {
+ // #region Create array for output set
+ var output_array = new Array();
+
+ for(var i = 0; i < this.types_and_values.length; i++)
+ output_array.push(this.types_and_values[i].toSchema());
+ // #endregion
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [new in_window.org.pkijs.asn1.SET({ value: output_array })]
+ }));
+ }
+
+ var asn1 = in_window.org.pkijs.fromBER(this.value_before_decode);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return asn1.result;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.RDN.prototype.isEqual =
+ function()
+ {
+ if(arguments[0] instanceof in_window.org.pkijs.simpl.RDN)
+ {
+ if(this.types_and_values.length != arguments[0].types_and_values.length)
+ return false;
+
+ for(var i = 0; i < this.types_and_values.length; i++)
+ {
+ if(this.types_and_values[i].isEqual(arguments[0].types_and_values[i]) === false)
+ return false;
+ }
+
+ return true;
+ }
+ else
+ {
+ if(arguments[0] instanceof ArrayBuffer)
+ return in_window.org.pkijs.isEqual_buffer(this.value_before_decode, arguments[0]);
+ else
+ return false;
+ }
+
+ return false;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.RDN.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ types_and_values: new Array()
+ };
+
+ for(var i = 0; i < this.types_and_values.length; i++)
+ _object.types_and_values.push(this.types_and_values[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "AuthorityKeyIdentifier" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.keyIdentifier - OCTETSTRING
+ // OPTIONAL this.authorityCertIssuer - Array of GeneralName
+ // OPTIONAL this.authorityCertSerialNumber - INTEGER
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("keyIdentifier" in arguments[0])
+ this.keyIdentifier = arguments[0].keyIdentifier;
+
+ if("authorityCertIssuer" in arguments[0])
+ this.authorityCertIssuer = arguments[0].authorityCertIssuer;
+
+ if("authorityCertSerialNumber" in arguments[0])
+ this.authorityCertSerialNumber = arguments[0].authorityCertSerialNumber;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.AuthorityKeyIdentifier({
+ names: {
+ keyIdentifier: "keyIdentifier",
+ authorityCertIssuer: "authorityCertIssuer",
+ authorityCertSerialNumber: "authorityCertSerialNumber"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AuthorityKeyIdentifier");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("keyIdentifier" in asn1.result)
+ {
+ asn1.result["keyIdentifier"].id_block.tag_class = 1; // UNIVERSAL
+ asn1.result["keyIdentifier"].id_block.tag_number = 4; // OCTETSTRING
+
+ this.keyIdentifier = asn1.result["keyIdentifier"];
+ }
+
+ if("authorityCertIssuer" in asn1.result)
+ {
+ this.authorityCertIssuer = new Array();
+ var issuer_array = asn1.result["authorityCertIssuer"];
+
+ for(var i = 0; i < issuer_array.length; i++)
+ this.authorityCertIssuer.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: issuer_array[i] }));
+ }
+
+ if("authorityCertSerialNumber" in asn1.result)
+ {
+ asn1.result["authorityCertSerialNumber"].id_block.tag_class = 1; // UNIVERSAL
+ asn1.result["authorityCertSerialNumber"].id_block.tag_number = 2; // INTEGER
+
+ this.authorityCertSerialNumber = asn1.result["authorityCertSerialNumber"];
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("keyIdentifier" in this)
+ {
+ var value = this.keyIdentifier;
+
+ value.id_block.tag_class = 3; // CONTEXT-SPECIFIC
+ value.id_block.tag_number = 0; // [0]
+
+ output_array.push(value);
+ }
+
+ if("authorityCertIssuer" in this)
+ {
+ var issuer_array = new Array();
+
+ for(var i = 0; i < this.authorityCertIssuer.length; i++)
+ issuer_array.push(this.authorityCertIssuer[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [new in_window.org.pkijs.asn1.SEQUENCE({
+ value: issuer_array
+ })]
+ }));
+ }
+
+ if("authorityCertSerialNumber" in this)
+ {
+ var value = this.authorityCertSerialNumber;
+
+ value.id_block.tag_class = 3; // CONTEXT-SPECIFIC
+ value.id_block.tag_number = 2; // [2]
+
+ output_array.push(value);
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("keyIdentifier" in this)
+ _object.keyIdentifier = this.keyIdentifier.toJSON();
+
+ if("authorityCertIssuer" in this)
+ {
+ _object.authorityCertIssuer = new Array();
+
+ for(var i = 0; i < this.authorityCertIssuer.length; i++)
+ _object.authorityCertIssuer.push(this.authorityCertIssuer[i].toJSON());
+ }
+
+ if("authorityCertSerialNumber" in this)
+ _object.authorityCertSerialNumber = this.authorityCertSerialNumber.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PrivateKeyUsagePeriod" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.notBefore - new Date()
+ // OPTIONAL this.notAfter - new Date()
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("notBefore" in arguments[0])
+ this.notBefore = arguments[0].notBefore;
+
+ if("notAfter" in arguments[0])
+ this.notAfter = arguments[0].notAfter;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PrivateKeyUsagePeriod({
+ names: {
+ notBefore: "notBefore",
+ notAfter: "notAfter"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PrivateKeyUsagePeriod");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("notBefore" in asn1.result)
+ {
+ var localNotBefore = new in_window.org.pkijs.asn1.GENERALIZEDTIME();
+ localNotBefore.fromBuffer(asn1.result["notBefore"].value_block.value_hex);
+ this.notBefore = localNotBefore.toDate();
+ }
+
+ if("notAfter" in asn1.result)
+ {
+ var localNotAfter = new in_window.org.pkijs.asn1.GENERALIZEDTIME({ value_hex: asn1.result["notAfter"].value_block.value_hex });
+ localNotAfter.fromBuffer(asn1.result["notAfter"].value_block.value_hex);
+ this.notAfter = localNotAfter.toDate();
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("notBefore" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value_hex: (new in_window.org.pkijs.asn1.GENERALIZEDTIME({ value_date: this.notBefore })).value_block.value_hex
+ }));
+
+ if("notAfter" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value_hex: (new in_window.org.pkijs.asn1.GENERALIZEDTIME({ value_date: this.notAfter })).value_block.value_hex
+ }));
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("notBefore" in this)
+ _object.notBefore = this.notBefore;
+
+ if("notAfter" in this)
+ _object.notAfter = this.notAfter;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "IssuerAltName" and "SubjectAltName" types of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AltName =
+ function()
+ {
+ // #region Internal properties of the object
+ this.altNames = new Array(); //Array of GeneralName
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.AltName.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.altNames = arguments[0].altNames || new Array();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AltName.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.AltName({
+ names: {
+ altNames: "altNames"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AltName");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("altNames" in asn1.result)
+ {
+ var altNames_array = asn1.result["altNames"];
+
+ for(var i = 0; i < altNames_array.length; i++)
+ this.altNames.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: altNames_array[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AltName.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.altNames.length; i++)
+ output_array.push(this.altNames[i].toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AltName.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ altNames: new Array()
+ };
+
+ for(var i = 0; i < this.altNames.length; i++)
+ _object.altNames.push(this.altNames[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "SubjectDirectoryAttributes" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes =
+ function()
+ {
+ // #region Internal properties of the object
+ this.attributes = new Array(); // Array of "simpl.ATTRIBUTE"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.attributes = arguments[0].attributes || new Array(); // Array of "simpl.ATTRIBUTE"
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.SubjectDirectoryAttributes({
+ names: {
+ attributes: "attributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SubjectDirectoryAttributes");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var attrs = asn1.result["attributes"];
+
+ for(var i = 0; i < attrs.length; i++)
+ this.attributes.push(new in_window.org.pkijs.simpl.ATTRIBUTE({ schema: attrs[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.attributes.length; i++)
+ output_array.push(this.attributes[i].toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ attributes: new Array()
+ };
+
+ for(var i = 0; i < this.attributes.length; i++)
+ _object.attributes.push(this.attributes[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PolicyMapping" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMapping =
+ function()
+ {
+ // #region Internal properties of the object
+ this.issuerDomainPolicy = "";
+ this.subjectDomainPolicy = "";
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PolicyMapping.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.issuerDomainPolicy = arguments[0].issuerDomainPolicy || "";
+ this.subjectDomainPolicy = arguments[0].subjectDomainPolicy || "";
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMapping.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PolicyMapping({
+ names: {
+ issuerDomainPolicy: "issuerDomainPolicy",
+ subjectDomainPolicy: "subjectDomainPolicy"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyMapping");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.issuerDomainPolicy = asn1.result["issuerDomainPolicy"].value_block.toString();
+ this.subjectDomainPolicy = asn1.result["subjectDomainPolicy"].value_block.toString();
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMapping.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.OID({ value: this.issuerDomainPolicy }),
+ new in_window.org.pkijs.asn1.OID({ value: this.subjectDomainPolicy })
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMapping.prototype.toJSON =
+ function()
+ {
+ return {
+ issuerDomainPolicy: this.issuerDomainPolicy,
+ subjectDomainPolicy: this.subjectDomainPolicy
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PolicyMappings" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMappings =
+ function()
+ {
+ // #region Internal properties of the object
+ this.mappings = new Array(); // Array of "simpl.x509.PolicyMapping"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PolicyMappings.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.mappings = arguments[0].mappings || new Array();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMappings.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PolicyMappings({
+ names: {
+ mappings: "mappings"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyMappings");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var maps = asn1.result["mappings"];
+
+ for(var i = 0; i < maps.length; i++)
+ this.mappings.push(new in_window.org.pkijs.simpl.x509.PolicyMapping({ schema: maps[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMappings.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.mappings.length; i++)
+ output_array.push(this.mappings.toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyMappings.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ mappings: new Array()
+ };
+
+ for(var i = 0; i < this.mappings.length; i++)
+ _object.mappings.push(this.mappings[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "GeneralSubtree" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.GeneralSubtree =
+ function()
+ {
+ // #region Internal properties of the object
+ this.base = new in_window.org.pkijs.simpl.GENERAL_NAME();
+ // OPTIONAL this.minimum // in_window.org.pkijs.asn1.INTEGER
+ // OPTIONAL this.maximum // in_window.org.pkijs.asn1.INTEGER
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.GeneralSubtree.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.base = arguments[0].base || new in_window.org.pkijs.simpl.GENERAL_NAME();
+
+ if("minimum" in arguments[0])
+ this.minimum = arguments[0].minimum;
+
+ if("maximum" in arguments[0])
+ this.maximum = arguments[0].maximum;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.GeneralSubtree.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.GeneralSubtree({
+ names: {
+ base: {
+ names: {
+ block_name: "base"
+ }
+ },
+ minimum: "minimum",
+ maximum: "maximum"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.base = new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: asn1.result["base"] });
+
+ if("minimum" in asn1.result)
+ {
+ if(asn1.result["minimum"].value_block.is_hex_only)
+ this.minimum = asn1.result["minimum"];
+ else
+ this.minimum = asn1.result["minimum"].value_block.value_dec;
+ }
+
+ if("maximum" in asn1.result)
+ {
+ if(asn1.result["maximum"].value_block.is_hex_only)
+ this.maximum = asn1.result["maximum"];
+ else
+ this.maximum = asn1.result["maximum"].value_block.value_dec;
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.GeneralSubtree.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(this.base.toSchema());
+
+ if("minimum" in this)
+ {
+ var value_minimum = 0;
+
+ if(this.minimum instanceof in_window.org.pkijs.asn1.INTEGER)
+ value_minimum = this.minimum;
+ else
+ value_minimum = new in_window.org.pkijs.asn1.INTEGER({ value: this.minimum });
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [value_minimum]
+ }));
+ }
+
+ if("maximum" in this)
+ {
+ var value_maximum = 0;
+
+ if(this.maximum instanceof in_window.org.pkijs.asn1.INTEGER)
+ value_maximum = this.maximum;
+ else
+ value_maximum = new in_window.org.pkijs.asn1.INTEGER({ value: this.maximum });
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [value_maximum]
+ }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.GeneralSubtree.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ base: this.base.toJSON()
+ };
+
+ if("minimum" in this)
+ {
+ if((typeof this.minimum) === "number")
+ _object.minimum = this.minimum;
+ else
+ _object.minimum = this.minimum.toJSON();
+ }
+
+ if("maximum" in this)
+ {
+ if((typeof this.maximum) === "number")
+ _object.maximum = this.maximum;
+ else
+ _object.maximum = this.maximum.toJSON();
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "NameConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.NameConstraints =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.permittedSubtrees - Array of "simpl.x509.GeneralSubtree"
+ // OPTIONAL this.excludedSubtrees - Array of "simpl.x509.GeneralSubtree"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.NameConstraints.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("permittedSubtrees" in arguments[0])
+ this.permittedSubtrees = arguments[0].permittedSubtrees;
+
+ if("excludedSubtrees" in arguments[0])
+ this.excludedSubtrees = arguments[0].excludedSubtrees;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.NameConstraints.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.NameConstraints({
+ names: {
+ permittedSubtrees: "permittedSubtrees",
+ excludedSubtrees: "excludedSubtrees"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for NameConstraints");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("permittedSubtrees" in asn1.result)
+ {
+ this.permittedSubtrees = new Array();
+ var permited_array = asn1.result["permittedSubtrees"];
+
+ for(var i = 0; i < permited_array.length; i++)
+ this.permittedSubtrees.push(new in_window.org.pkijs.simpl.x509.GeneralSubtree({ schema: permited_array[i] }));
+ }
+
+ if("excludedSubtrees" in asn1.result)
+ {
+ this.excludedSubtrees = new Array();
+ var excluded_array = asn1.result["excludedSubtrees"];
+
+ for(var i = 0; i < excluded_array.length; i++)
+ this.excludedSubtrees.push(new in_window.org.pkijs.simpl.x509.GeneralSubtree({ schema: excluded_array[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.NameConstraints.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("permittedSubtrees" in this)
+ {
+ var permited_array = new Array();
+
+ for(var i = 0; i < this.permittedSubtrees.length; i++)
+ permited_array.push(this.permittedSubtrees[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [new in_window.org.pkijs.asn1.SEQUENCE({
+ value: permited_array
+ })]
+ }));
+ }
+
+ if("excludedSubtrees" in this)
+ {
+ var excluded_array = new Array();
+
+ for(var i = 0; i < this.excludedSubtrees.length; i++)
+ excluded_array.push(this.excludedSubtrees[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [new in_window.org.pkijs.asn1.SEQUENCE({
+ value: excluded_array
+ })]
+ }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.NameConstraints.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("permittedSubtrees" in this)
+ {
+ _object.permittedSubtrees = new Array();
+
+ for(var i = 0; i < this.permittedSubtrees.length; i++)
+ _object.permittedSubtrees.push(this.permittedSubtrees[i].toJSON());
+ }
+
+ if("excludedSubtrees" in this)
+ {
+ _object.excludedSubtrees = new Array();
+
+ for(var i = 0; i < this.excludedSubtrees.length; i++)
+ _object.excludedSubtrees.push(this.excludedSubtrees[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "BasicConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.BasicConstraints =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.cA - boolean value
+ // OPTIONAL this.pathLenConstraint - integer value
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.BasicConstraints.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("cA" in arguments[0])
+ this.cA = arguments[0].cA;
+
+ if("pathLenConstraint" in arguments[0])
+ this.pathLenConstraint = arguments[0].pathLenConstraint;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.BasicConstraints.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.BasicConstraints({
+ names: {
+ cA: "cA",
+ pathLenConstraint: "pathLenConstraint"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for BasicConstraints");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("cA" in asn1.result)
+ this.cA = asn1.result["cA"].value_block.value;
+
+ if("pathLenConstraint" in asn1.result)
+ this.pathLenConstraint = asn1.result["pathLenConstraint"].value_block.value_dec;
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.BasicConstraints.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("cA" in this)
+ output_array.push(new in_window.org.pkijs.asn1.BOOLEAN({ value: this.cA }));
+
+ if("pathLenConstraint" in this)
+ output_array.push(new in_window.org.pkijs.asn1.INTEGER({ value: this.pathLenConstraint }));
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.BasicConstraints.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("cA" in this)
+ _object.cA = this.cA;
+
+ if("pathLenConstraint" in this)
+ _object.pathLenConstraint = this.pathLenConstraint;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PolicyQualifierInfo" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyQualifierInfo =
+ function()
+ {
+ // #region Internal properties of the object
+ this.policyQualifierId = "";
+ this.qualifier = new in_window.org.pkijs.asn1.ANY();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PolicyQualifierInfo.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.policyQualifierId = arguments[0].policyQualifierId || "";
+ this.qualifier = arguments[0].qualifier || new in_window.org.pkijs.asn1.ANY();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyQualifierInfo.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PolicyQualifierInfo({
+ names: {
+ policyQualifierId: "policyQualifierId",
+ qualifier: "qualifier"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyQualifierInfo");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.policyQualifierId = asn1.result["policyQualifierId"].value_block.toString();
+ this.qualifier = asn1.result["qualifier"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyQualifierInfo.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.OID({ value: this.policyQualifierId }),
+ this.qualifier
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyQualifierInfo.prototype.toJSON =
+ function()
+ {
+ return {
+ policyQualifierId: this.policyQualifierId,
+ qualifier: this.qualifier.toJSON()
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PolicyInformation" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyInformation =
+ function()
+ {
+ // #region Internal properties of the object
+ this.policyIdentifier = "";
+ // OPTIONAL this.policyQualifiers = new Array(); // Array of "simpl.x509.PolicyQualifierInfo"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PolicyInformation.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.policyIdentifier = arguments[0].policyIdentifier || "";
+
+ if("policyQualifiers" in arguments[0])
+ this.policyQualifiers = arguments[0].policyQualifiers;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyInformation.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PolicyInformation({
+ names: {
+ policyIdentifier: "policyIdentifier",
+ policyQualifiers: "policyQualifiers"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyInformation");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.policyIdentifier = asn1.result["policyIdentifier"].value_block.toString();
+
+ if("policyQualifiers" in asn1.result)
+ {
+ this.policyQualifiers = new Array();
+ var qualifiers = asn1.result["policyQualifiers"];
+
+ for(var i = 0; i < qualifiers.length; i++)
+ this.policyQualifiers.push(new in_window.org.pkijs.simpl.x509.PolicyQualifierInfo({ schema: qualifiers[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyInformation.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.OID({ value: this.policyIdentifier }));
+
+ if("policyQualifiers" in this)
+ {
+ var qualifiers = new Array();
+
+ for(var i = 0; i < this.policyQualifiers.length; i++)
+ qualifiers.push(this.policyQualifiers[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.SEQUENCE({
+ value: qualifiers
+ }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyInformation.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ policyIdentifier: this.policyIdentifier
+ };
+
+ if("policyQualifiers" in this)
+ {
+ _object.policyQualifiers = new Array();
+
+ for(var i = 0; i < this.policyQualifiers.length; i++)
+ _object.policyQualifiers.push(this.policyQualifiers[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "CertificatePolicies" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CertificatePolicies =
+ function()
+ {
+ // #region Internal properties of the object
+ this.certificatePolicies = new Array(); // Array of "simpl.x509.PolicyInformation"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.CertificatePolicies.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.certificatePolicies = arguments[0].certificatePolicies || new Array(); // Array of "simpl.x509.PolicyInformation"
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CertificatePolicies.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.CertificatePolicies({
+ names: {
+ certificatePolicies: "certificatePolicies"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificatePolicies");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var policies = asn1.result["certificatePolicies"];
+
+ for(var i = 0; i < policies.length; i++)
+ this.certificatePolicies.push(new in_window.org.pkijs.simpl.x509.PolicyInformation({ schema: policies[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CertificatePolicies.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.certificatePolicies.length; i++)
+ output_array.push(this.certificatePolicies[i].toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CertificatePolicies.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ certificatePolicies: new Array()
+ };
+
+ for(var i = 0; i < this.certificatePolicies.length; i++)
+ _object.certificatePolicies.push(this.certificatePolicies[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "PolicyConstraints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyConstraints =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.requireExplicitPolicy = 0;
+ // OPTIONAL this.inhibitPolicyMapping = 0;
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.PolicyConstraints.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.requireExplicitPolicy = arguments[0].requireExplicitPolicy || 0;
+ this.inhibitPolicyMapping = arguments[0].inhibitPolicyMapping || 0;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyConstraints.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.PolicyConstraints({
+ names: {
+ requireExplicitPolicy: "requireExplicitPolicy",
+ inhibitPolicyMapping: "inhibitPolicyMapping"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyConstraints");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("requireExplicitPolicy" in asn1.result)
+ {
+ var field1 = asn1.result["requireExplicitPolicy"];
+
+ field1.id_block.tag_class = 1; // UNIVERSAL
+ field1.id_block.tag_number = 2; // INTEGER
+
+ var ber1 = field1.toBER(false);
+ var int1 = in_window.org.pkijs.fromBER(ber1);
+
+ this.requireExplicitPolicy = int1.result.value_block.value_dec;
+ }
+
+ if("inhibitPolicyMapping" in asn1.result)
+ {
+ var field2 = asn1.result["inhibitPolicyMapping"];
+
+ field2.id_block.tag_class = 1; // UNIVERSAL
+ field2.id_block.tag_number = 2; // INTEGER
+
+ var ber2 = field2.toBER(false);
+ var int2 = in_window.org.pkijs.fromBER(ber2);
+
+ this.inhibitPolicyMapping = int2.result.value_block.value_dec;
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyConstraints.prototype.toSchema =
+ function()
+ {
+ // #region Create correct values for output sequence
+ var output_array = new Array();
+
+ if("requireExplicitPolicy" in this)
+ {
+ var int1 = new in_window.org.pkijs.asn1.INTEGER({ value: this.requireExplicitPolicy });
+
+ int1.id_block.tag_class = 3; // CONTEXT-SPECIFIC
+ int1.id_block.tag_number = 0; // [0]
+
+ output_array.push(int1);
+ }
+
+ if("inhibitPolicyMapping" in this)
+ {
+ var int2 = new in_window.org.pkijs.asn1.INTEGER({ value: this.inhibitPolicyMapping });
+
+ int1.id_block.tag_class = 3; // CONTEXT-SPECIFIC
+ int1.id_block.tag_number = 1; // [1]
+
+ output_array.push(int2);
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.PolicyConstraints.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("requireExplicitPolicy" in this)
+ _object.requireExplicitPolicy = this.requireExplicitPolicy;
+
+ if("inhibitPolicyMapping" in this)
+ _object.inhibitPolicyMapping = this.inhibitPolicyMapping;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "ExtKeyUsage" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.ExtKeyUsage =
+ function()
+ {
+ // #region Internal properties of the object
+ this.keyPurposes = new Array(); // Array of strings (OIDs value for key purposes)
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.ExtKeyUsage.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.keyPurposes = arguments[0].keyPurposes || new Array(); // Array of strings (OIDs value for key purposes)
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.ExtKeyUsage.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.ExtKeyUsage({
+ names: {
+ keyPurposes: "keyPurposes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ExtKeyUsage");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var purposes = asn1.result["keyPurposes"];
+
+ for(var i = 0; i < purposes.length; i++)
+ this.keyPurposes.push(purposes[i].value_block.toString());
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.ExtKeyUsage.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.keyPurposes.length; i++)
+ output_array.push(new in_window.org.pkijs.asn1.OID({ value: this.keyPurposes[i] }));
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.ExtKeyUsage.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ keyPurposes: new Array()
+ };
+
+ for(var i = 0; i < this.keyPurposes.length; i++)
+ _object.keyPurposes.push(this.keyPurposes[i]);
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "DistributionPoint" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.DistributionPoint =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.distributionPoint // Array of "simpl.GENERAL_NAME" or a value of "simpl.RDN" type
+ // OPTIONAL this.reasons // BITSTRING value
+ // OPTIONAL this.cRLIssuer // Array of "simpl.GENERAL_NAME"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.DistributionPoint.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("distributionPoint" in arguments[0])
+ this.distributionPoint = arguments[0].distributionPoint;
+
+ if("reasons" in arguments[0])
+ this.reasons = arguments[0].reasons;
+
+ if("cRLIssuer" in arguments[0])
+ this.cRLIssuer = arguments[0].cRLIssuer;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.DistributionPoint.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.DistributionPoint({
+ names: {
+ distributionPoint: "distributionPoint",
+ distributionPoint_names: "distributionPoint_names",
+ reasons: "reasons",
+ cRLIssuer: "cRLIssuer",
+ cRLIssuer_names: "cRLIssuer_names"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for DistributionPoint");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("distributionPoint" in asn1.result)
+ {
+ if(asn1.result["distributionPoint"].id_block.tag_number == 0) // GENERAL_NAMES variant
+ {
+ this.distributionPoint = new Array();
+ var names = asn1.result["distributionPoint_names"];
+
+ for(var i = 0; i < names.length; i++)
+ this.distributionPoint.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: names[i] }));
+ }
+
+ if(asn1.result["distributionPoint"].id_block.tag_number == 1) // RDN variant
+ {
+ asn1.result["distributionPoint"].id_block.tag_class = 1; // UNIVERSAL
+ asn1.result["distributionPoint"].id_block.tag_number = 16; // SEQUENCE
+
+ this.distributionPoint = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["distributionPoint"] });
+ }
+ }
+
+ if("reasons" in asn1.result)
+ this.reasons = new in_window.org.pkijs.asn1.BITSTRING({ value_hex: asn1.result["reasons"].value_block.value_hex });
+
+ if("cRLIssuer" in asn1.result)
+ {
+ this.cRLIssuer = new Array();
+ var crl_names = asn1.result["cRLIssuer_names"];
+
+ for(var i = 0; i < crl_names; i++)
+ this.cRLIssuer.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: crl_names[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.DistributionPoint.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("distributionPoint" in this)
+ {
+ var internalValue;
+
+ if(this.distributionPoint instanceof Array)
+ {
+ var namesArray = new Array();
+
+ for(var i = 0; i < this.distributionPoint.length; i++)
+ namesArray.push(this.distributionPoint[i].toSchema());
+
+ internalValue = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: namesArray
+ });
+ }
+ else
+ {
+ internalValue = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value: [this.distributionPoint.toSchema()]
+ });
+ }
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [internalValue]
+ }));
+ }
+
+ if("reasons" in this)
+ {
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value_hex: this.reasons.value_block.value_hex
+ }));
+ }
+
+ if("cRLIssuer" in this)
+ {
+ var value = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ }
+ });
+
+ for(var i = 0; i < this.cRLIssuer.length; i++)
+ value.value_block.value.push(this.cRLIssuer[i].toSchema());
+
+ output_array.push(value);
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.DistributionPoint.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("distributionPoint" in this)
+ {
+ if(this.distributionPoint instanceof Array)
+ {
+ _object.distributionPoint = new Array();
+
+ for(var i = 0; i < this.distributionPoint.length; i++)
+ _object.distributionPoint.push(this.distributionPoint[i].toJSON());
+ }
+ else
+ _object.distributionPoint = this.distributionPoint.toJSON();
+ }
+
+ if("reasons" in this)
+ _object.reasons = this.reasons.toJSON();
+
+ if("cRLIssuer" in this)
+ {
+ _object.cRLIssuer = new Array();
+
+ for(var i = 0; i < this.cRLIssuer.length; i++)
+ _object.cRLIssuer.push(this.cRLIssuer[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "CRLDistributionPoints" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CRLDistributionPoints =
+ function()
+ {
+ // #region Internal properties of the object
+ this.distributionPoints = new Array(); // Array of "simpl.x509.DistributionPoint"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.CRLDistributionPoints.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.distributionPoints = arguments[0].distributionPoints || new Array(); // Array of "simpl.x509.DistributionPoint"
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CRLDistributionPoints.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.CRLDistributionPoints({
+ names: {
+ distributionPoints: "distributionPoints"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CRLDistributionPoints");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var points = asn1.result["distributionPoints"];
+
+ for(var i = 0; i < points.length; i++)
+ this.distributionPoints.push(new in_window.org.pkijs.simpl.x509.DistributionPoint({ schema: points[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CRLDistributionPoints.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.distributionPoints.length; i++)
+ output_array.push(this.distributionPoints[i].toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.CRLDistributionPoints.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ distributionPoints: new Array()
+ };
+
+ for(var i = 0; i < this.distributionPoints.length; i++)
+ _object.distributionPoints.push(this.distributionPoints[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "AccessDescription" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AccessDescription =
+ function()
+ {
+ // #region Internal properties of the object
+ this.accessMethod = "";
+ this.accessLocation = new in_window.org.pkijs.simpl.GENERAL_NAME();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.AccessDescription.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.accessMethod = arguments[0].accessMethod || "";
+ this.accessLocation = arguments[0].accessLocation || new in_window.org.pkijs.simpl.GENERAL_NAME();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AccessDescription.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.AccessDescription({
+ names: {
+ accessMethod: "accessMethod",
+ accessLocation: {
+ names: {
+ block_name: "accessLocation"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AccessDescription");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.accessMethod = asn1.result["accessMethod"].value_block.toString();
+ this.accessLocation = new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: asn1.result["accessLocation"] });
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AccessDescription.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.OID({ value: this.accessMethod }),
+ this.accessLocation.toSchema()
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.AccessDescription.prototype.toJSON =
+ function()
+ {
+ return {
+ accessMethod: this.accessMethod,
+ accessLocation: this.accessLocation.toJSON()
+ };
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "AuthorityInfoAccess" and "SubjectInfoAccess" types of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.InfoAccess =
+ function()
+ {
+ // #region Internal properties of the object
+ this.accessDescriptions = new Array(); // Array of "simpl.x509.AccessDescription"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.InfoAccess.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.accessDescriptions = arguments[0].accessDescriptions || new Array(); // Array of "simpl.x509.DistributionPoint"
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.InfoAccess.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.InfoAccess({
+ names: {
+ accessDescriptions: "accessDescriptions"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for InfoAccess");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ var descriptions = asn1.result["accessDescriptions"];
+
+ for(var i = 0; i < descriptions.length; i++)
+ this.accessDescriptions.push(new in_window.org.pkijs.simpl.x509.AccessDescription({ schema: descriptions[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.InfoAccess.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ for(var i = 0; i < this.accessDescriptions.length; i++)
+ output_array.push(this.accessDescriptions[i].toSchema());
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.InfoAccess.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ accessDescriptions: new Array()
+ };
+
+ for(var i = 0; i < this.accessDescriptions.length; i++)
+ _object.accessDescriptions.push(this.accessDescriptions[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "IssuingDistributionPoint" type of extension
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.IssuingDistributionPoint =
+ function()
+ {
+ // #region Internal properties of the object
+ // OPTIONAL this.distributionPoint // Array of "simpl.GENERAL_NAME" or a value of "simpl.RDN" type
+ // OPTIONAL this.onlyContainsUserCerts // BOOLEAN flag
+ // OPTIONAL this.onlyContainsCACerts // BOOLEAN flag
+ // OPTIONAL this.onlySomeReasons // BITSTRING
+ // OPTIONAL this.indirectCRL // BOOLEAN flag
+ // OPTIONAL this.onlyContainsAttributeCerts // BOOLEAN flag
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.x509.IssuingDistributionPoint.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ if("distributionPoint" in arguments[0])
+ this.distributionPoint = arguments[0].distributionPoint;
+
+ if("onlyContainsUserCerts" in arguments[0])
+ this.onlyContainsUserCerts = arguments[0].onlyContainsUserCerts;
+
+ if("onlyContainsCACerts" in arguments[0])
+ this.onlyContainsCACerts = arguments[0].onlyContainsCACerts;
+
+ if("onlySomeReasons" in arguments[0])
+ this.onlySomeReasons = arguments[0].onlySomeReasons;
+
+ if("indirectCRL" in arguments[0])
+ this.indirectCRL = arguments[0].indirectCRL;
+
+ if("onlyContainsAttributeCerts" in arguments[0])
+ this.onlyContainsAttributeCerts = arguments[0].onlyContainsAttributeCerts;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.IssuingDistributionPoint.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.x509.IssuingDistributionPoint({
+ names: {
+ distributionPoint: "distributionPoint",
+ distributionPoint_names: "distributionPoint_names",
+ onlyContainsUserCerts: "onlyContainsUserCerts",
+ onlyContainsCACerts: "onlyContainsCACerts",
+ onlySomeReasons: "onlySomeReasons",
+ indirectCRL: "indirectCRL",
+ onlyContainsAttributeCerts: "onlyContainsAttributeCerts"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for IssuingDistributionPoint");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ if("distributionPoint" in asn1.result)
+ {
+ if(asn1.result["distributionPoint"].id_block.tag_number == 0) // GENERAL_NAMES variant
+ {
+ this.distributionPoint = new Array();
+ var names = asn1.result["distributionPoint_names"];
+
+ for(var i = 0; i < names.length; i++)
+ this.distributionPoint.push(new in_window.org.pkijs.simpl.GENERAL_NAME({ schema: names[i] }));
+ }
+
+ if(asn1.result["distributionPoint"].id_block.tag_number == 1) // RDN variant
+ {
+ asn1.result["distributionPoint"].id_block.tag_class = 1; // UNIVERSAL
+ asn1.result["distributionPoint"].id_block.tag_number = 16; // SEQUENCE
+
+ this.distributionPoint = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["distributionPoint"] });
+ }
+ }
+
+ if("onlyContainsUserCerts" in asn1.result)
+ {
+ var view = new Uint8Array(asn1.result["onlyContainsUserCerts"].value_block.value_hex);
+ this.onlyContainsUserCerts = (view[0] !== 0x00);
+ }
+
+ if("onlyContainsCACerts" in asn1.result)
+ {
+ var view = new Uint8Array(asn1.result["onlyContainsCACerts"].value_block.value_hex);
+ this.onlyContainsCACerts = (view[0] !== 0x00);
+ }
+
+ if("onlySomeReasons" in asn1.result)
+ {
+ var view = new Uint8Array(asn1.result["onlySomeReasons"].value_block.value_hex);
+ this.onlySomeReasons = view[0];
+ }
+
+ if("indirectCRL" in asn1.result)
+ {
+ var view = new Uint8Array(asn1.result["indirectCRL"].value_block.value_hex);
+ this.indirectCRL = (view[0] !== 0x00);
+ }
+
+ if("onlyContainsAttributeCerts" in asn1.result)
+ {
+ var view = new Uint8Array(asn1.result["onlyContainsAttributeCerts"].value_block.value_hex);
+ this.onlyContainsAttributeCerts = (view[0] !== 0x00);
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.IssuingDistributionPoint.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("distributionPoint" in this)
+ {
+ var value;
+
+ if(this.distributionPoint instanceof Array)
+ {
+ value = new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ }
+ });
+
+ for(var i = 0; i < this.distributionPoint.length; i++)
+ value.value_block.value.push(this.distributionPoint[i].toSchema());
+ }
+ else
+ {
+ value = this.distributionPoint.toSchema();
+
+ value.id_block.tag_class = 3; // CONTEXT - SPECIFIC
+ value.id_block.tag_number = 1; // [1]
+ }
+
+ output_array.push(value);
+ }
+
+ if("onlyContainsUserCerts" in this)
+ {
+ var buffer = new ArrayBuffer(1);
+ var view = new Uint8Array(buffer);
+
+ view[0] = (this.onlyContainsUserCerts === false) ? 0x00 : 0xFF;
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value_hex: buffer
+ }));
+ }
+
+ if("onlyContainsCACerts" in this)
+ {
+ var buffer = new ArrayBuffer(1);
+ var view = new Uint8Array(buffer);
+
+ view[0] = (this.onlyContainsCACerts === false) ? 0x00 : 0xFF;
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ value_hex: buffer
+ }));
+ }
+
+ if("onlySomeReasons" in this)
+ {
+ var buffer = new ArrayBuffer(1);
+ var view = new Uint8Array(buffer);
+
+ view[0] = this.onlySomeReasons;
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ value_hex: buffer
+ }));
+ }
+
+ if("indirectCRL" in this)
+ {
+ var buffer = new ArrayBuffer(1);
+ var view = new Uint8Array(buffer);
+
+ view[0] = (this.indirectCRL === false) ? 0x00 : 0xFF;
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 4 // [4]
+ },
+ value_hex: buffer
+ }));
+ }
+
+ if("onlyContainsAttributeCerts" in this)
+ {
+ var buffer = new ArrayBuffer(1);
+ var view = new Uint8Array(buffer);
+
+ view[0] = (this.onlyContainsAttributeCerts === false) ? 0x00 : 0xFF;
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 5 // [5]
+ },
+ value_hex: buffer
+ }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.x509.IssuingDistributionPoint.prototype.toJSON =
+ function()
+ {
+ var _object = {};
+
+ if("distributionPoint" in this)
+ {
+ if(this.distributionPoint instanceof Array)
+ {
+ _object.distributionPoint = new Array();
+
+ for(var i = 0; i < this.distributionPoint.length; i++)
+ _object.distributionPoint.push(this.distributionPoint[i].toJSON());
+ }
+ else
+ _object.distributionPoint = this.distributionPoint.toJSON();
+ }
+
+ if("onlyContainsUserCerts" in this)
+ _object.onlyContainsUserCerts = this.onlyContainsUserCerts;
+
+ if("onlyContainsCACerts" in this)
+ _object.onlyContainsCACerts = this.onlyContainsCACerts;
+
+ if("onlySomeReasons" in this)
+ _object.onlySomeReasons = this.onlySomeReasons.toJSON();
+
+ if("indirectCRL" in this)
+ _object.indirectCRL = this.indirectCRL;
+
+ if("onlyContainsAttributeCerts" in this)
+ _object.onlyContainsAttributeCerts = this.onlyContainsAttributeCerts;
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "Extension" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSION =
+ function()
+ {
+ // #region Internal properties of the object
+ this.extnID = "";
+ this.critical = false;
+ this.extnValue = new in_window.org.pkijs.asn1.OCTETSTRING();
+
+ // OPTIONAL this.parsedValue - Parsed "extnValue" in case of well-known "extnID"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.EXTENSION.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.extnID = (arguments[0].extnID || "");
+ this.critical = (arguments[0].critical || false);
+ if("extnValue" in arguments[0])
+ this.extnValue = new in_window.org.pkijs.asn1.OCTETSTRING({ value_hex: arguments[0].extnValue });
+ else
+ this.extnValue = new in_window.org.pkijs.asn1.OCTETSTRING();
+
+ if("parsedValue" in arguments[0])
+ this.parsedValue = arguments[0].parsedValue;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSION.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.EXTENSION({
+ names: {
+ extnID: "extnID",
+ critical: "critical",
+ extnValue: "extnValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EXTENSION");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.extnID = asn1.result.extnID.value_block.toString();
+ if("critical" in asn1.result)
+ this.critical = asn1.result.critical.value_block.value;
+ this.extnValue = asn1.result.extnValue;
+
+ // #region Get "parsedValue" for well-known extensions
+ var asn1 = in_window.org.pkijs.fromBER(this.extnValue.value_block.value_hex);
+ if(asn1.offset === (-1))
+ return;
+
+ switch(this.extnID)
+ {
+ case "2.5.29.9": // SubjectDirectoryAttributes
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.SubjectDirectoryAttributes({ schema: asn1.result });
+ break;
+ case "2.5.29.14": // SubjectKeyIdentifier
+ this.parsedValue = asn1.result; // Should be just a simple OCTETSTRING
+ break;
+ case "2.5.29.15": // KeyUsage
+ this.parsedValue = asn1.result; // Should be just a simple BITSTRING
+ break;
+ case "2.5.29.16": // PrivateKeyUsagePeriod
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.PrivateKeyUsagePeriod({ schema: asn1.result });
+ break;
+ case "2.5.29.17": // SubjectAltName
+ case "2.5.29.18": // IssuerAltName
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.AltName({ schema: asn1.result });
+ break;
+ case "2.5.29.19": // BasicConstraints
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.BasicConstraints({ schema: asn1.result });
+ break;
+ case "2.5.29.20": // CRLNumber
+ case "2.5.29.27": // BaseCRLNumber (delta CRL indicator)
+ this.parsedValue = asn1.result; // Should be just a simple INTEGER
+ break;
+ case "2.5.29.21": // CRLReason
+ this.parsedValue = asn1.result; // Should be just a simple ENUMERATED
+ break;
+ case "2.5.29.24": // InvalidityDate
+ this.parsedValue = asn1.result; // Should be just a simple GeneralizedTime
+ break;
+ case "2.5.29.28": // IssuingDistributionPoint
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.IssuingDistributionPoint({ schema: asn1.result });
+ break;
+ case "2.5.29.29": // CertificateIssuer
+ this.parsedValue = new in_window.org.pkijs.simpl.GENERAL_NAMES({ schema: asn1.result }); // Should be just a simple
+ break;
+ case "2.5.29.30": // NameConstraints
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.NameConstraints({ schema: asn1.result });
+ break;
+ case "2.5.29.31": // CRLDistributionPoints
+ case "2.5.29.46": // FreshestCRL
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.CRLDistributionPoints({ schema: asn1.result });
+ break;
+ case "2.5.29.32": // CertificatePolicies
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.CertificatePolicies({ schema: asn1.result });
+ break;
+ case "2.5.29.33": // PolicyMappings
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.PolicyMappings({ schema: asn1.result });
+ break;
+ case "2.5.29.35": // AuthorityKeyIdentifier
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.AuthorityKeyIdentifier({ schema: asn1.result });
+ break;
+ case "2.5.29.36": // PolicyConstraints
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.PolicyConstraints({ schema: asn1.result });
+ break;
+ case "2.5.29.37": // ExtKeyUsage
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.ExtKeyUsage({ schema: asn1.result });
+ break;
+ case "2.5.29.54": // InhibitAnyPolicy
+ this.parsedValue = asn1.result; // Should be just a simple INTEGER
+ break;
+ case "1.3.6.1.5.5.7.1.1": // AuthorityInfoAccess
+ case "1.3.6.1.5.5.7.1.11": // SubjectInfoAccess
+ this.parsedValue = new in_window.org.pkijs.simpl.x509.InfoAccess({ schema: asn1.result });
+ break;
+ default:
+ }
+ // #endregion
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSION.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.OID({ value: this.extnID }));
+
+ if(this.critical)
+ output_array.push(new in_window.org.pkijs.asn1.BOOLEAN({ value: this.critical }));
+
+ output_array.push(this.extnValue);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSION.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ extnID: this.extnID,
+ critical: this.critical,
+ extnValue: this.extnValue.toJSON()
+ };
+
+ if("parsedValue" in this)
+ _object.parsedValue = this.parsedValue.toJSON();
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "Extensions" type (sequence of many Extension)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSIONS =
+ function()
+ {
+ // #region Internal properties of the object
+ this.extensions_array = new Array();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.EXTENSIONS.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ this.extensions_array = (arguments[0].extensions_array || (new Array()));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSIONS.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.EXTENSIONS({
+ names: {
+ extensions: "extensions"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EXTENSIONS");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ for(var i = 0; i < asn1.result.extensions.length; i++)
+ this.extensions_array.push(new in_window.org.pkijs.simpl.EXTENSION({ schema: asn1.result.extensions[i] }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSIONS.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ var extension_schemas = new Array();
+
+ for(var i = 0; i < this.extensions_array.length; i++)
+ extension_schemas.push(this.extensions_array[i].toSchema());
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: extension_schemas
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.EXTENSIONS.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ extensions_array: new Array()
+ };
+
+ for(var i = 0; i < this.extensions_array.length; i++)
+ _object.extensions_array.push(this.extensions_array[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for X.509 v3 certificate (RFC5280)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT =
+ function()
+ {
+ // #region Internal properties of the object
+ // #region Properties from certificate TBS part
+ this.tbs = new ArrayBuffer(0); // Encoded value of certificate TBS (need to have it for certificate validation)
+
+ // OPTIONAL this.version = 0;
+ this.serialNumber = new in_window.org.pkijs.asn1.INTEGER(); // Might be a very long integer value
+ this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate TBS part
+ this.issuer = new in_window.org.pkijs.simpl.RDN();
+ this.notBefore = new in_window.org.pkijs.simpl.TIME();
+ this.notAfter = new in_window.org.pkijs.simpl.TIME();
+ this.subject = new in_window.org.pkijs.simpl.RDN();
+ this.subjectPublicKeyInfo = new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO();
+ // OPTIONAL this.issuerUniqueID = new ArrayBuffer(0); // IMPLICIT bistring value
+ // OPTIONAL this.subjectUniqueID = new ArrayBuffer(0); // IMPLICIT bistring value
+ // OPTIONAL this.extensions = new Array(); // Array of "simpl.EXTENSION"
+ // #endregion
+
+ // #region Properties from certificate major part
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate major part
+ this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.CERT.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ // #region Properties from certificate TBS part
+ this.tbs = arguments[0].tbs || new ArrayBuffer(0);
+
+ if("version" in arguments[0])
+ this.version = arguments[0].version;
+ this.serialNumber = arguments[0].serialNumber || new in_window.org.pkijs.asn1.INTEGER(); // Might be a very long integer value
+ this.signature = arguments[0].signature || new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate TBS part
+ this.issuer = arguments[0].issuer || new in_window.org.pkijs.simpl.RDN();
+ this.notBefore = arguments[0].not_before || new in_window.org.pkijs.simpl.TIME();
+ this.notAfter = arguments[0].not_after || new in_window.org.pkijs.simpl.TIME();
+ this.subject = arguments[0].subject || new in_window.org.pkijs.simpl.RDN();
+ this.subjectPublicKeyInfo = arguments[0].subjectPublicKeyInfo || new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO();
+ if("issuerUniqueID" in arguments[0])
+ this.issuerUniqueID = arguments[0].issuerUniqueID;
+ if("subjectUniqueID" in arguments[0])
+ this.subjectUniqueID = arguments[0].subjectUniqueID;
+ if("extensions" in arguments[0])
+ this.extensions = arguments[0].extensions;
+ // #endregion
+
+ // #region Properties from certificate major part
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate major part
+ this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.CERT({
+ names: {
+ tbsCertificate: {
+ names: {
+ extensions: {
+ names: {
+ extensions: "tbsCertificate.extensions"
+ }
+ }
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CERT");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.tbs = asn1.result["tbsCertificate"].value_before_decode;
+
+ if("tbsCertificate.version" in asn1.result)
+ this.version = asn1.result["tbsCertificate.version"].value_block.value_dec;
+ this.serialNumber = asn1.result["tbsCertificate.serialNumber"];
+ this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["tbsCertificate.signature"] });
+ this.issuer = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["tbsCertificate.issuer"] });
+ this.notBefore = new in_window.org.pkijs.simpl.TIME({ schema: asn1.result["tbsCertificate.notBefore"] });
+ this.notAfter = new in_window.org.pkijs.simpl.TIME({ schema: asn1.result["tbsCertificate.notAfter"] });
+ this.subject = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["tbsCertificate.subject"] });
+ this.subjectPublicKeyInfo = new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO({ schema: asn1.result["tbsCertificate.subjectPublicKeyInfo"] });
+ if("tbsCertificate.issuerUniqueID" in asn1.result)
+ this.issuerUniqueID = asn1.result["tbsCertificate.issuerUniqueID"].value_block.value_hex;
+ if("tbsCertificate.subjectUniqueID" in asn1.result)
+ this.issuerUniqueID = asn1.result["tbsCertificate.subjectUniqueID"].value_block.value_hex;
+ if("tbsCertificate.extensions" in asn1.result)
+ {
+ this.extensions = new Array();
+
+ var extensions = asn1.result["tbsCertificate.extensions"];
+
+ for(var i = 0; i < extensions.length; i++)
+ this.extensions.push(new in_window.org.pkijs.simpl.EXTENSION({ schema: extensions[i] }));
+ }
+
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["signatureAlgorithm"] });
+ this.signatureValue = asn1.result["signatureValue"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.encodeTBS =
+ function()
+ {
+ /// <summary>Create ASN.1 schema for existing values of TBS part for the certificate</summary>
+
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("version" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ value: this.version }) // EXPLICIT integer value
+ ]
+ }));
+
+ output_array.push(this.serialNumber);
+ output_array.push(this.signature.toSchema());
+ output_array.push(this.issuer.toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ this.notBefore.toSchema(),
+ this.notAfter.toSchema()
+ ]
+ }));
+
+ output_array.push(this.subject.toSchema());
+ output_array.push(this.subjectPublicKeyInfo.toSchema());
+
+ if("issuerUniqueID" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 1 // [1]
+ },
+ value_hex: this.issuerUniqueID
+ }));
+ if("subjectUniqueID" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_PRIMITIVE({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 2 // [2]
+ },
+ value_hex: this.subjectUniqueID
+ }));
+
+ if("subjectUniqueID" in this)
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ value: [this.extensions.toSchema()]
+ }));
+
+ if("extensions" in this)
+ {
+ var extensions = new Array();
+
+ for(var i = 0; i < this.extensions.length; i++)
+ extensions.push(this.extensions[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 3 // [3]
+ },
+ value: [new in_window.org.pkijs.asn1.SEQUENCE({
+ value: extensions
+ })]
+ }));
+ }
+ // #endregion
+
+ // #region Create and return output sequence
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.toSchema =
+ function(encodeFlag)
+ {
+ /// <param name="encodeFlag" type="Boolean">If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.</param>
+
+ if(typeof encodeFlag === "undefined")
+ encodeFlag = false;
+
+ var tbs_schema = {};
+
+ // #region Decode stored TBS value
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored certificate TBS part
+ return in_window.org.pkijs.schema.CERT().value[0];
+
+ var tbs_asn1 = in_window.org.pkijs.fromBER(this.tbs);
+
+ tbs_schema = tbs_asn1.result;
+ }
+ // #endregion
+ // #region Create TBS schema via assembling from TBS parts
+ else
+ tbs_schema = in_window.org.pkijs.simpl.CERT.prototype.encodeTBS.call(this);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ tbs_schema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.verify =
+ function()
+ {
+ /// <summary>!!! Works well in Chrome dev versions only (April 2014th) !!!</summary>
+ /// <returns type="Promise">Returns a new Promise object (in case of error), or a result of "crypto.subtle.veryfy" function</returns>
+
+ // #region Global variables
+ var sequence = Promise.resolve();
+
+ var subjectPublicKeyInfo = {};
+
+ var signature = this.signatureValue;
+ var tbs = this.tbs;
+
+ var _this = this;
+ // #endregion
+
+ // #region Set correct "subjectPublicKeyInfo" value
+ if(arguments[0] instanceof Object)
+ {
+ if("issuerCertificate" in arguments[0]) // Must be of type "simpl.CERT"
+ subjectPublicKeyInfo = arguments[0].issuerCertificate.subjectPublicKeyInfo;
+ }
+ else
+ {
+ if(this.issuer.isEqual(this.subject)) // Self-signed certificate
+ subjectPublicKeyInfo = this.subjectPublicKeyInfo;
+ }
+
+ if((subjectPublicKeyInfo instanceof in_window.org.pkijs.simpl.PUBLIC_KEY_INFO) === false)
+ return new Promise(function(resolve, reject) { reject("Please provide issuer certificate as a parameter"); });
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Find signer's hashing algorithm
+ var sha_algorithm = in_window.org.pkijs.getHashAlgorithm(this.signatureAlgorithm);
+ if(sha_algorithm === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+ // #endregion
+
+ // #region Importing public key
+ sequence = sequence.then(
+ function()
+ {
+ // #region Get information about public key algorithm and default parameters for import
+ var algorithmObject = in_window.org.pkijs.getAlgorithmByOID(_this.signatureAlgorithm.algorithm_id);
+ if(("name" in algorithmObject) === false)
+ return new Promise(function(resolve, reject) { reject("Unsupported public key algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+
+ var algorithm_name = algorithmObject.name;
+
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(algorithm_name, "importkey");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ var publicKeyInfo_schema = subjectPublicKeyInfo.toSchema();
+ var publicKeyInfo_buffer = publicKeyInfo_schema.toBER(false);
+ var publicKeyInfo_view = new Uint8Array(publicKeyInfo_buffer);
+
+ return crypto.importKey("spki", publicKeyInfo_view, algorithm.algorithm, true, algorithm.usages);
+ }
+ );
+ // #endregion
+
+ // #region Verify signature for the certificate
+ sequence = sequence.then(
+ function(publicKey)
+ {
+ // #region Get default algorithm parameters for verification
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(publicKey.algorithm.name, "verify");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ // #region Special case for ECDSA signatures
+ var signature_value = signature.value_block.value_hex;
+
+ if(publicKey.algorithm.name === "ECDSA")
+ {
+ var asn1 = in_window.org.pkijs.fromBER(signature_value);
+ signature_value = in_window.org.pkijs.createECDSASignatureFromCMS(asn1.result);
+ }
+ // #endregion
+
+ // #region Special case for RSA-PSS
+ if(publicKey.algorithm.name === "RSA-PSS")
+ {
+ var pssParameters;
+
+ try
+ {
+ pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params({ schema: _this.signatureAlgorithm.algorithm_params });
+ }
+ catch(ex)
+ {
+ return new Promise(function(resolve, reject) { reject(ex); });
+ }
+
+ if("saltLength" in pssParameters)
+ algorithm.algorithm.saltLength = pssParameters.saltLength;
+ else
+ algorithm.algorithm.saltLength = 20;
+
+ var hash_algo = "SHA-1";
+
+ if("hashAlgorithm" in pssParameters)
+ {
+ var hashAlgorithm = in_window.org.pkijs.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithm_id);
+ if(("name" in hashAlgorithm) === false)
+ return new Promise(function(resolve, reject) { reject("Unrecognized hash algorithm: " + pssParameters.hashAlgorithm.algorithm_id); });
+
+ hash_algo = hashAlgorithm.name;
+ }
+
+ algorithm.algorithm.hash.name = hash_algo;
+ }
+ // #endregion
+
+ return crypto.verify(algorithm.algorithm,
+ publicKey,
+ new Uint8Array(signature_value),
+ new Uint8Array(tbs));
+ }
+ );
+ // #endregion
+
+ return sequence;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.sign =
+ function(privateKey, hashAlgorithm)
+ {
+ /// <param name="privateKey" type="CryptoKey">Private key for "subjectPublicKeyInfo" structure</param>
+ /// <param name="hashAlgorithm" type="String" optional="true">Hashing algorithm. Default SHA-1</param>
+
+ // #region Initial variables
+ var _this = this;
+ // #endregion
+
+ // #region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return new Promise(function(resolve, reject) { reject("Need to provide a private key for signing"); });
+ // #endregion
+
+ // #region Get hashing algorithm
+ if(typeof hashAlgorithm === "undefined")
+ hashAlgorithm = "SHA-1";
+ else
+ {
+ // #region Simple check for supported algorithm
+ var oid = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(oid === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+ // #endregion
+ }
+ // #endregion
+
+ // #region Get a "default parameters" for current algorithm
+ var defParams = in_window.org.pkijs.getAlgorithmParameters(privateKey.algorithm.name, "sign");
+ defParams.algorithm.hash.name = hashAlgorithm;
+ // #endregion
+
+ // #region Fill internal structures base on "privateKey" and "hashAlgorithm"
+ switch(privateKey.algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ case "ECDSA":
+ _this.signature.algorithm_id = in_window.org.pkijs.getOIDByAlgorithm(defParams.algorithm);
+ _this.signatureAlgorithm.algorithm_id = _this.signature.algorithm_id;
+ break;
+ case "RSA-PSS":
+ {
+ // #region Set "saltLength" as a length (in octets) of hash function result
+ switch(hashAlgorithm.toUpperCase())
+ {
+ case "SHA-256":
+ defParams.algorithm.saltLength = 32;
+ break;
+ case "SHA-384":
+ defParams.algorithm.saltLength = 48;
+ break;
+ case "SHA-512":
+ defParams.algorithm.saltLength = 64;
+ break;
+ default:
+ }
+ // #endregion
+
+ // #region Fill "RSASSA_PSS_params" object
+ var paramsObject = {};
+
+ if(hashAlgorithm.toUpperCase() !== "SHA-1")
+ {
+ var hashAlgorithmOID = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(hashAlgorithmOID === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+
+ paramsObject.hashAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: hashAlgorithmOID,
+ algorithm_params: new in_window.org.pkijs.asn1.NULL()
+ });
+
+ paramsObject.maskGenAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.8", // MGF1
+ algorithm_params: paramsObject.hashAlgorithm.toSchema()
+ })
+ }
+
+ if(defParams.algorithm.saltLength !== 20)
+ paramsObject.saltLength = defParams.algorithm.saltLength;
+
+ var pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params(paramsObject);
+ // #endregion
+
+ // #region Automatically set signature algorithm
+ _this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.10",
+ algorithm_params: pssParameters.toSchema()
+ });
+ _this.signatureAlgorithm = _this.signature; // Must be the same
+ // #endregion
+ }
+ break;
+ default:
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + privateKey.algorithm.name); });
+ }
+ // #endregion
+
+ // #region Create TBS data for signing
+ _this.tbs = in_window.org.pkijs.simpl.CERT.prototype.encodeTBS.call(this).toBER(false);
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Signing TBS data on provided private key
+ return crypto.sign(defParams.algorithm,
+ privateKey,
+ new Uint8Array(_this.tbs)).then(
+ function(result)
+ {
+ // #region Special case for ECDSA algorithm
+ if(defParams.algorithm.name === "ECDSA")
+ result = in_window.org.pkijs.createCMSECDSASignature(result);
+ // #endregion
+
+ _this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING({ value_hex: result });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject) { reject("Signing error: " + error); });
+ }
+ );
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.getPublicKey =
+ function()
+ {
+ /// <summary>Importing public key for current certificate</summary>
+
+ // #region Initial variables
+ var algorithm;
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Find correct algorithm for imported public key
+ if(arguments[0] instanceof Object)
+ {
+ if("algorithm" in arguments[0])
+ algorithm = arguments[0].algorithm;
+ else
+ return new Promise(function(resolve, reject) { reject("Absent mandatory parameter \"algorithm\""); });
+ }
+ else
+ {
+ // #region Find signer's hashing algorithm
+ var sha_algorithm = in_window.org.pkijs.getHashAlgorithm(this.signatureAlgorithm);
+ if(sha_algorithm === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+ // #endregion
+
+ // #region Get information about public key algorithm and default parameters for import
+ var algorithmObject = in_window.org.pkijs.getAlgorithmByOID(this.signatureAlgorithm.algorithm_id);
+ if(("name" in algorithmObject) === false)
+ return new Promise(function(resolve, reject) { reject("Unsupported public key algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+
+ var algorithm_name = algorithmObject.name;
+
+ algorithm = in_window.org.pkijs.getAlgorithmParameters(algorithm_name, "importkey");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+ }
+ // #endregion
+
+ // #region Get neccessary values from internal fields for current certificate
+ var publicKeyInfo_schema = this.subjectPublicKeyInfo.toSchema();
+ var publicKeyInfo_buffer = publicKeyInfo_schema.toBER(false);
+ var publicKeyInfo_view = new Uint8Array(publicKeyInfo_buffer);
+ // #endregion
+
+ return crypto.importKey("spki", publicKeyInfo_view, algorithm.algorithm, true, algorithm.usages);
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.getKeyHash =
+ function()
+ {
+ /// <summary>Get SHA-1 hash value for subject public key</summary>
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ return crypto.digest({ name: "sha-1" }, new Uint8Array(this.subjectPublicKeyInfo.subjectPublicKey.value_block.value_hex));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ tbs: in_window.org.pkijs.bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ serialNumber: this.serialNumber.toJSON(),
+ signature: this.signature.toJSON(),
+ issuer: this.issuer.toJSON(),
+ notBefore: this.notBefore.toJSON(),
+ notAfter: this.notAfter.toJSON(),
+ subject: this.subject.toJSON(),
+ subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if("version" in this)
+ _object.version = this.version;
+
+ if("issuerUniqueID" in this)
+ _object.issuerUniqueID = in_window.org.pkijs.bufferToHexCodes(this.issuerUniqueID, 0, this.issuerUniqueID.byteLength);
+
+ if("subjectUniqueID" in this)
+ _object.subjectUniqueID = in_window.org.pkijs.bufferToHexCodes(this.subjectUniqueID, 0, this.subjectUniqueID.byteLength);
+
+ if("extensions" in this)
+ {
+ _object.extensions = new Array();
+
+ for(var i = 0; i < this.extensions.length; i++)
+ _object.extensions.push(this.extensions[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "revoked certificate" type (to use in CRL)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.REV_CERT =
+ function()
+ {
+ // #region Internal properties of the object
+ this.userCertificate = new in_window.org.pkijs.asn1.INTEGER();
+ this.revocationDate = new in_window.org.pkijs.simpl.TIME();
+ // OPTIONAL this.crlEntryExtensions = new Array(); // Array of "in_window.org.pkijs.simpl.EXTENSION");
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.REV_CERT.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.userCertificate = arguments[0].userCertificate || new in_window.org.pkijs.asn1.INTEGER();
+ this.revocationDate = arguments[0].revocationDate || new in_window.org.pkijs.simpl.TIME();
+ if("crlEntryExtensions" in arguments[0])
+ this.crlEntryExtensions = arguments[0].crlEntryExtensions;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.REV_CERT.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.INTEGER({ name: "userCertificate" }),
+ in_window.org.pkijs.schema.TIME({
+ names: {
+ utcTimeName: "revocationDate",
+ generalTimeName: "revocationDate"
+ }
+ }),
+ in_window.org.pkijs.schema.EXTENSIONS({
+ names: {
+ block_name: "crlEntryExtensions"
+ }
+ }, true)
+ ]
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for REV_CERT");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.userCertificate = asn1.result["userCertificate"];
+ this.revocationDate = new in_window.org.pkijs.simpl.TIME({ schema: asn1.result["revocationDate"] });
+
+ if("crlEntryExtensions" in asn1.result)
+ {
+ this.crlEntryExtensions = new Array();
+ var exts = asn1.result["crlEntryExtensions"].value_block.value;
+
+ for(var i = 0; i < exts.length; i++)
+ this.crlEntryExtensions.push(new in_window.org.pkijs.simpl.EXTENSION({ schema: exts[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.REV_CERT.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var sequence_array = new Array();
+ sequence_array.push(this.userCertificate);
+ sequence_array.push(this.revocationDate.toSchema());
+
+ if("crlEntryExtensions" in this)
+ {
+ var exts = new Array();
+
+ for(var i = 0; i < this.crlEntryExtensions.length; i++)
+ exts.push(this.crlEntryExtensions[i].toSchema());
+
+ sequence_array.push(new in_window.org.pkijs.asn1.SEQUENCE({ value: exts }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: sequence_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.REV_CERT.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ userCertificate: this.userCertificate.toJSON(),
+ revocationDate: this.revocationDate.toJSON
+ };
+
+ if("crlEntryExtensions" in this)
+ {
+ _object.crlEntryExtensions = new Array();
+
+ for(var i = 0; i < this.crlEntryExtensions.length; i++)
+ _object.crlEntryExtensions.push(this.crlEntryExtensions[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for X.509 CRL (Certificate Revocation List)(RFC5280)
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL =
+ function()
+ {
+ // #region Internal properties of the object
+ // #region Properties from CRL TBS part
+ this.tbs = new ArrayBuffer(0);
+
+ // OPTIONAL this.version = 1;
+ this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.issuer = new in_window.org.pkijs.simpl.RDN();
+ this.thisUpdate = new in_window.org.pkijs.simpl.TIME();
+ // OPTIONAL this.nextUpdate = new in_window.org.pkijs.simpl.TIME();
+ // OPTIONAL this.revokedCertificates = new Array(); // Array of REV_CERT objects
+ // OPTIONAL this.crlExtensions = new Array(); // Array of in_window.org.pkijs.simpl.EXTENSION();
+ // #endregion
+
+ // #region Properties from CRL major part
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.CRL.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ // #region Properties from CRL TBS part
+ this.tbs = arguments[0].tbs || new ArrayBuffer(0);
+
+ if("version" in arguments[0])
+ this.version = arguments[0].version;
+ this.signature = arguments[0].signature || new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.issuer = arguments[0].issuer || new in_window.org.pkijs.simpl.RDN();
+ this.thisUpdate = arguments[0].thisUpdate || new in_window.org.pkijs.simpl.TIME();
+ if("nextUpdate" in arguments[0])
+ this.nextUpdate = arguments[0].nextUpdate;
+ if("revokedCertificates" in arguments[0])
+ this.revokedCertificates = arguments[0].revokedCertificates;
+ if("crlExtensions" in arguments[0])
+ this.crlExtensions = arguments[0].crlExtensions;
+ // #endregion
+
+ // #region Properties from CRL major part
+ this.signatureAlgorithm = arguments[0].signatureAlgorithm || new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.signatureValue = arguments[0].signatureValue || new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.CRL()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CRL");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.tbs = asn1.result["tbsCertList"].value_before_decode;
+
+ if("tbsCertList.version" in asn1.result)
+ this.version = asn1.result["tbsCertList.version"].value_block.value_dec;
+ this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["tbsCertList.signature"] });
+ this.issuer = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["tbsCertList.issuer"] });
+ this.thisUpdate = new in_window.org.pkijs.simpl.TIME({ schema: asn1.result["tbsCertList.thisUpdate"] });
+ if("tbsCertList.nextUpdate" in asn1.result)
+ this.nextUpdate = new in_window.org.pkijs.simpl.TIME({ schema: asn1.result["tbsCertList.nextUpdate"] });
+ if("tbsCertList.revokedCertificates" in asn1.result)
+ {
+ this.revokedCertificates = new Array();
+
+ var rev_certs = asn1.result["tbsCertList.revokedCertificates"];
+ for(var i = 0; i < rev_certs.length; i++)
+ this.revokedCertificates.push(new in_window.org.pkijs.simpl.REV_CERT({ schema: rev_certs[i] }));
+ }
+ if("tbsCertList.extensions" in asn1.result)
+ {
+ this.crlExtensions = new Array();
+ var exts = asn1.result["tbsCertList.extensions"].value_block.value;
+
+ for(var i = 0; i < exts.length; i++)
+ this.crlExtensions.push(new in_window.org.pkijs.simpl.EXTENSION({ schema: exts[i] }));
+ }
+
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["signatureAlgorithm"] });
+ this.signatureValue = asn1.result["signatureValue"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.encodeTBS =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ if("version" in this)
+ output_array.push(new in_window.org.pkijs.asn1.INTEGER({ value: this.version }));
+
+ output_array.push(this.signature.toSchema());
+ output_array.push(this.issuer.toSchema());
+ output_array.push(this.thisUpdate.toSchema());
+
+ if("nextUpdate" in this)
+ output_array.push(this.nextUpdate.toSchema());
+
+ if("revokedCertificates" in this)
+ {
+ var rev_certificates = new Array();
+
+ for(var i = 0; i < this.revokedCertificates.length; i++)
+ rev_certificates.push(this.revokedCertificates[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.SEQUENCE({
+ value: rev_certificates
+ }));
+ }
+
+ if("crlExtensions" in this)
+ {
+ var extensions = new Array();
+
+ for(var j = 0; j < this.crlExtensions.length; j++)
+ extensions.push(this.crlExtensions[j].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: [
+ new in_window.org.pkijs.asn1.SEQUENCE({
+ value: extensions
+ })
+ ]
+ }));
+ }
+ // #endregion
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.toSchema =
+ function(encodeFlag)
+ {
+ /// <param name="encodeFlag" type="Boolean">If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.</param>
+
+ // #region Check "encodeFlag"
+ if(typeof encodeFlag === "undefined")
+ encodeFlag = false;
+ // #endregion
+
+ // #region Decode stored TBS value
+ var tbs_schema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored TBS part
+ return in_window.org.pkijs.schema.CRL();
+
+ tbs_schema = in_window.org.pkijs.fromBER(this.tbs).result;
+ }
+ // #endregion
+ // #region Create TBS schema via assembling from TBS parts
+ else
+ tbs_schema = in_window.org.pkijs.simpl.CRL.prototype.encodeTBS.call(this);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ tbs_schema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.verify =
+ function()
+ {
+ // #region Global variables
+ var sequence = Promise.resolve();
+
+ var signature = this.signatureValue;
+ var tbs = this.tbs;
+
+ var subjectPublicKeyInfo = -1;
+
+ var _this = this;
+ // #endregion
+
+ // #region Get information about CRL issuer certificate
+ if(arguments[0] instanceof Object)
+ {
+ if("issuerCertificate" in arguments[0]) // "issuerCertificate" must be of type "simpl.CERT"
+ {
+ subjectPublicKeyInfo = arguments[0].issuerCertificate.subjectPublicKeyInfo;
+
+ // The CRL issuer name and "issuerCertificate" subject name are not equal
+ if(this.issuer.isEqual(arguments[0].issuerCertificate.subject) == false)
+ return new Promise(function(resolve, reject) { resolve(false); });
+ }
+
+ // #region In case if there is only public key during verification
+ if("publicKeyInfo" in arguments[0])
+ subjectPublicKeyInfo = arguments[0].publicKeyInfo; // Must be of type "org.pkijs.simpl.PUBLIC_KEY_INFO"
+ // #endregion
+ }
+
+ if((subjectPublicKeyInfo instanceof in_window.org.pkijs.simpl.PUBLIC_KEY_INFO) === false)
+ return new Promise(function(resolve, reject) { reject("Issuer's certificate must be provided as an input parameter"); });
+ // #endregion
+
+ // #region Check the CRL for unknown critical extensions
+ if("crlExtensions" in this)
+ {
+ for(var i = 0; i < this.crlExtensions.length; i++)
+ {
+ if(this.crlExtensions[i].critical)
+ {
+ // We can not be sure that unknown extension has no value for CRL signature
+ if(("parsedValue" in this.crlExtensions[i]) == false)
+ return new Promise(function(resolve, reject) { resolve(false); });
+ }
+ }
+ }
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Find signer's hashing algorithm
+ var sha_algorithm = in_window.org.pkijs.getHashAlgorithm(this.signatureAlgorithm);
+ if(sha_algorithm === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+ // #endregion
+
+ // #region Import public key
+ sequence = sequence.then(
+ function()
+ {
+ // #region Get information about public key algorithm and default parameters for import
+ var algorithmObject = in_window.org.pkijs.getAlgorithmByOID(_this.signature.algorithm_id);
+ if(("name" in algorithmObject) === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported public key algorithm: " + _this.signature.algorithm_id); });
+
+ var algorithm_name = algorithmObject.name;
+
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(algorithm_name, "importkey");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ var publicKeyInfo_schema = subjectPublicKeyInfo.toSchema();
+ var publicKeyInfo_buffer = publicKeyInfo_schema.toBER(false);
+ var publicKeyInfo_view = new Uint8Array(publicKeyInfo_buffer);
+
+ return crypto.importKey("spki",
+ publicKeyInfo_view,
+ algorithm.algorithm,
+ true,
+ algorithm.usages);
+ }
+ );
+ // #endregion
+
+ // #region Verify signature for the certificate
+ sequence = sequence.then(
+ function(publicKey)
+ {
+ // #region Get default algorithm parameters for verification
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(publicKey.algorithm.name, "verify");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ // #region Special case for ECDSA signatures
+ var signature_value = signature.value_block.value_hex;
+
+ if(publicKey.algorithm.name === "ECDSA")
+ {
+ var asn1 = in_window.org.pkijs.fromBER(signature_value);
+ signature_value = in_window.org.pkijs.createECDSASignatureFromCMS(asn1.result);
+ }
+ // #endregion
+
+ // #region Special case for RSA-PSS
+ if(publicKey.algorithm.name === "RSA-PSS")
+ {
+ var pssParameters;
+
+ try
+ {
+ pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params({ schema: _this.signatureAlgorithm.algorithm_params });
+ }
+ catch(ex)
+ {
+ return new Promise(function(resolve, reject) { reject(ex); });
+ }
+
+ if("saltLength" in pssParameters)
+ algorithm.algorithm.saltLength = pssParameters.saltLength;
+ else
+ algorithm.algorithm.saltLength = 20;
+
+ var hash_algo = "SHA-1";
+
+ if("hashAlgorithm" in pssParameters)
+ {
+ var hashAlgorithm = in_window.org.pkijs.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithm_id);
+ if(("name" in hashAlgorithm) === false)
+ return new Promise(function(resolve, reject) { reject("Unrecognized hash algorithm: " + pssParameters.hashAlgorithm.algorithm_id); });
+
+ hash_algo = hashAlgorithm.name;
+ }
+
+ algorithm.algorithm.hash.name = hash_algo;
+ }
+ // #endregion
+
+ return crypto.verify(algorithm.algorithm,
+ publicKey,
+ new Uint8Array(signature_value),
+ new Uint8Array(tbs));
+ }
+ );
+ // #endregion
+
+ return sequence;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.sign =
+ function(privateKey, hashAlgorithm)
+ {
+ /// <param name="privateKey" type="Key">Private key for "subjectPublicKeyInfo" structure</param>
+ /// <param name="hashAlgorithm" type="String" optional="true">Hashing algorithm. Default SHA-1</param>
+
+ // #region Initial variables
+ var _this = this;
+ // #endregion
+
+ // #region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return new Promise(function(resolve, reject) { reject("Need to provide a private key for signing"); });
+ // #endregion
+
+ // #region Get hashing algorithm
+ if(typeof hashAlgorithm === "undefined")
+ hashAlgorithm = "SHA-1";
+ else
+ {
+ // #region Simple check for supported algorithm
+ var oid = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(oid === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+ // #endregion
+ }
+ // #endregion
+
+ // #region Get a "default parameters" for current algorithm
+ var defParams = in_window.org.pkijs.getAlgorithmParameters(privateKey.algorithm.name, "sign");
+ defParams.algorithm.hash.name = hashAlgorithm;
+ // #endregion
+
+ // #region Fill internal structures base on "privateKey" and "hashAlgorithm"
+ switch(privateKey.algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ case "ECDSA":
+ _this.signature.algorithm_id = in_window.org.pkijs.getOIDByAlgorithm(defParams.algorithm);
+ _this.signatureAlgorithm.algorithm_id = _this.signature.algorithm_id;
+ break;
+ case "RSA-PSS":
+ {
+ // #region Set "saltLength" as a length (in octets) of hash function result
+ switch(hashAlgorithm.toUpperCase())
+ {
+ case "SHA-256":
+ defParams.algorithm.saltLength = 32;
+ break;
+ case "SHA-384":
+ defParams.algorithm.saltLength = 48;
+ break;
+ case "SHA-512":
+ defParams.algorithm.saltLength = 64;
+ break;
+ default:
+ }
+ // #endregion
+
+ // #region Fill "RSASSA_PSS_params" object
+ var paramsObject = {};
+
+ if(hashAlgorithm.toUpperCase() !== "SHA-1")
+ {
+ var hashAlgorithmOID = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(hashAlgorithmOID === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+
+ paramsObject.hashAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: hashAlgorithmOID,
+ algorithm_params: new in_window.org.pkijs.asn1.NULL()
+ });
+
+ paramsObject.maskGenAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.8", // MGF1
+ algorithm_params: paramsObject.hashAlgorithm.toSchema()
+ })
+ }
+
+ if(defParams.algorithm.saltLength !== 20)
+ paramsObject.saltLength = defParams.algorithm.saltLength;
+
+ var pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params(paramsObject);
+ // #endregion
+
+ // #region Automatically set signature algorithm
+ _this.signature = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.10",
+ algorithm_params: pssParameters.toSchema()
+ });
+ _this.signatureAlgorithm = _this.signature; // Must be the same
+ // #endregion
+ }
+ break;
+ default:
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + privateKey.algorithm.name); });
+ }
+ // #endregion
+
+ // #region Create TBS data for signing
+ _this.tbs = in_window.org.pkijs.simpl.CRL.prototype.encodeTBS.call(this).toBER(false);
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Signing TBS data on provided private key
+ return crypto.sign(
+ defParams.algorithm,
+ privateKey,
+ new Uint8Array(_this.tbs)).
+ then(
+ function(result)
+ {
+ // #region Special case for ECDSA algorithm
+ if(defParams.algorithm.name === "ECDSA")
+ result = in_window.org.pkijs.createCMSECDSASignature(result);
+ // #endregion
+
+ _this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING({ value_hex: result });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject) { reject("Signing error: " + error); });
+ }
+ );
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.isCertificateRevoked =
+ function()
+ {
+ // #region Get input certificate
+ var certificate = {};
+
+ if(arguments[0] instanceof Object)
+ {
+ if("certificate" in arguments[0])
+ certificate = arguments[0].certificate;
+ }
+
+ if((certificate instanceof in_window.org.pkijs.simpl.CERT) === false)
+ return false;
+ // #endregion
+
+ // #region Check that issuer of the input certificate is the same with issuer of this CRL
+ if(this.issuer.isEqual(certificate.issuer) === false)
+ return false;
+ // #endregion
+
+ // #region Check that there are revoked certificates in this CRL
+ if(("revokedCertificates" in this) === false)
+ return false;
+ // #endregion
+
+ // #region Search for input certificate in revoked certificates array
+ for(var i = 0; i < this.revokedCertificates.length; i++)
+ {
+ if(this.revokedCertificates[i].userCertificate.isEqual(certificate.serialNumber))
+ return true;
+ }
+ // #endregion
+
+ return false;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CRL.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ tbs: in_window.org.pkijs.bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ signature: this.signature.toJSON(),
+ issuer: this.issuer.toJSON(),
+ thisUpdate: this.thisUpdate.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if("version" in this)
+ _object.version = this.version;
+
+ if("nextUpdate" in this)
+ _object.nextUpdate = this.nextUpdate.toJSON();
+
+ if("revokedCertificates" in this)
+ {
+ _object.revokedCertificates = new Array();
+
+ for(var i = 0; i < this.revokedCertificates.length; i++)
+ _object.revokedCertificates.push(this.revokedCertificates[i].toJSON());
+ }
+
+ if("crlExtensions" in this)
+ {
+ _object.crlExtensions = new Array();
+
+ for(var i = 0; i < this.crlExtensions.length; i++)
+ _object.crlExtensions.push(this.crlExtensions[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for "Attribute" type
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTRIBUTE =
+ function()
+ {
+ // #region Internal properties of the object
+ this.type = "";
+ this.values = new Array();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.ATTRIBUTE.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.type = arguments[0].type || "";
+ this.values = arguments[0].values || new Array();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTRIBUTE.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.ATTRIBUTE({
+ names: {
+ type: "type",
+ values: "values"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ATTRIBUTE");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.type = asn1.result["type"].value_block.toString();
+ this.values = asn1.result["values"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTRIBUTE.prototype.toSchema =
+ function()
+ {
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ new in_window.org.pkijs.asn1.OID({ value: this.type }),
+ new in_window.org.pkijs.asn1.SET({
+ value: this.values
+ })
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.ATTRIBUTE.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ type: this.type,
+ values: new Array()
+ };
+
+ for(var i = 0; i < this.values.length; i++)
+ _object.values.push(this.values[i].toJSON());
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for PKCS#10 certificate request
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10 =
+ function()
+ {
+ // #region Internal properties of the object
+ this.tbs = new ArrayBuffer(0);
+
+ this.version = 0;
+ this.subject = new in_window.org.pkijs.simpl.RDN();
+ this.subjectPublicKeyInfo = new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO();
+ // OPTIONAL this.attributes = new Array(); // Array of simpl.ATTRIBUTE objects
+
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate major part
+ this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING();
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.PKCS10.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.tbs = arguments[0].tbs || new ArrayBuffer(0);
+
+ this.version = arguments[0].version || 0;
+ this.subject = arguments[0].subject || new in_window.org.pkijs.simpl.RDN();
+ this.subjectPublicKeyInfo = arguments[0].subjectPublicKeyInfo || new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO();
+
+ if("attributes" in arguments[0])
+ this.attributes = arguments[0].attributes;
+
+ this.signatureAlgorithm = arguments[0].signatureAlgorithm || new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER(); // Signature algorithm from certificate major part
+ this.signatureValue = arguments[0].signatureValue || new in_window.org.pkijs.asn1.BITSTRING();
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.PKCS10()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PKCS10");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.tbs = asn1.result["CertificationRequestInfo"].value_before_decode;
+
+ this.version = asn1.result["CertificationRequestInfo.version"].value_block.value_dec;
+ this.subject = new in_window.org.pkijs.simpl.RDN({ schema: asn1.result["CertificationRequestInfo.subject"] });
+ this.subjectPublicKeyInfo = new in_window.org.pkijs.simpl.PUBLIC_KEY_INFO({ schema: asn1.result["CertificationRequestInfo.subjectPublicKeyInfo"] });
+ if("CertificationRequestInfo.attributes" in asn1.result)
+ {
+ this.attributes = new Array();
+
+ var attrs = asn1.result["CertificationRequestInfo.attributes"];
+ for(var i = 0; i < attrs.length; i++)
+ this.attributes.push(new in_window.org.pkijs.simpl.ATTRIBUTE({ schema: attrs[i] }));
+ }
+
+ this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["signatureAlgorithm"] });
+ this.signatureValue = asn1.result["signatureValue"];
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.encodeTBS =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.INTEGER({ value: this.version }));
+ output_array.push(this.subject.toSchema());
+ output_array.push(this.subjectPublicKeyInfo.toSchema());
+
+ if("attributes" in this)
+ {
+ var attributes = new Array();
+
+ for(var i = 0; i < this.attributes.length; i++)
+ attributes.push(this.attributes[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: attributes
+ }));
+ }
+ // #endregion
+
+ return (new in_window.org.pkijs.asn1.SEQUENCE({ value: output_array }));
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.toSchema =
+ function(encodeFlag)
+ {
+ /// <param name="encodeFlag" type="Boolean">If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.</param>
+
+ // #region Check "encodeFlag"
+ if(typeof encodeFlag === "undefined")
+ encodeFlag = false;
+ // #endregion
+
+ // #region Decode stored TBS value
+ var tbs_schema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored TBS part
+ return in_window.org.pkijs.schema.PKCS10();
+
+ tbs_schema = in_window.org.pkijs.fromBER(this.tbs).result;
+ }
+ // #endregion
+ // #region Create TBS schema via assembling from TBS parts
+ else
+ tbs_schema = in_window.org.pkijs.simpl.PKCS10.prototype.encodeTBS.call(this);
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: [
+ tbs_schema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.verify =
+ function()
+ {
+ /// <summary>!!! Works well in Chrome dev versions only (April 2014th) !!!</summary>
+ /// <returns type="Promise">Returns a new Promise object (in case of error), or a result of "crypto.subtle.veryfy" function</returns>
+
+ // #region Global variables
+ var _this = this;
+ var sha_algorithm = "";
+
+ var sequence = Promise.resolve();
+
+ var subjectPublicKeyInfo = this.subjectPublicKeyInfo;
+ var signature = this.signatureValue;
+ var tbs = this.tbs;
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Find a correct hashing algorithm
+ sha_algorithm = in_window.org.pkijs.getHashAlgorithm(this.signatureAlgorithm);
+ if(sha_algorithm === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+ // #endregion
+
+ // #region Importing public key
+ sequence = sequence.then(
+ function()
+ {
+ // #region Get information about public key algorithm and default parameters for import
+ var algorithmObject = in_window.org.pkijs.getAlgorithmByOID(_this.signatureAlgorithm.algorithm_id);
+ if(("name" in algorithmObject) === false)
+ return new Promise(function(resolve, reject) { reject("Unsupported public key algorithm: " + _this.signatureAlgorithm.algorithm_id); });
+
+ var algorithm_name = algorithmObject.name;
+
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(algorithm_name, "importkey");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ var publicKeyInfo_schema = subjectPublicKeyInfo.toSchema();
+ var publicKeyInfo_buffer = publicKeyInfo_schema.toBER(false);
+ var publicKeyInfo_view = new Uint8Array(publicKeyInfo_buffer);
+
+ return crypto.importKey("spki", publicKeyInfo_view, algorithm.algorithm, true, algorithm.usages);
+ }
+ );
+ // #endregion
+
+ // #region Verify signature
+ sequence = sequence.then(
+ function(publicKey)
+ {
+ // #region Get default algorithm parameters for verification
+ var algorithm = in_window.org.pkijs.getAlgorithmParameters(publicKey.algorithm.name, "verify");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = sha_algorithm;
+ // #endregion
+
+ // #region Special case for ECDSA signatures
+ var signature_value = signature.value_block.value_hex;
+
+ if(publicKey.algorithm.name === "ECDSA")
+ {
+ var asn1 = in_window.org.pkijs.fromBER(signature_value);
+ signature_value = in_window.org.pkijs.createECDSASignatureFromCMS(asn1.result);
+ }
+ // #endregion
+
+ // #region Special case for RSA-PSS
+ if(publicKey.algorithm.name === "RSA-PSS")
+ {
+ var pssParameters;
+
+ try
+ {
+ pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params({ schema: _this.signatureAlgorithm.algorithm_params });
+ }
+ catch(ex)
+ {
+ return new Promise(function(resolve, reject) { reject(ex); });
+ }
+
+ if("saltLength" in pssParameters)
+ algorithm.algorithm.saltLength = pssParameters.saltLength;
+ else
+ algorithm.algorithm.saltLength = 20;
+
+ var hash_algo = "SHA-1";
+
+ if("hashAlgorithm" in pssParameters)
+ {
+ var hashAlgorithm = in_window.org.pkijs.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithm_id);
+ if(("name" in hashAlgorithm) === false)
+ return new Promise(function(resolve, reject) { reject("Unrecognized hash algorithm: " + pssParameters.hashAlgorithm.algorithm_id); });
+
+ hash_algo = hashAlgorithm.name;
+ }
+
+ algorithm.algorithm.hash.name = hash_algo;
+ }
+ // #endregion
+
+ return crypto.verify(algorithm.algorithm,
+ publicKey,
+ new Uint8Array(signature_value),
+ new Uint8Array(tbs));
+ }
+ );
+ // #endregion
+
+ return sequence;
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.sign =
+ function(privateKey, hashAlgorithm)
+ {
+ /// <param name="privateKey" type="Key">Private key for "subjectPublicKeyInfo" structure</param>
+ /// <param name="hashAlgorithm" type="String" optional="true">Hashing algorithm. Default SHA-1</param>
+
+ // #region Initial variables
+ var _this = this;
+ // #endregion
+
+ // #region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return new Promise(function(resolve, reject) { reject("Need to provide a private key for signing"); });
+ // #endregion
+
+ // #region Get hashing algorithm
+ if(typeof hashAlgorithm === "undefined")
+ hashAlgorithm = "SHA-1";
+ else
+ {
+ // #region Simple check for supported algorithm
+ var oid = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(oid === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+ // #endregion
+ }
+ // #endregion
+
+ // #region Get a "default parameters" for current algorithm
+ var defParams = in_window.org.pkijs.getAlgorithmParameters(privateKey.algorithm.name, "sign");
+ defParams.algorithm.hash.name = hashAlgorithm;
+ // #endregion
+
+ // #region Fill internal structures base on "privateKey" and "hashAlgorithm"
+ switch(privateKey.algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ case "ECDSA":
+ _this.signatureAlgorithm.algorithm_id = in_window.org.pkijs.getOIDByAlgorithm(defParams.algorithm);
+ break;
+ case "RSA-PSS":
+ {
+ // #region Set "saltLength" as a length (in octets) of hash function result
+ switch(hashAlgorithm.toUpperCase())
+ {
+ case "SHA-256":
+ defParams.algorithm.saltLength = 32;
+ break;
+ case "SHA-384":
+ defParams.algorithm.saltLength = 48;
+ break;
+ case "SHA-512":
+ defParams.algorithm.saltLength = 64;
+ break;
+ default:
+ }
+ // #endregion
+
+ // #region Fill "RSASSA_PSS_params" object
+ var paramsObject = {};
+
+ if(hashAlgorithm.toUpperCase() !== "SHA-1")
+ {
+ var hashAlgorithmOID = in_window.org.pkijs.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(hashAlgorithmOID === "")
+ return new Promise(function(resolve, reject) { reject("Unsupported hash algorithm: " + hashAlgorithm); });
+
+ paramsObject.hashAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: hashAlgorithmOID,
+ algorithm_params: new in_window.org.pkijs.asn1.NULL()
+ });
+
+ paramsObject.maskGenAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.8", // MGF1
+ algorithm_params: paramsObject.hashAlgorithm.toSchema()
+ })
+ }
+
+ if(defParams.algorithm.saltLength !== 20)
+ paramsObject.saltLength = defParams.algorithm.saltLength;
+
+ var pssParameters = new in_window.org.pkijs.simpl.x509.RSASSA_PSS_params(paramsObject);
+ // #endregion
+
+ // #region Automatically set signature algorithm
+ _this.signatureAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({
+ algorithm_id: "1.2.840.113549.1.1.10",
+ algorithm_params: pssParameters.toSchema()
+ });
+ // #endregion
+ }
+ break;
+ default:
+ return new Promise(function(resolve, reject) { reject("Unsupported signature algorithm: " + privateKey.algorithm.name); });
+ }
+ // #endregion
+
+ // #region Create TBS data for signing
+ _this.tbs = in_window.org.pkijs.simpl.PKCS10.prototype.encodeTBS.call(this).toBER(false);
+ // #endregion
+
+ // #region Get a "crypto" extension
+ var crypto = in_window.org.pkijs.getCrypto();
+ if(typeof crypto == "undefined")
+ return new Promise(function(resolve, reject) { reject("Unable to create WebCrypto object"); });
+ // #endregion
+
+ // #region Signing TBS data on provided private key
+ return crypto.sign(defParams.algorithm,
+ privateKey,
+ new Uint8Array(_this.tbs)).then(
+ function(result)
+ {
+ // #region Special case for ECDSA algorithm
+ if(defParams.algorithm.name === "ECDSA")
+ result = in_window.org.pkijs.createCMSECDSASignature(result);
+ // #endregion
+
+ _this.signatureValue = new in_window.org.pkijs.asn1.BITSTRING({ value_hex: result });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject) { reject("Signing error: " + error); });
+ }
+ );
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS10.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ tbs: in_window.org.pkijs.bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ version: this.version,
+ subject: this.subject.toJSON(),
+ subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if("attributes" in this)
+ {
+ _object.attributes = new Array();
+
+ for(var i = 0; i < this.attributes.length; i++)
+ _object.attributes.push(this.attributes[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for PKCS#8 private key bag
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS8 =
+ function()
+ {
+ // #region Internal properties of the object
+ this.version = 0;
+ this.privateKeyAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.privateKey = new in_window.org.pkijs.asn1.OCTETSTRING();
+ // OPTIONAL this.attributes // Array of "in_window.org.pkijs.simpl.ATTRIBUTE"
+ // #endregion
+
+ // #region If input argument array contains "schema" for this object
+ if((arguments[0] instanceof Object) && ("schema" in arguments[0]))
+ in_window.org.pkijs.simpl.PKCS8.prototype.fromSchema.call(this, arguments[0].schema);
+ // #endregion
+ // #region If input argument array contains "native" values for internal properties
+ else
+ {
+ if(arguments[0] instanceof Object)
+ {
+ this.version = arguments[0].version || 0;
+ this.privateKeyAlgorithm = arguments[0].privateKeyAlgorithm || new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER();
+ this.privateKey = arguments[0].privateKey || new in_window.org.pkijs.asn1.OCTETSTRING();
+
+ if("attributes" in arguments[0])
+ this.attributes = arguments[0].attributes;
+ }
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS8.prototype.fromSchema =
+ function(schema)
+ {
+ // #region Check the schema is valid
+ var asn1 = in_window.org.pkijs.compareSchema(schema,
+ schema,
+ in_window.org.pkijs.schema.PKCS8({
+ names: {
+ version: "version",
+ privateKeyAlgorithm: {
+ names: {
+ block_name: "privateKeyAlgorithm"
+ }
+ },
+ privateKey: "privateKey",
+ attributes: "attributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PKCS8");
+ // #endregion
+
+ // #region Get internal properties from parsed schema
+ this.version = asn1.result["version"].value_block.value_dec;
+ this.privateKeyAlgorithm = new in_window.org.pkijs.simpl.ALGORITHM_IDENTIFIER({ schema: asn1.result["privateKeyAlgorithm"] });
+ this.privateKey = asn1.result["privateKey"];
+
+ if("attributes" in asn1.result)
+ {
+ this.attributes = new Array();
+ var attrs = asn1.result["attributes"];
+
+ for(var i = 0; i < attrs.length; i++)
+ this.attributes.push(new in_window.org.pkijs.simpl.ATTRIBUTE({ schema: attrs[i] }));
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS8.prototype.toSchema =
+ function()
+ {
+ // #region Create array for output sequence
+ var output_array = new Array();
+
+ output_array.push(new in_window.org.pkijs.asn1.INTEGER({ value: this.version }));
+ output_array.push(this.privateKeyAlgorithm.toSchema());
+ output_array.push(this.privateKey);
+
+ if("attributes" in this)
+ {
+ var attrs = new Array();
+
+ for(var i = 0; i < this.attributes.length; i++)
+ attrs.push(this.attributes[i].toSchema());
+
+ output_array.push(new in_window.org.pkijs.asn1.ASN1_CONSTRUCTED({
+ optional: true,
+ id_block: {
+ tag_class: 3, // CONTEXT-SPECIFIC
+ tag_number: 0 // [0]
+ },
+ value: attrs
+ }));
+ }
+ // #endregion
+
+ // #region Construct and return new ASN.1 schema for this object
+ return (new in_window.org.pkijs.asn1.SEQUENCE({
+ value: output_array
+ }));
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.PKCS8.prototype.toJSON =
+ function()
+ {
+ var _object = {
+ version: this.version,
+ privateKeyAlgorithm: this.privateKeyAlgorithm.toJSON(),
+ privateKey: this.privateKey.toJSON()
+ };
+
+ if("attributes" in this)
+ {
+ _object.attributes = new Array();
+
+ for(var i = 0; i < this.attributes.length; i++)
+ _object.attributes.push(this.attributes[i].toJSON());
+ }
+
+ return _object;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+ // #region Simplified structure for working with X.509 certificate chains
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT_CHAIN =
+ function()
+ {
+ // #region Internal properties of the object
+ /// <field name="trusted_certs" type="Array" elementType="in_window.org.pkijs.simpl.CERT">Array of pre-defined trusted (by user) certificates</field>
+ this.trusted_certs = new Array();
+ /// <field name="certs" type="Array" elementType="in_window.org.pkijs.simpl.CERT">Array with certificate chain. Could be only one end-user certificate in there!</field>
+ this.certs = new Array();
+ /// <field name="crls" type="Array" elementType="in_window.org.pkijs.simpl.CRL">Array of all CRLs for all certificates from certificate chain</field>
+ this.crls = new Array();
+ // #endregion
+
+ // #region Initialize internal properties by input values
+ if(arguments[0] instanceof Object)
+ {
+ this.trusted_certs = arguments[0].trusted_certs || new Array();
+ this.certs = arguments[0].certs || new Array();
+ this.crls = arguments[0].crls || new Array();
+ }
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT_CHAIN.prototype.sort =
+ function()
+ {
+ // #region Initial variables
+ /// <var type="Array" elementType="in_window.org.pkijs.simpl.CERT">Array of sorted certificates</var>
+ var sorted_certs = new Array();
+
+ /// <var type="Array" elementType="in_window.org.pkijs.simpl.CERT">Initial array of certificates</var>
+ var certs = this.certs.slice(0); // Explicity copy "this.certs"
+
+ /// <var type="Date">Date for checking certificate validity period</var>
+ var check_date = new Date();
+
+ var _this = this;
+ // #endregion
+
+ // #region Initial checks
+ if(certs.length === 0)
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 2,
+ result_message: "Certificate's array can not be empty"
+ });
+ });
+ // #endregion
+
+ // #region Find end-user certificate
+ var end_user_index = -1;
+
+ for(var i = 0; i < certs.length; i++)
+ {
+ var isCA = false;
+
+ if("extensions" in certs[i])
+ {
+ var mustBeCA = false;
+ var keyUsagePresent = false;
+ var cRLSign = false;
+
+ for(var j = 0; j < certs[i].extensions.length; j++)
+ {
+ if((certs[i].extensions[j].critical === true) &&
+ (("parsedValue" in certs[i].extensions[j]) === false))
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 6,
+ result_message: "Unable to parse critical certificate extension: " + certs[i].extensions[j].extnID
+ });
+ });
+ }
+
+ if(certs[i].extensions[j].extnID === "2.5.29.15") // KeyUsage
+ {
+ keyUsagePresent = true;
+
+ var view = new Uint8Array(certs[i].extensions[j].parsedValue.value_block.value_hex);
+
+ if((view[0] & 0x04) === 0x04) // Set flag "keyCertSign"
+ mustBeCA = true;
+
+ if((view[0] & 0x02) === 0x02) // Set flag "cRLSign"
+ cRLSign = true;
+ }
+
+ if(certs[i].extensions[j].extnID === "2.5.29.19") // BasicConstraints
+ {
+ if("cA" in certs[i].extensions[j].parsedValue)
+ {
+ if(certs[i].extensions[j].parsedValue.cA === true)
+ isCA = true;
+ }
+ }
+ }
+
+ if((mustBeCA === true) && (isCA === false))
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 3,
+ result_message: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstaints"
+ });
+ });
+
+ if((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false))
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 4,
+ result_message: "Unable to build certificate chain - \"keyCertSign\" flag was not set"
+ });
+ });
+
+ if((isCA === true) && (keyUsagePresent === true) && (cRLSign === false))
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 5,
+ result_message: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag"
+ });
+ });
+ }
+
+ if(isCA === false)
+ {
+ if(sorted_certs.length !== 0)
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 7,
+ result_message: "Unable to build certificate chain - more than one possible end-user certificate"
+ });
+ });
+
+ sorted_certs.push(certs[i]);
+ end_user_index = i;
+ }
+ }
+
+ certs.splice(end_user_index, 1);
+ // #endregion
+
+ // #region Check that end-user certificate was found
+ if(sorted_certs.length === 0)
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 1,
+ result_message: "Can't find end-user certificate"
+ });
+ });
+ // #endregion
+
+ // #region Return if there is only one certificate in certificate's array
+ if(certs.length === 0)
+ {
+ if(sorted_certs[0].issuer.isEqual(sorted_certs[0].subject) === true)
+ return new Promise(function(resolve, reject) { resolve(sorted_certs); });
+ else
+ {
+ if(this.trusted_certs.length === 0)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 70,
+ result_message: "Can't find root certificate"
+ });
+ });
+ }
+ else
+ {
+ certs = _this.trusted_certs.splice(0);
+ }
+ }
+
+ }
+ // #endregion
+
+ /// <var type="in_window.org.pkijs.simpl.CERT">Current certificate (to find issuer for)</var>
+ var current_certificate = sorted_certs[0];
+
+ // #region Auxiliary functions working with Promises
+ function basic(subject_certificate, issuer_certificate)
+ {
+ /// <summary>Basic certificate checks</summary>
+ /// <param name="subject_certificate" type="in_window.org.pkijs.simpl.CERT">Certificate for testing (subject)</param>
+ /// <param name="issuer_certificate" type="in_window.org.pkijs.simpl.CERT">Certificate for issuer of subject certificate</param>
+
+ // #region Initial variables
+ var sequence = Promise.resolve();
+ // #endregion
+
+ // #region Check validity period for subject certificate
+ sequence = sequence.then(
+ function()
+ {
+ if((subject_certificate.notBefore.value > check_date) ||
+ (subject_certificate.notAfter.value < check_date))
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 8,
+ result_message: "Certificate validity period is out of checking date"
+ });
+ });
+ }
+ }
+ );
+ // #endregion
+
+ // #region Give ability to not provide CRLs (all certificates assume to be valid)
+ if(_this.crls.length === 0)
+ return sequence.then(
+ function()
+ {
+ return new Promise(function(resolve, reject) { resolve(); });
+ }
+ );
+ // #endregion
+
+ // #region Find correct CRL for "issuer_certificate"
+ function find_crl(index)
+ {
+ return _this.crls[index].verify({ issuerCertificate: issuer_certificate }).then(
+ function(result)
+ {
+ if(result === true)
+ return new Promise(function(resolve, reject) { resolve(_this.crls[index]); });
+ else
+ {
+ index++;
+
+ if(index < _this.crls.length)
+ return find_crl(index);
+ else
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 9,
+ result_message: "Unable to find CRL for issuer's certificate"
+ });
+ });
+ }
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 10,
+ result_message: "Unable to find CRL for issuer's certificate"
+ });
+ });
+ }
+ );
+ }
+
+ sequence = sequence.then(
+ function()
+ {
+ return find_crl(0);
+ }
+ );
+ // #endregion
+
+ // #region Check that subject certificate is not in the CRL
+ sequence = sequence.then(
+ function(crl)
+ {
+ /// <param name="crl" type="in_window.org.pkijs.simpl.CRL">CRL for issuer's certificate</param>
+
+ if(crl.isCertificateRevoked({ certificate: subject_certificate }) === true)
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 11,
+ result_message: "Subject certificate was revoked"
+ });
+ });
+ else
+ return new Promise(function(resolve, reject) { resolve(); });
+ },
+ function(error)
+ {
+ /// <summary>Not for all certificates we have a CRL. So, this "stub" is for handling such situation - assiming we have a valid, non-revoked certificate</summary>
+ return new Promise(function(resolve, reject) { resolve(); });
+ }
+ );
+ // #endregion
+
+ return sequence;
+ }
+
+ function outer()
+ {
+ return inner(current_certificate, 0).then(
+ function(index)
+ {
+ sorted_certs.push(certs[index]);
+ current_certificate = certs[index];
+
+ certs.splice(index, 1);
+
+ if(current_certificate.issuer.isEqual(current_certificate.subject) === true)
+ {
+ // #region Check that the "self-signed" certificate there is in "trusted_certs" array
+ var found = (_this.trusted_certs.length === 0); // If user did not set "trusted_certs" then we have an option to trust any self-signed certificate as root
+
+ for(var i = 0; i < _this.trusted_certs.length; i++)
+ {
+ if((current_certificate.issuer.isEqual(_this.trusted_certs[i].issuer) === true) &&
+ (current_certificate.subject.isEqual(_this.trusted_certs[i].subject) === true) &&
+ (current_certificate.serialNumber.isEqual(_this.trusted_certs[i].serialNumber) === true))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if(found === false)
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 22,
+ result_message: "Self-signed root certificate not in \"trusted certificates\" array"
+ });
+ });
+ // #endregion
+
+ return (current_certificate.verify()).then( // Verifing last, self-signed certificate
+ function(result)
+ {
+ if(result === true)
+ return basic(current_certificate, current_certificate).then(
+ function()
+ {
+ return new Promise(function(resolve, reject) { resolve(sorted_certs); });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 12,
+ result_message: error
+ });
+ });
+ }
+ );
+ else
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 13,
+ result_message: "Unable to build certificate chain - signature of root certificate is invalid"
+ });
+ });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 14,
+ result_message: error
+ });
+ });
+ }
+ );
+ }
+ else // In case if self-signed cert for the chain in the "trusted_certs" array
+ {
+ if(certs.length > 0)
+ return outer();
+ else
+ {
+ if(_this.trusted_certs.length !== 0)
+ {
+ certs = _this.trusted_certs.splice(0);
+ return outer();
+ }
+ else
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 23,
+ result_message: "Root certificate not found"
+ });
+ });
+ }
+ }
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject(error);
+ });
+ }
+ );
+ }
+
+ function inner(current_certificate, index)
+ {
+ if(certs[index].subject.isEqual(current_certificate.issuer) === true)
+ {
+ return current_certificate.verify({ issuerCertificate: certs[index] }).then(
+ function(result)
+ {
+ if(result === true)
+ {
+ return basic(current_certificate, certs[index]).then(
+ function()
+ {
+ return new Promise(function(resolve, reject) { resolve(index); });
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 16,
+ result_message: error
+ });
+ });
+ }
+ );
+ }
+ else
+ {
+ if(index < (certs.length - 1))
+ return inner(current_certificate, index + 1);
+ else
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 17,
+ result_message: "Unable to build certificate chain - incomplete certificate chain or signature of some certificate is invalid"
+ });
+ });
+ }
+ },
+ function(error)
+ {
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 18,
+ result_message: "Unable to build certificate chain - error during certificate signature verification"
+ });
+ });
+ }
+ );
+ }
+ else
+ {
+ if(index < (certs.length - 1))
+ return inner(current_certificate, index + 1);
+ else
+ return new Promise(function(resolve, reject)
+ {
+ reject({
+ result: false,
+ result_code: 19,
+ result_message: "Unable to build certificate chain - incomplete certificate chain"
+ });
+ });
+ }
+ }
+ // #endregion
+
+ // #region Find certificates for all issuers
+ return outer();
+ // #endregion
+ };
+ //**************************************************************************************
+ in_window.org.pkijs.simpl.CERT_CHAIN.prototype.verify =
+ function()
+ {
+ // #region Initial checks
+ if(this.certs.length === 0)
+ return new Promise(function(resolve, reject) { reject("Empty certificate array"); });
+ // #endregion
+
+ // #region Initial variables
+ var sequence = Promise.resolve();
+
+ var _this = this;
+ // #endregion
+
+ // #region Get input variables
+ var initial_policy_set = new Array();
+ initial_policy_set.push("2.5.29.32.0"); // "anyPolicy"
+
+ var initial_explicit_policy = false;
+ var initial_policy_mapping_inhibit = false;
+ var initial_inhibit_policy = false;
+
+ var initial_permitted_subtrees_set = new Array(); // Array of "simpl.x509.GeneralSubtree"
+ var initial_excluded_subtrees_set = new Array(); // Array of "simpl.x509.GeneralSubtree"
+ var initial_required_name_forms = new Array(); // Array of "simpl.x509.GeneralSubtree"
+
+ var verification_time = new Date();
+
+ if(arguments[0] instanceof Object)
+ {
+ if("initial_policy_set" in arguments[0])
+ initial_policy_set = arguments[0].initial_policy_set;
+
+ if("initial_explicit_policy" in arguments[0])
+ initial_explicit_policy = arguments[0].initial_explicit_policy;
+
+ if("initial_policy_mapping_inhibit" in arguments[0])
+ initial_policy_mapping_inhibit = arguments[0].initial_policy_mapping_inhibit;
+
+ if("initial_inhibit_policy" in arguments[0])
+ initial_inhibit_policy = arguments[0].initial_inhibit_policy;
+
+ if("initial_permitted_subtrees_set" in arguments[0])
+ initial_permitted_subtrees_set = arguments[0].initial_permitted_subtrees_set;
+
+ if("initial_excluded_subtrees_set" in arguments[0])
+ initial_excluded_subtrees_set = arguments[0].initial_excluded_subtrees_set;
+
+ if("initial_required_name_forms" in arguments[0])
+ initial_required_name_forms = arguments[0].initial_required_name_forms;
+ }
+
+ var explicit_policy_indicator = initial_explicit_policy;
+ var policy_mapping_inhibit_indicator = initial_policy_mapping_inhibit;
+ var inhibit_any_policy_indicator = initial_inhibit_policy;
+
+ var pending_constraints = new Array(3);
+ pending_constraints[0] = false; // For "explicit_policy_pending"
+ pending_constraints[1] = false; // For "policy_mapping_inhibit_pending"
+ pending_constraints[2] = false; // For "inhibit_any_policy_pending"
+
+ var explicit_policy_pending = 0;
+ var policy_mapping_inhibit_pending = 0;
+ var inhibit_any_policy_pending = 0;
+
+ var permitted_subtrees = initial_permitted_subtrees_set;
+ var excluded_subtrees = initial_excluded_subtrees_set;
+ var required_name_forms = initial_required_name_forms;
+
+ var path_depth = 1;
+ // #endregion
+
+ // #region Sorting certificates in the chain array
+ sequence = (in_window.org.pkijs.simpl.CERT_CHAIN.prototype.sort.call(this)).then(
+ function(sorted_certs)
+ {
+ _this.certs = sorted_certs;
+ }
+ );
+ // #endregion
+
+ // #region Work with policies
+ sequence = sequence.then(
+ function()
+ {
+ // #region Support variables
+ var all_policies = new Array(); // Array of all policies (string values)
+ all_policies.push("2.5.29.32.0"); // Put "anyPolicy" at first place
+
+ var policies_and_certs = new Array(); // In fact "array of array" where rows are for each specific policy, column for each certificate and value is "true/false"
+
+ var any_policy_array = new Array(_this.certs.length - 1); // Minus "trusted anchor"
+ for(var ii = 0; ii < (_this.certs.length - 1) ; ii++)
+ any_policy_array[ii] = true;
+
+ policies_and_certs.push(any_policy_array);
+
+ var policy_mappings = new Array(_this.certs.length - 1); // Array of "PolicyMappings" for each certificate
+ var cert_policies = new Array(_this.certs.length - 1); // Array of "CertificatePolicies" for each certificate
+ // #endregion
+
+ for(var i = (_this.certs.length - 2) ; i >= 0 ; i--, path_depth++)
+ {
+ if("extensions" in _this.certs[i])
+ {
+ for(var j = 0; j < _this.certs[i].extensions.length; j++)
+ {
+ // #region CertificatePolicies
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.32")
+ {
+ cert_policies[i] = _this.certs[i].extensions[j].parsedValue;
+
+ for(var k = 0; k < _this.certs[i].extensions[j].parsedValue.certificatePolicies.length; k++)
+ {
+ var policy_index = (-1);
+
+ // #region Try to find extension in "all_policies" array
+ for(var s = 0; s < all_policies.length; s++)
+ {
+ if(_this.certs[i].extensions[j].parsedValue.certificatePolicies[k].policyIdentifier === all_policies[s])
+ {
+ policy_index = s;
+ break;
+ }
+ }
+ // #endregion
+
+ if(policy_index === (-1))
+ {
+ all_policies.push(_this.certs[i].extensions[j].parsedValue.certificatePolicies[k].policyIdentifier);
+
+ var cert_array = new Array(_this.certs.length - 1);
+ cert_array[i] = true;
+
+ policies_and_certs.push(cert_array);
+ }
+ else(policies_and_certs[policy_index])[i] = true;
+ }
+ }
+ // #endregion
+
+ // #region PolicyMappings
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.33")
+ policy_mappings[i] = _this.certs[i].extensions[j].parsedValue;
+ // #endregion
+
+ // #region PolicyConstraints
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.36")
+ {
+ if(explicit_policy_indicator == false)
+ {
+ // #region requireExplicitPolicy
+ if(_this.certs[i].extensions[j].parsedValue.requireExplicitPolicy === 0)
+ explicit_policy_indicator = true;
+ else
+ {
+ if(pending_constraints[0] === false)
+ {
+ pending_constraints[0] = true;
+ explicit_policy_pending = _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy;
+ }
+ else
+ {
+ explicit_policy_pending = (explicit_policy_pending > _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy) ? _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy : explicit_policy_pending;
+ }
+ }
+ // #endregion
+
+ // #region inhibitPolicyMapping
+ if(_this.certs[i].extensions[j].parsedValue.inhibitPolicyMapping === 0)
+ policy_mapping_inhibit_indicator = true;
+ else
+ {
+ if(pending_constraints[1] === false)
+ {
+ pending_constraints[1] = true;
+ policy_mapping_inhibit_pending = _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy;
+ }
+ else
+ {
+ policy_mapping_inhibit_pending = (policy_mapping_inhibit_pending > _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy) ? _this.certs[i].extensions[j].parsedValue.requireExplicitPolicy : policy_mapping_inhibit_pending;
+ }
+ }
+ // #endregion
+ }
+ }
+ // #endregion
+
+ // #region InhibitAnyPolicy
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.54")
+ {
+ if(inhibit_any_policy_indicator === false)
+ {
+ if(_this.certs[i].extensions[j].parsedValue.value_block.value_dec === 0)
+ inhibit_any_policy_indicator = true;
+ else
+ {
+ if(pending_constraints[2] === false)
+ {
+ pending_constraints[2] = true;
+ inhibit_any_policy_pending = _this.certs[i].extensions[j].parsedValue.value_block.value_dec;
+ }
+ else
+ {
+ inhibit_any_policy_pending = (inhibit_any_policy_pending > _this.certs[i].extensions[j].parsedValue.value_block.value_dec) ? _this.certs[i].extensions[j].parsedValue.value_block.value_dec : inhibit_any_policy_pending;
+ }
+ }
+ }
+ }
+ // #endregion
+ }
+
+ // #region Check "inhibit_any_policy_indicator"
+ if(inhibit_any_policy_indicator === true)
+ delete (policies_and_certs[0])[i]; // Unset value to "undefined" for "anyPolicies" value for current certificate
+ // #endregion
+
+ // #region Combine information from certificate policies and policy mappings
+ if((typeof cert_policies[i] !== "undefined") &&
+ (typeof policy_mappings[i] !== "undefined") &&
+ (policy_mapping_inhibit_indicator === false))
+ {
+ for(var m = 0; m < cert_policies[i].certificatePolicies.length; m++)
+ {
+ var domainPolicy = "";
+
+ // #region Find if current policy is in "mappings" array
+ for(var n = 0; n < policy_mappings[i].mappings.length; n++)
+ {
+ if(policy_mappings[i].mappings[n].subjectDomainPolicy === cert_policies[i].certificatePolicies[m].policyIdentifier)
+ {
+ domainPolicy = policy_mappings[i].mappings[n].issuerDomainPolicy;
+ break;
+ }
+
+ // #region Could be the case for some reasons
+ if(policy_mappings[i].mappings[n].issuerDomainPolicy === cert_policies[i].certificatePolicies[m].policyIdentifier)
+ {
+ domainPolicy = policy_mappings[i].mappings[n].subjectDomainPolicy;
+ break;
+ }
+ // #endregion
+ }
+
+ if(domainPolicy === "")
+ continue;
+ // #endregion
+
+ // #region Find the index of "domainPolicy"
+ var domainPolicy_index = (-1);
+
+ for(var p = 0; p < all_policies.length; p++)
+ {
+ if(all_policies[p] === domainPolicy)
+ {
+ domainPolicy_index = p;
+ break;
+ }
+ }
+ // #endregion
+
+ // #region Change array value for "domainPolicy"
+ if(domainPolicy_index !== (-1))
+ (policies_and_certs[domainPolicy_index])[i] = true; // Put "set" in "domainPolicy" cell for specific certificate
+ // #endregion
+ }
+ }
+ // #endregion
+
+ // #region Process with "pending constraints"
+ if(explicit_policy_indicator === false)
+ {
+ if(pending_constraints[0] === true)
+ {
+ explicit_policy_pending--;
+ if(explicit_policy_pending === 0)
+ {
+ explicit_policy_indicator = true;
+ pending_constraints[0] = false;
+ }
+ }
+ }
+
+ if(policy_mapping_inhibit_indicator === false)
+ {
+ if(pending_constraints[1] === true)
+ {
+ policy_mapping_inhibit_pending--;
+ if(policy_mapping_inhibit_pending === 0)
+ {
+ policy_mapping_inhibit_indicator = true;
+ pending_constraints[1] = false;
+ }
+ }
+ }
+
+ if(inhibit_any_policy_indicator === false)
+ {
+ if(pending_constraints[2] === true)
+ {
+ inhibit_any_policy_pending--;
+ if(inhibit_any_policy_pending === 0)
+ {
+ inhibit_any_policy_indicator = true;
+ pending_constraints[2] = false;
+ }
+ }
+ }
+ // #endregion
+ }
+ }
+
+ // #region Create "set of authorities-constrained policies"
+ var auth_constr_policies = new Array();
+
+ for(var i = 0; i < policies_and_certs.length; i++)
+ {
+ var found = true;
+
+ for(var j = 0; j < (_this.certs.length - 1) ; j++)
+ {
+ if(typeof (policies_and_certs[i])[j] === "undefined")
+ {
+ found = false;
+ break;
+ }
+ }
+
+ if(found === true)
+ auth_constr_policies.push(all_policies[i]);
+ }
+ // #endregion
+
+ // #region Create "set of user-constrained policies"
+ var user_constr_policies = new Array();
+
+ for(var i = 0; i < auth_constr_policies.length; i++)
+ {
+ for(var j = 0; j < initial_policy_set.length; j++)
+ {
+ if(initial_policy_set[j] === auth_constr_policies[i])
+ {
+ user_constr_policies.push(initial_policy_set[j]);
+ break;
+ }
+ }
+ }
+ // #endregion
+
+ // #region Combine output object
+ return {
+ result: (user_constr_policies.length > 0),
+ result_code: 0,
+ result_message: (user_constr_policies.length > 0) ? "" : "Zero \"user_constr_policies\" array, no intersections with \"auth_constr_policies\"",
+ auth_constr_policies: auth_constr_policies,
+ user_constr_policies: user_constr_policies,
+ explicit_policy_indicator: explicit_policy_indicator,
+ policy_mappings: policy_mappings
+ };
+ // #endregion
+ }
+ );
+ // #endregion
+
+ // #region Work with name constraints
+ sequence = sequence.then(
+ function(policy_result)
+ {
+ // #region Auxiliary functions for name constraints checking
+ function compare_dNSName(name, constraint)
+ {
+ /// <summary>Compare two dNSName values</summary>
+ /// <param name="name" type="String">DNS from name</param>
+ /// <param name="constraint" type="String">Constraint for DNS from name</param>
+ /// <returns type="Boolean">Boolean result - valid or invalid the "name" against the "constraint"</returns>
+
+ // #region Make a "string preparation" for both name and constrain
+ var name_prepared = in_window.org.pkijs.stringPrep(name);
+ var constraint_prepared = in_window.org.pkijs.stringPrep(constraint);
+ // #endregion
+
+ // #region Make a "splitted" versions of "constraint" and "name"
+ var name_splitted = name_prepared.split(".");
+ var constraint_splitted = constraint_prepared.split(".");
+ // #endregion
+
+ // #region Length calculation and additional check
+ var name_len = name_splitted.length;
+ var constr_len = constraint_splitted.length;
+
+ if((name_len === 0) || (constr_len === 0) || (name_len < constr_len))
+ return false;
+ // #endregion
+
+ // #region Check that no part of "name" has zero length
+ for(var i = 0; i < name_len; i++)
+ {
+ if(name_splitted[i].length === 0)
+ return false;
+ }
+ // #endregion
+
+ // #region Check that no part of "constraint" has zero length
+ for(var i = 0; i < constr_len; i++)
+ {
+ if(constraint_splitted[i].length === 0)
+ {
+ if(i === 0)
+ {
+ if(constr_len === 1)
+ return false;
+ else
+ continue;
+ }
+
+ return false;
+ }
+ }
+ // #endregion
+
+ // #region Check that "name" has a tail as "constraint"
+
+ for(var i = 0; i < constr_len; i++)
+ {
+ if(constraint_splitted[constr_len - 1 - i].length === 0)
+ continue;
+
+ if(name_splitted[name_len - 1 - i].localeCompare(constraint_splitted[constr_len - 1 - i]) !== 0)
+ return false;
+ }
+ // #endregion
+
+ return true;
+ }
+
+ function compare_rfc822Name(name, constraint)
+ {
+ /// <summary>Compare two rfc822Name values</summary>
+ /// <param name="name" type="String">E-mail address from name</param>
+ /// <param name="constraint" type="String">Constraint for e-mail address from name</param>
+ /// <returns type="Boolean">Boolean result - valid or invalid the "name" against the "constraint"</returns>
+
+ // #region Make a "string preparation" for both name and constrain
+ var name_prepared = in_window.org.pkijs.stringPrep(name);
+ var constraint_prepared = in_window.org.pkijs.stringPrep(constraint);
+ // #endregion
+
+ // #region Make a "splitted" versions of "constraint" and "name"
+ var name_splitted = name_prepared.split("@");
+ var constraint_splitted = constraint_prepared.split("@");
+ // #endregion
+
+ // #region Splitted array length checking
+ if((name_splitted.length === 0) || (constraint_splitted.length === 0) || (name_splitted.length < constraint_splitted.length))
+ return false;
+ // #endregion
+
+ if(constraint_splitted.length === 1)
+ {
+ var result = compare_dNSName(name_splitted[1], constraint_splitted[0]);
+
+ if(result)
+ {
+ // #region Make a "splitted" versions of domain name from "constraint" and "name"
+ var ns = name_splitted[1].split(".");
+ var cs = constraint_splitted[0].split(".");
+ // #endregion
+
+ if(cs[0].length === 0)
+ return true;
+
+ return ns.length === cs.length;
+ }
+ else
+ return false;
+ }
+ else
+ return (name_prepared.localeCompare(constraint_prepared) === 0);
+
+ return false;
+ }
+
+ function compare_uniformResourceIdentifier(name, constraint)
+ {
+ /// <summary>Compare two uniformResourceIdentifier values</summary>
+ /// <param name="name" type="String">uniformResourceIdentifier from name</param>
+ /// <param name="constraint" type="String">Constraint for uniformResourceIdentifier from name</param>
+ /// <returns type="Boolean">Boolean result - valid or invalid the "name" against the "constraint"</returns>
+
+ // #region Make a "string preparation" for both name and constrain
+ var name_prepared = in_window.org.pkijs.stringPrep(name);
+ var constraint_prepared = in_window.org.pkijs.stringPrep(constraint);
+ // #endregion
+
+ // #region Find out a major URI part to compare with
+ var ns = name_prepared.split("/");
+ var cs = constraint_prepared.split("/");
+
+ if(cs.length > 1) // Malformed constraint
+ return false;
+
+ if(ns.length > 1) // Full URI string
+ {
+ for(var i = 0; i < ns.length; i++)
+ {
+ if((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ':'))
+ {
+ var ns_port = ns[i].split(":");
+ name_prepared = ns_port[0];
+ break;
+ }
+ }
+ }
+ // #endregion
+
+ var result = compare_dNSName(name_prepared, constraint_prepared);
+
+ if(result)
+ {
+ // #region Make a "splitted" versions of "constraint" and "name"
+ var name_splitted = name_prepared.split(".");
+ var constraint_splitted = constraint_prepared.split(".");
+ // #endregion
+
+ if(constraint_splitted[0].length === 0)
+ return true;
+
+ return name_splitted.length === constraint_splitted.length;
+ }
+ else
+ return false;
+
+ return false;
+ }
+
+ function compare_iPAddress(name, constraint)
+ {
+ /// <summary>Compare two iPAddress values</summary>
+ /// <param name="name" type="in_window.org.pkijs.asn1.OCTETSTRING">iPAddress from name</param>
+ /// <param name="constraint" type="in_window.org.pkijs.asn1.OCTETSTRING">Constraint for iPAddress from name</param>
+ /// <returns type="Boolean">Boolean result - valid or invalid the "name" against the "constraint"</returns>
+
+ // #region Common variables
+ var name_view = new Uint8Array(name.value_block.value_hex);
+ var constraint_view = new Uint8Array(constraint.value_block.value_hex);
+ // #endregion
+
+ // #region Work with IPv4 addresses
+ if((name_view.length === 4) && (constraint_view.length === 8))
+ {
+ for(var i = 0; i < 4; i++)
+ {
+ if((name_view[i] ^ constraint_view[i]) & constraint_view[i + 4])
+ return false;
+ }
+
+ return true;
+ }
+ // #endregion
+
+ // #region Work with IPv6 addresses
+ if((name_view.length === 16) && (constraint_view.length === 32))
+ {
+ for(var i = 0; i < 16; i++)
+ {
+ if((name_view[i] ^ constraint_view[i]) & constraint_view[i + 16])
+ return false;
+ }
+
+ return true;
+ }
+ // #endregion
+
+ return false;
+ }
+
+ function compare_directoryName(name, constraint)
+ {
+ /// <summary>Compare two directoryName values</summary>
+ /// <param name="name" type="in_window.org.pkijs.simpl.RDN">directoryName from name</param>
+ /// <param name="constraint" type="in_window.org.pkijs.simpl.RDN">Constraint for directoryName from name</param>
+ /// <param name="any" type="Boolean">Boolean flag - should be comparision interrupted after first match or we need to match all "constraints" parts</param>
+ /// <returns type="Boolean">Boolean result - valid or invalid the "name" against the "constraint"</returns>
+
+ // #region Initial check
+ if((name.types_and_values.length === 0) || (constraint.types_and_values.length === 0))
+ return true;
+
+ if(name.types_and_values.length < constraint.types_and_values.length)
+ return false;
+ // #endregion
+
+ // #region Initial variables
+ var result = true;
+ var name_start = 0;
+ // #endregion
+
+ for(var i = 0; i < constraint.types_and_values.length; i++)
+ {
+ var local_result = false;
+
+ for(var j = name_start; j < name.types_and_values.length; j++)
+ {
+ local_result = name.types_and_values[j].isEqual(constraint.types_and_values[i]);
+
+ if(name.types_and_values[j].type === constraint.types_and_values[i].type)
+ result = result && local_result;
+
+ if(local_result === true)
+ {
+ if((name_start === 0) || (name_start === j))
+ {
+ name_start = j + 1;
+ break;
+ }
+ else // Structure of "name" must be the same with "constraint"
+ return false;
+ }
+ }
+
+ if(local_result === false)
+ return false;
+ }
+
+ return (name_start === 0) ? false : result;
+ }
+ // #endregion
+
+ // #region Check a result from "policy checking" part
+ if(policy_result.result === false)
+ return policy_result;
+ // #endregion
+
+ // #region Check all certificates, excluding "trust anchor"
+ path_depth = 1;
+
+ for(var i = (_this.certs.length - 2) ; i >= 0 ; i--, path_depth++)
+ {
+ // #region Support variables
+ var subject_alt_names = new Array();
+
+ var cert_permitted_subtrees = new Array();
+ var cert_excluded_subtrees = new Array();
+ // #endregion
+
+ if("extensions" in _this.certs[i])
+ {
+ for(var j = 0; j < _this.certs[i].extensions.length; j++)
+ {
+ // #region NameConstraints
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.30")
+ {
+ if("permittedSubtrees" in _this.certs[i].extensions[j].parsedValue)
+ cert_permitted_subtrees = cert_permitted_subtrees.concat(_this.certs[i].extensions[j].parsedValue.permittedSubtrees);
+
+ if("excludedSubtrees" in _this.certs[i].extensions[j].parsedValue)
+ cert_excluded_subtrees = cert_excluded_subtrees.concat(_this.certs[i].extensions[j].parsedValue.excludedSubtrees);
+ }
+ // #endregion
+
+ // #region SubjectAltName
+ if(_this.certs[i].extensions[j].extnID === "2.5.29.17")
+ subject_alt_names = subject_alt_names.concat(_this.certs[i].extensions[j].parsedValue.altNames);
+ // #endregion
+ }
+ }
+
+ // #region Checking for "required name forms"
+ var form_found = (required_name_forms.length <= 0);
+
+ for(var j = 0; j < required_name_forms.length; j++)
+ {
+ switch(required_name_forms[j].base.NameType)
+ {
+ case 4: // directoryName
+ {
+ if(required_name_forms[j].base.Name.types_and_values.length !== _this.certs[i].subject.types_and_values.length)
+ continue;
+
+ form_found = true;
+
+ for(var k = 0; k < _this.certs[i].subject.types_and_values.length; k++)
+ {
+ if(_this.certs[i].subject.types_and_values[k].type !== required_name_forms[j].base.Name.types_and_values[k].type)
+ {
+ form_found = false;
+ break;
+ }
+ }
+
+ if(form_found === true)
+ break;
+ }
+ break;
+ default: // ??? Probably here we should reject the certificate ???
+ }
+ }
+
+ if(form_found === false)
+ {
+ policy_result.result = false;
+ policy_result.result_code = 21;
+ policy_result.result_message = "No neccessary name form found";
+
+ return new Promise(function(resolve, reject)
+ {
+ reject(policy_result);
+ });
+ }
+ // #endregion
+
+ // #region Checking for "permited sub-trees"
+ // #region Make groups for all types of constraints
+ var constr_groups = new Array(); // Array of array for groupped constraints
+ constr_groups[0] = new Array(); // rfc822Name
+ constr_groups[1] = new Array(); // dNSName
+ constr_groups[2] = new Array(); // directoryName
+ constr_groups[3] = new Array(); // uniformResourceIdentifier
+ constr_groups[4] = new Array(); // iPAddress
+
+ for(var j = 0; j < permitted_subtrees.length; j++)
+ {
+ switch(permitted_subtrees[j].base.NameType)
+ {
+ // #region rfc822Name
+ case 1:
+ constr_groups[0].push(permitted_subtrees[j]);
+ break;
+ // #endregion
+ // #region dNSName
+ case 2:
+ constr_groups[1].push(permitted_subtrees[j]);
+ break;
+ // #endregion
+ // #region directoryName
+ case 4:
+ constr_groups[2].push(permitted_subtrees[j]);
+ break;
+ // #endregion
+ // #region uniformResourceIdentifier
+ case 6:
+ constr_groups[3].push(permitted_subtrees[j]);
+ break;
+ // #endregion
+ // #region iPAddress
+ case 7:
+ constr_groups[4].push(permitted_subtrees[j]);
+ break;
+ // #endregion
+ // #region default
+
+ default:
+ // #endregion
+ }
+ }
+ // #endregion
+
+ // #region Check name constraints groupped by type, one-by-one
+ for(var p = 0; p < 5; p++)
+ {
+ var group_permitted = false;
+ var valueExists = false;
+ var group = constr_groups[p];
+
+ for(var j = 0; j < group.length; j++)
+ {
+ switch(p)
+ {
+ // #region rfc822Name
+ case 0:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 1) // rfc822Name
+ {
+ valueExists = true;
+ group_permitted = group_permitted || compare_rfc822Name(subject_alt_names[k].Name, group[j].base.Name);
+ }
+ }
+ }
+ else // Try to find out "emailAddress" inside "subject"
+ {
+ for(var k = 0; k < _this.certs[i].subject.types_and_values.length; k++)
+ {
+ if((_this.certs[i].subject.types_and_values[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
+ (_this.certs[i].subject.types_and_values[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
+ {
+ valueExists = true;
+ group_permitted = group_permitted || compare_rfc822Name(_this.certs[i].subject.types_and_values[k].value.value_block.value, group[j].base.Name);
+ }
+ }
+ }
+ break;
+ // #endregion
+ // #region dNSName
+ case 1:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 2) // dNSName
+ {
+ valueExists = true;
+ group_permitted = group_permitted || compare_dNSName(subject_alt_names[k].Name, group[j].base.Name);
+ }
+ }
+ }
+ break;
+ // #endregion
+ // #region directoryName
+ case 2:
+ valueExists = true;
+ group_permitted = compare_directoryName(_this.certs[i].subject, group[j].base.Name);
+ break;
+ // #endregion
+ // #region uniformResourceIdentifier
+ case 3:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 6) // uniformResourceIdentifier
+ {
+ valueExists = true;
+ group_permitted = group_permitted || compare_uniformResourceIdentifier(subject_alt_names[k].Name, group[j].base.Name);
+ }
+ }
+ }
+ break;
+ // #endregion
+ // #region iPAddress
+ case 4:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 7) // iPAddress
+ {
+ valueExists = true;
+ group_permitted = group_permitted || compare_iPAddress(subject_alt_names[k].Name, group[j].base.Name);
+ }
+ }
+ }
+ break;
+ // #endregion
+ // #region default
+
+ default:
+ // #endregion
+ }
+
+ if(group_permitted)
+ break;
+ }
+
+ if((group_permitted === false) && (group.length > 0) && valueExists)
+ {
+ policy_result.result = false;
+ policy_result.result_code = 41;
+ policy_result.result_message = "Failed to meet \"permitted sub-trees\" name constraint";
+
+ return new Promise(function(resolve, reject)
+ {
+ reject(policy_result);
+ });
+ }
+ }
+ // #endregion
+ // #endregion
+
+ // #region Checking for "excluded sub-trees"
+ var excluded = false;
+
+ for(var j = 0; j < excluded_subtrees.length; j++)
+ {
+ switch(excluded_subtrees[j].base.NameType)
+ {
+ // #region rfc822Name
+ case 1:
+ if(subject_alt_names.length >= 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 1) // rfc822Name
+ excluded = excluded || compare_rfc822Name(subject_alt_names[k].Name, excluded_subtrees[j].base.Name);
+ }
+ }
+ else // Try to find out "emailAddress" inside "subject"
+ {
+ for(var k = 0; k < _this.subject.types_and_values.length; k++)
+ {
+ if((_this.subject.types_and_values[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
+ (_this.subject.types_and_values[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
+ {
+ excluded = excluded || compare_rfc822Name(_this.subject.types_and_values[k].value.value_block.value, excluded_subtrees[j].base.Name);
+ }
+ }
+ }
+ break;
+ // #endregion
+ // #region dNSName
+ case 2:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 2) // dNSName
+ excluded = excluded || compare_dNSName(subject_alt_names[k].Name, excluded_subtrees[j].base.Name);
+ }
+ }
+ break;
+ // #endregion
+ // #region directoryName
+ case 4:
+ excluded = excluded || compare_directoryName(_this.certs[i].subject, excluded_subtrees[j].base.Name);
+ break;
+ // #endregion
+ // #region uniformResourceIdentifier
+ case 6:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 6) // uniformResourceIdentifier
+ excluded = excluded || compare_uniformResourceIdentifier(subject_alt_names[k].Name, excluded_subtrees[j].base.Name);
+ }
+ }
+ break;
+ // #endregion
+ // #region iPAddress
+ case 7:
+ if(subject_alt_names.length > 0)
+ {
+ for(var k = 0; k < subject_alt_names.length; k++)
+ {
+ if(subject_alt_names[k].NameType === 7) // iPAddress
+ excluded = excluded || compare_iPAddress(subject_alt_names[k].Name, excluded_subtrees[j].base.Name);
+ }
+ }
+ break;
+ // #endregion
+ // #region default
+
+ default: // No action, but probably here we need to create a warning for "malformed constraint"
+ // #endregion
+ }
+
+ if(excluded)
+ break;
+ }
+
+ if(excluded === true)
+ {
+ policy_result.result = false;
+ policy_result.result_code = 42;
+ policy_result.result_message = "Failed to meet \"excluded sub-trees\" name constraint";
+
+ return new Promise(function(resolve, reject)
+ {
+ reject(policy_result);
+ });
+ }
+ // #endregion
+
+ // #region Append "cert_..._subtrees" to "..._subtrees"
+ permitted_subtrees = permitted_subtrees.concat(cert_permitted_subtrees);
+ excluded_subtrees = excluded_subtrees.concat(cert_excluded_subtrees);
+ // #endregion
+ }
+ // #endregion
+
+ return policy_result;
+ }
+ );
+ // #endregion
+
+ return sequence;
+ };
+ //**************************************************************************************
+ // #endregion
+ //**************************************************************************************
+}
+)(typeof exports !== "undefined" ? exports : window);
diff --git a/dom/webauthn/tests/test_webauthn_abort_signal.html b/dom/webauthn/tests/test_webauthn_abort_signal.html
new file mode 100644
index 0000000000..eb9f43333a
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_abort_signal.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for aborting W3C Web Authentication request</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>Test for aborting W3C Web Authentication request</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1415675">Mozilla Bug 1415675</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectAbortError(aResult) {
+ is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
+ }
+
+ add_task(async () => {
+ // Enable USB tokens.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["security.webauth.webauthn_enable_softtoken", false],
+ ["security.webauth.webauthn_enable_usbtoken", true],
+ ]});
+ });
+
+ // Start a new MakeCredential() request.
+ function requestMakeCredential(signal) {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ };
+
+ return navigator.credentials.create({publicKey, signal});
+ }
+
+ // Start a new GetAssertion() request.
+ async function requestGetAssertion(signal) {
+ let newCredential = {
+ type: "public-key",
+ id: crypto.getRandomValues(new Uint8Array(16)),
+ transports: ["usb"],
+ };
+
+ let publicKey = {
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: document.domain,
+ allowCredentials: [newCredential]
+ };
+
+ // Start the request, handle failures only.
+ return navigator.credentials.get({publicKey, signal});
+ }
+
+ // Create an AbortController and abort immediately.
+ add_task(async function test_create_abortcontroller_and_abort() {
+ let ctrl = new AbortController();
+ ctrl.abort();
+
+ // The event shouldn't fire.
+ ctrl.signal.onabort = arrivingHereIsBad;
+
+ // MakeCredential() should abort immediately.
+ await requestMakeCredential(ctrl.signal)
+ .then(arrivingHereIsBad)
+ .catch(expectAbortError);
+
+ // GetAssertion() should abort immediately.
+ await requestGetAssertion(ctrl.signal)
+ .then(arrivingHereIsBad)
+ .catch(expectAbortError);
+ });
+
+ // Request a new credential and abort the request.
+ add_task(async function test_request_credential_and_abort() {
+ let aborted = false;
+ let ctrl = new AbortController();
+
+ ctrl.signal.onabort = () => {
+ ok(!aborted, "abort event fired once");
+ aborted = true;
+ };
+
+ // Request a new credential.
+ let request = requestMakeCredential(ctrl.signal)
+ .then(arrivingHereIsBad)
+ .catch(err => {
+ ok(aborted, "abort event was fired");
+ expectAbortError(err);
+ });
+
+ // Wait a tick for the statemachine to start.
+ await Promise.resolve();
+
+ // Abort the request.
+ ok(!aborted, "request still pending");
+ ctrl.abort();
+ ok(aborted, "request aborted");
+
+ // Wait for the request to terminate.
+ await request;
+ });
+
+ // Request a new assertion and abort the request.
+ add_task(async function test_request_assertion_and_abort() {
+ let aborted = false;
+ let ctrl = new AbortController();
+
+ ctrl.signal.onabort = () => {
+ ok(!aborted, "abort event fired once");
+ aborted = true;
+ };
+
+ // Request a new assertion.
+ let request = requestGetAssertion(ctrl.signal)
+ .then(arrivingHereIsBad)
+ .catch(err => {
+ ok(aborted, "abort event was fired");
+ expectAbortError(err);
+ });
+
+ // Wait a tick for the statemachine to start.
+ await Promise.resolve();
+
+ // Abort the request.
+ ok(!aborted, "request still pending");
+ ctrl.abort();
+ ok(aborted, "request aborted");
+
+ // Wait for the request to terminate.
+ await request;
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_attestation_conveyance.html b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
new file mode 100644
index 0000000000..061e2c2624
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>W3C Web Authentication - Attestation Conveyance</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <script type="text/javascript" src="cbor.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <h1>W3C Web Authentication - Attestation Conveyance</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1428916">Mozilla Bug 1428916</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1416056">Mozilla Bug 1416056</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["security.webauth.webauthn_testing_allow_direct_attestation", true],
+ ]});
+ await addVirtualAuthenticator();
+ });
+
+ function verifyAnonymizedCertificate(aResult) {
+ return webAuthnDecodeCBORAttestation(aResult.response.attestationObject)
+ .then(({fmt, attStmt}) => {
+ is(fmt, "none", "Is a None Attestation");
+ is(typeof(attStmt), "object", "attStmt is a map");
+ is(Object.keys(attStmt).length, 0, "attStmt is empty");
+ });
+ }
+
+ async function verifyDirectCertificate(aResult) {
+ let clientDataHash = await crypto.subtle.digest("SHA-256", aResult.response.clientDataJSON)
+ .then(digest => new Uint8Array(digest));
+ let {fmt, attStmt, authData, authDataObj} = await webAuthnDecodeCBORAttestation(aResult.response.attestationObject);
+ is(fmt, "packed", "Is a Packed Attestation");
+ let signedData = new Uint8Array(authData.length + clientDataHash.length);
+ signedData.set(authData);
+ signedData.set(clientDataHash, authData.length);
+ let valid = await verifySignature(authDataObj.publicKeyHandle, signedData, new Uint8Array(attStmt.sig));
+ ok(valid, "Signature is valid.");
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectTypeError(aResult) {
+ ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError, got " + aResult);
+ }
+
+ // Start a new MakeCredential() request.
+ function requestMakeCredential(attestation) {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ attestation,
+ };
+
+ return navigator.credentials.create({publicKey});
+ }
+
+ // Test success cases for make credential.
+ add_task(async function test_make_credential_success () {
+ // No selection criteria should be equal to none, which means anonymized
+ await requestMakeCredential()
+ .then(verifyAnonymizedCertificate)
+ .catch(arrivingHereIsBad);
+
+ // Request an unknown attestation type. This should be treated as "none".
+ await requestMakeCredential("unknown")
+ .then(verifyAnonymizedCertificate)
+ .catch(arrivingHereIsBad);
+
+ // Request no attestation.
+ await requestMakeCredential("none")
+ .then(verifyAnonymizedCertificate)
+ .catch(arrivingHereIsBad);
+
+ // Request indirect attestation, which is the same as direct.
+ await requestMakeCredential("indirect")
+ .then(verifyDirectCertificate)
+ .catch(arrivingHereIsBad);
+
+ // Request direct attestation, which will prompt for user intervention.
+ await requestMakeCredential("direct")
+ .then(verifyDirectCertificate)
+ .catch(arrivingHereIsBad);
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_authenticator_selection.html b/dom/webauthn/tests/test_webauthn_authenticator_selection.html
new file mode 100644
index 0000000000..35667493a7
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_authenticator_selection.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>W3C Web Authentication - Authenticator Selection Criteria</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>W3C Web Authentication - Authenticator Selection Criteria</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406462">Mozilla Bug 1406462</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ // We store the credential of the first successful make credential
+ // operation so we can use it for get assertion tests later.
+ let gCredential;
+
+ // Start a new MakeCredential() request.
+ function requestMakeCredential(authenticatorSelection) {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ authenticatorSelection,
+ };
+
+ return navigator.credentials.create({publicKey});
+ }
+
+ // Start a new GetAssertion() request.
+ function requestGetAssertion(userVerification) {
+ let newCredential = {
+ type: "public-key",
+ id: gCredential,
+ transports: ["usb"],
+ };
+
+ let publicKey = {
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: document.domain,
+ allowCredentials: [newCredential]
+ };
+
+ if (userVerification) {
+ publicKey.userVerification = userVerification;
+ }
+
+ return navigator.credentials.get({publicKey});
+ }
+
+ // Test success cases for make credential.
+ add_task(async function test_make_credential_successes() {
+ // No selection criteria.
+ await requestMakeCredential({})
+ // Save the credential so we can use it for sign success tests.
+ .then(res => gCredential = res.rawId)
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Request a cross-platform authenticator.
+ await requestMakeCredential({authenticatorAttachment: "cross-platform"})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Require a resident key.
+ await requestMakeCredential({requireResidentKey: true})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Don't require a resident key.
+ await requestMakeCredential({requireResidentKey: false})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Require user verification.
+ await requestMakeCredential({userVerification: "required"})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Prefer user verification.
+ await requestMakeCredential({userVerification: "preferred"})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Discourage user verification.
+ await requestMakeCredential({userVerification: "discouraged"})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ });
+
+ // Test success cases for get assertion.
+ add_task(async function test_get_assertion_successes() {
+ // No selection criteria.
+ await requestGetAssertion()
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Require user verification.
+ await requestGetAssertion("required")
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Prefer user verification.
+ await requestGetAssertion("preferred")
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Discourage user verification.
+ await requestGetAssertion("discouraged")
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ });
+
+ // Test failure cases for make credential.
+ add_task(async function test_make_credential_failures() {
+ // Request a platform authenticator.
+ await requestMakeCredential({authenticatorAttachment: "platform"})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ });
+
+ // Test failure cases for get assertion.
+ add_task(async function test_get_assertion_failures() {
+ // No failures currently tested
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_authenticator_transports.html b/dom/webauthn/tests/test_webauthn_authenticator_transports.html
new file mode 100644
index 0000000000..0d6e6d3055
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_authenticator_transports.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>W3C Web Authentication - Authenticator Transports</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>W3C Web Authentication - Authenticator Transports</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406467">Mozilla Bug 1406467</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ function expectInvalidStateError(aResult) {
+ ok(aResult.toString().startsWith("InvalidStateError"), "Expecting a InvalidStateError, got " + aResult);
+ }
+
+ // Store the credential and transports of the first successful make credential
+ // operation so we can use them to get assertions later.
+ let gCredential;
+ let gTransports;
+
+ // Start a new MakeCredential() request.
+ function requestMakeCredential(excludeCredentials) {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ excludeCredentials
+ };
+
+ return navigator.credentials.create({publicKey});
+ }
+
+ // Start a new GetAssertion() request.
+ function requestGetAssertion(allowCredentials) {
+ let publicKey = {
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: document.domain,
+ allowCredentials
+ };
+
+ return navigator.credentials.get({publicKey});
+ }
+
+ // Test make credential behavior.
+ add_task(async function test_make_credential() {
+ // Make a credential.
+ await requestMakeCredential([])
+ // Save the credential for later.
+ .then(res => {
+ gCredential = res.rawId;
+ gTransports = res.response.getTransports();
+ })
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // gTransports should be "a sequence of zero or more unique DOMStrings in lexicographical order."
+ for (let i = 0; i < gTransports.length - 1; i++) {
+ if (gTransports[i] >= gTransports[i+1]) {
+ ok(false, "getTransports() should return a sorted list");
+ }
+ }
+
+ // Pass a random credential to exclude.
+ await requestMakeCredential([{
+ type: "public-key",
+ id: crypto.getRandomValues(new Uint8Array(16)),
+ transports: gTransports,
+ }]).then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Pass gCredential with transport=usb.
+ // The credential already exists, and the softoken consents to create,
+ // so the error is InvalidState and not NotAllowed.
+ await requestMakeCredential([{
+ type: "public-key",
+ id: gCredential,
+ transports: ["usb"],
+ }]).then(arrivingHereIsBad)
+ .catch(expectInvalidStateError);
+
+ // Pass gCredential with transport=nfc.
+ // The softoken pretends to support all transports.
+ // Also, as above, the credential exists and the token indicates consent.
+ await requestMakeCredential([{
+ type: "public-key",
+ id: gCredential,
+ transports: ["nfc"],
+ }]).then(arrivingHereIsBad)
+ .catch(expectInvalidStateError);
+
+ // Pass gCredential with an empty transports list.
+ // As above, the token indicates consent, so expect InvalidStateError.
+ await requestMakeCredential([{
+ type: "public-key",
+ id: gCredential,
+ transports: [],
+ }]).then(arrivingHereIsBad)
+ .catch(expectInvalidStateError);
+ });
+
+ // Test get assertion behavior.
+ add_task(async function test_get_assertion() {
+ // Request an assertion for gCredential.
+ await requestGetAssertion([{
+ type: "public-key",
+ id: gCredential,
+ transports: gTransports,
+ }]).then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Request an assertion for a random credential. This should be
+ // indistinguishable from the user denying consent for a known
+ // credential, so expect a NotAllowedError.
+ await requestGetAssertion([{
+ type: "public-key",
+ id: crypto.getRandomValues(new Uint8Array(16)),
+ transports: ["usb"],
+ }]).then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // Request an assertion for gCredential with transport=nfc.
+ // The softoken pretends to support all transports.
+ await requestGetAssertion([{
+ type: "public-key",
+ id: gCredential,
+ transports: ["nfc"],
+ }]).then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ // Request an assertion for gCredential with an empty transports list.
+ await requestGetAssertion([{
+ type: "public-key",
+ id: gCredential,
+ transports: [],
+ }]).then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_crossorigin_featurepolicy.html b/dom/webauthn/tests/test_webauthn_crossorigin_featurepolicy.html
new file mode 100644
index 0000000000..6e092d02ba
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_crossorigin_featurepolicy.html
@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for Publickey-Credentials-Get Feature Policy for W3C Web Authentication</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 for Publickey-Credentials-Get Feature Policy for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460986">Mozilla Bug 1460986</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ var gAuthenticatorId;
+ var gSameCredId;
+ var gCrossCredId;
+
+ const CROSS_DOMAIN = "example.org";
+
+ function compare(a, b) {
+ if (a.length != b.length) return false;
+ for (let i = 0; i < a.length; i += 1) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+ }
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult == "NotAllowedError", "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ function expectSameCredId(aResult) {
+ ok(compare(aResult, gSameCredId), "Expecting credential for " + document.domain);
+ }
+
+ function expectCrossCredId(aResult) {
+ ok(compare(aResult, gCrossCredId), "Expecting credential for " + CROSS_DOMAIN);
+ }
+
+ function getAssertion(id) {
+ let chall = new Uint8Array(16);
+ this.content.window.crypto.getRandomValues(chall);
+
+ let options = {
+ challenge: chall,
+ allowCredentials: [ { type: "public-key", id } ],
+ };
+
+ return this.content.window.navigator.credentials.get({publicKey: options})
+ .then(res => Promise.resolve(new Uint8Array(res.rawId)))
+ .catch(e => Promise.reject(e.name));
+ }
+
+ function createCredential() {
+ this.content.document.notifyUserGestureActivation();
+
+ const cose_alg_ECDSA_w_SHA256 = -7;
+ let publicKey = {
+ rp: {id: this.content.window.document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: this.content.window.crypto.getRandomValues(new Uint8Array(16)),
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ };
+
+ return this.content.window.navigator.credentials.create({publicKey})
+ .then(res => Promise.resolve(new Uint8Array(res.rawId)))
+ .catch(e => Promise.reject(e.name));
+ }
+
+ async function setup(preloadSame, preloadCross) {
+ if (!gAuthenticatorId) {
+ gAuthenticatorId = await addVirtualAuthenticator();
+ }
+ if (gSameCredId) {
+ removeCredential(gAuthenticatorId, bytesToBase64UrlSafe(gSameCredId));
+ gSameCredId = undefined;
+ }
+ if (gCrossCredId) {
+ removeCredential(gAuthenticatorId, bytesToBase64UrlSafe(gCrossCredId));
+ gCrossCredId = undefined;
+ }
+ if (preloadSame) {
+ gSameCredId = await addCredential(gAuthenticatorId, document.domain).then(id => base64ToBytesUrlSafe(id));
+ }
+ if (preloadCross) {
+ gCrossCredId = await addCredential(gAuthenticatorId, CROSS_DOMAIN).then(id => base64ToBytesUrlSafe(id));
+ }
+ }
+
+ add_task(async function test_same_origin_iframe_allow() {
+ // Don't preload any credentials. We'll try to create one in content.
+ await setup(false, false);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + document.domain + "/tests/dom/webauthn/tests/empty.html");
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe allows publickey-credentials-create");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe allows publickey-credentials-get");
+
+ // We should be able to create a credential in a same-origin iframe by default.
+ is(gSameCredId, undefined);
+ gSameCredId = new Uint8Array(await SpecialPowers.spawn(iframe, [], createCredential));
+
+ // We should be able to assert a credential in a same-origin iframe by default.
+ await SpecialPowers.spawn(iframe, [gSameCredId], getAssertion)
+ .then(expectSameCredId)
+ .catch(expectNotAllowedError);
+ });
+
+ add_task(async function test_same_origin_iframe_deny() {
+ // Preload same-origin credential to ensure we cannot assert it.
+ await setup(true, false);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + document.domain + "/tests/dom/webauthn/tests/empty.html");
+ iframe.setAttribute("allow", "publickey-credentials-create 'none'; publickey-credentials-get 'none'");
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe does not allow publickey-credentials-create");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe does not allow publickey-credentials-get");
+
+ // We should not be able to create a credential in a same-origin iframe if
+ // the iframe does not allow publickey-credentials-create.
+ await SpecialPowers.spawn(iframe, [], createCredential)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // We should not be able to assert a credential in a same-origin iframe if
+ // the iframe does not allow publickey-credentials-get.
+ await SpecialPowers.spawn(iframe, [gSameCredId], getAssertion)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ });
+
+ add_task(async function test_cross_origin_iframe_allow() {
+ // Don't preload any credentials. We'll try to create one in content.
+ await setup(false, false);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + CROSS_DOMAIN + "/tests/dom/webauthn/tests/empty.html");
+ iframe.setAttribute("allow", "publickey-credentials-create https://" + CROSS_DOMAIN + "; publickey-credentials-get https://" + CROSS_DOMAIN);
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe allows publickey-credentials-create");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe allows publickey-credentials-get");
+
+ // We should be able to create a credential in a same-origin iframe if
+ // the iframe allows publickey-credentials-create.
+ is(gCrossCredId, undefined);
+ gCrossCredId = new Uint8Array(await SpecialPowers.spawn(iframe, [], createCredential));
+
+ // We should be able to assert a credential in a cross-origin iframe if
+ // the iframe allows publickey-credentials-get.
+ await SpecialPowers.spawn(iframe, [gCrossCredId], getAssertion)
+ .then(expectCrossCredId)
+ .catch(arrivingHereIsBad);
+ });
+
+ add_task(async function test_cross_origin_iframe_deny() {
+ // Preload cross-origin credential to ensure we cannot assert it.
+ await setup(false, true);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + CROSS_DOMAIN + "/tests/dom/webauthn/tests/empty.html");
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe does not allow publickey-credentials-create");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe does not allow publickey-credentials-get");
+
+ // We should not be able to create a credential in a cross-origin iframe if
+ // the iframe does not allow publickey-credentials-create.
+ await SpecialPowers.spawn(iframe, [], createCredential)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // We should not be able to assert a credential in a cross-origin iframe if
+ // the iframe does not allow publickey-credentials-get.
+ await SpecialPowers.spawn(iframe, [gCrossCredId], getAssertion)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ });
+
+ add_task(async function test_cross_origin_iframe_create_but_not_get() {
+ // Don't preload any credentials. We'll try to create one in content.
+ await setup(false, false);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + CROSS_DOMAIN + "/tests/dom/webauthn/tests/empty.html");
+ iframe.setAttribute("allow", "publickey-credentials-create https://" + CROSS_DOMAIN);
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe allows publickey-credentials-create");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe does not allow publickey-credentials-get");
+
+ // We should be able to create a credential in a cross-origin iframe if
+ // the iframe allows publickey-credentials-create.
+ is(gCrossCredId, undefined);
+ gCrossCredId = new Uint8Array(await SpecialPowers.spawn(iframe, [], createCredential));
+
+ // We should not be able to assert a credential in a cross-origin iframe if
+ // the iframe does not allow publickey-credentials-get.
+ await SpecialPowers.spawn(iframe, [gCrossCredId], getAssertion)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ });
+
+ add_task(async function test_cross_origin_iframe_get_but_not_create() {
+ // Preload cross-origin credential so we can assert it.
+ await setup(false, true);
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://" + CROSS_DOMAIN + "/tests/dom/webauthn/tests/empty.html");
+ iframe.setAttribute("allow", "publickey-credentials-get https://" + CROSS_DOMAIN);
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ ok("featurePolicy" in iframe, "we have iframe.featurePolicy");
+ ok(!iframe.featurePolicy.allowsFeature("publickey-credentials-create"), "iframe does not publickey-credentials-create");
+ ok(iframe.featurePolicy.allowsFeature("publickey-credentials-get"), "iframe allows publickey-credentials-get");
+
+ // We should not be able to create a credential in a cross-origin iframe if
+ // the iframe does not allow publickey-credentials-create.
+ await SpecialPowers.spawn(iframe, [], createCredential)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+
+ // We should not be able to assert a credential in a cross-origin iframe if
+ // the iframe does not allow publickey-credentials-get.
+ await SpecialPowers.spawn(iframe, [gCrossCredId], getAssertion)
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_ctap2_omitted_credential_id.html b/dom/webauthn/tests/test_webauthn_ctap2_omitted_credential_id.html
new file mode 100644
index 0000000000..a2c14eb91d
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_ctap2_omitted_credential_id.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for omitted credential ID in a CTAP 2.0 authenticator response</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 for omitted credential ID in a CTAP 2.0 authenticator response</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1864504">Mozilla Bug 1864504</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ // CTAP 2.0 allows GetAssertion responses to omit a credential
+ // ID if the allowlist has length one. This can cause problems in
+ // MakeCredential as well because GetAssertion is used for pre-flighting.
+ await addVirtualAuthenticator("ctap2");
+ });
+
+ let validCred = null;
+
+ add_task(test_setup_valid_credential);
+ add_task(test_create_with_one_excluded_credential);
+ add_task(test_get_with_one_allowed_credential);
+
+ async function test_setup_valid_credential() {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ };
+
+ let res = await navigator.credentials.create({publicKey});
+ validCred = {type: "public-key", id: res.rawId};
+ }
+
+ async function test_create_with_one_excluded_credential() {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ excludeList: [validCred],
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ };
+
+ await navigator.credentials.create({publicKey});
+ ok(true, "create should not throw");
+ }
+
+ async function test_get_with_one_allowed_credential() {
+ let publicKey = {
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ allowCredentials: [validCred]
+ };
+
+ await navigator.credentials.get({publicKey});
+ ok(true, "get should not throw");
+ }
+
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_get_assertion.html b/dom/webauthn/tests/test_webauthn_get_assertion.html
new file mode 100644
index 0000000000..3de282a09a
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_get_assertion.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for GetAssertion for W3C Web Authentication</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <h1>Tests for GetAssertion for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+ let gAssertionChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(gAssertionChallenge);
+
+ let unknownCredType = {type: "Magic", id: base64ToBytes("AAA=")};
+ let unknownCred = {type: "public-key", id: base64ToBytes("AAA=")};
+ let validCred = null;
+
+ add_task(test_setup_valid_credential);
+ add_task(test_with_credential);
+ add_task(test_unexpected_option);
+ add_task(test_unexpected_option_with_credential);
+ add_task(test_unexpected_transport);
+ add_task(test_unknown_credential_type);
+ add_task(test_unknown_credential);
+ add_task(test_too_many_credentials);
+ add_task(test_unexpected_option_unknown_credential_type);
+ add_task(test_empty_credential_list);
+
+ function requestGetAssertion(params) {
+ return navigator.credentials.get(params);
+ }
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ function expectInvalidStateError(aResult) {
+ ok(aResult.toString().startsWith("InvalidStateError"), "Expecting a InvalidStateError, got " + aResult);
+ }
+
+ function expectTypeError(aResult) {
+ ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError, got " + aResult);
+ }
+
+ function expectSecurityError(aResult) {
+ ok(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError, got " + aResult);
+ }
+
+ function expectAbortError(aResult) {
+ is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError");
+ }
+
+ // Set up a valid credential
+ async function test_setup_valid_credential() {
+ let publicKey = {
+ rp: {id: document.domain, name: "none"},
+ user: {id: new Uint8Array(), name: "none", displayName: "none"},
+ challenge: crypto.getRandomValues(new Uint8Array(16)),
+ pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+ };
+
+ return navigator.credentials.create({publicKey})
+ .then(res => validCred = {type: "public-key", id: res.rawId} );
+ }
+
+ // Test with a valid credential
+ async function test_with_credential() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ allowCredentials: [validCred]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with an unexpected option. That won't stop anything, and we'll
+ // fail with NotAllowed just as if we had no valid credentials -- which
+ // we don't.
+ async function test_unexpected_option() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ unknownValue: "hi"
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with an unexpected option but a valid credential
+ async function test_unexpected_option_with_credential() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ unknownValue: "hi",
+ allowCredentials: [validCred]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with an unexpected transport on a valid credential
+ async function test_unexpected_transport() {
+ let cred = validCred;
+ cred.transports = ["unknown", "usb"];
+
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ unknownValue: "hi",
+ allowCredentials: [cred]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with an unknown credential type
+ async function test_unknown_credential_type() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ allowCredentials: [unknownCredType]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with an unknown credential
+ async function test_unknown_credential() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ allowCredentials: [unknownCred]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with too many credentials
+ async function test_too_many_credentials() {
+ let tooManyCredentials = Array(21).fill(validCred);
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ allowCredentials: tooManyCredentials,
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+
+ // Test with an unexpected option and an unknown credential type
+ async function test_unexpected_option_unknown_credential_type() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ unknownValue: "hi",
+ allowCredentials: [unknownCredType]
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with an empty credential list
+ async function test_empty_credential_list() {
+ let publicKey = {
+ challenge: gAssertionChallenge,
+ allowCredentials: []
+ };
+
+ await requestGetAssertion({publicKey})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_get_assertion_dead_object.html b/dom/webauthn/tests/test_webauthn_get_assertion_dead_object.html
new file mode 100644
index 0000000000..35892a60d1
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_get_assertion_dead_object.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for GetAssertion on dead object</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>Test for GetAssertion on dead object</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1483905">Mozilla Bug 1483905</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout(
+ "Due to the nature of this test, there's no way for the window we're opening to signal " +
+ "that it's done (the `document.writeln('')` is essential and basically clears any state " +
+ "we could use). So, we have to wait at least 15 seconds for the webauthn call to time out.");
+ SpecialPowers.pushPrefEnv({"set": [
+ ["security.webauth.webauthn_enable_softtoken", false],
+ ["security.webauth.webauthn_enable_usbtoken", true],
+ ]}).then(() => {
+ let win = window.open("https://example.com/tests/dom/webauthn/tests/get_assertion_dead_object.html");
+ setTimeout(() => {
+ win.close();
+ SimpleTest.finish();
+ }, 20000);
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
new file mode 100644
index 0000000000..15b83659d5
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for W3C Web Authentication isUserVerifyingPlatformAuthenticatorAvailable</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Test for W3C Web Authentication isUserVerifyingPlatformAuthenticatorAvailable</h1>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+add_task(async function test_uvpaa_with_no_authenticator() {
+ let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
+ ok(uvpaa === false, "Platform authenticator is not available");
+});
+
+add_task(async () => {
+ await addVirtualAuthenticator("ctap2_1", "usb");
+});
+
+add_task(async function test_uvpaa_with_usb_authenticator() {
+ let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
+ ok(uvpaa === false, "Platform authenticator is not available");
+});
+
+add_task(async () => {
+ await addVirtualAuthenticator("ctap2_1", "internal");
+});
+
+add_task(async function test_uvpaa_with_usb_and_platform_authenticator() {
+ let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
+ ok(uvpaa === true, "Platform authenticator is available");
+});
+
+
+</script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_loopback.html b/dom/webauthn/tests/test_webauthn_loopback.html
new file mode 100644
index 0000000000..70905739a0
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <script type="text/javascript" src="cbor.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+add_task(async function() {
+ // This test intentionally compares items to themselves.
+ /* eslint-disable no-self-compare */
+ await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_testing_allow_direct_attestation", true]]});
+ await addVirtualAuthenticator();
+ is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+ let credm = navigator.credentials;
+
+ let gCredentialChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(gCredentialChallenge);
+ let gAssertionChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(gAssertionChallenge);
+
+ await testMakeCredential();
+
+ async function decodeCreatedCredential(aCredInfo) {
+ /* PublicKeyCredential : Credential
+ - rawId: Key Handle buffer pulled from U2F Register() Response
+ - id: Key Handle buffer in base64url form, should == rawId
+ - type: Literal 'public-key'
+ - response : AuthenticatorAttestationResponse : AuthenticatorResponse
+ - attestationObject: CBOR object
+ - clientDataJSON: serialized JSON
+ */
+
+ is(aCredInfo.type, "public-key", "Credential type must be public-key")
+
+ ok(aCredInfo.rawId.byteLength > 0, "Key ID exists");
+ is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
+
+ ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
+ ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
+ ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
+ ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
+
+ let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
+ is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
+ is(clientData.origin, window.location.origin, "Origin is correct");
+ is(clientData.type, "webauthn.create", "Type is correct");
+
+ let attestationObj = await webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject);
+ // Make sure the RP ID hash matches what we calculate.
+ let calculatedRpIdHash = await crypto.subtle.digest("SHA-256", string2buffer(document.domain));
+ let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
+ let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(attestationObj.authDataObj.rpIdHash));
+ is(calcHashStr, providedHashStr, "Calculated RP ID hash must match what the browser derived.");
+ ok(true, attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT));
+ ok(attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT) == (flag_TUP | flag_AT),
+ "User presence and Attestation Object flags must be set");
+ aCredInfo.clientDataObj = clientData;
+ aCredInfo.publicKeyHandle = attestationObj.authDataObj.publicKeyHandle;
+ aCredInfo.attestationObject = attestationObj.authDataObj.attestationAuthData;
+ return aCredInfo;
+ }
+
+ async function checkAssertionAndSigValid(aPublicKey, aAssertion) {
+ /* PublicKeyCredential : Credential
+ - rawId: ID of Credential from AllowList that succeeded
+ - id: Key Handle buffer in base64url form, should == rawId
+ - type: Literal 'public-key'
+ - response : AuthenticatorAssertionResponse : AuthenticatorResponse
+ - clientDataJSON: serialized JSON
+ - authenticatorData: RP ID Hash || U2F Sign() Response
+ - signature: U2F Sign() Response
+ */
+ is(aAssertion.type, "public-key", "Credential type must be public-key")
+ ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
+ is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
+ ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
+ ok(aAssertion.response.authenticatorData instanceof ArrayBuffer, "AuthenticatorAssertionResponse.AuthenticatorData is an ArrayBuffer");
+ ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
+ ok(aAssertion.response.signature instanceof ArrayBuffer, "AuthenticatorAssertionResponse.Signature is an ArrayBuffer");
+ ok(aAssertion.response.userHandle === null, "AuthenticatorAssertionResponse.UserHandle is null for u2f authenticators");
+
+ ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
+ let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
+ is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
+ is(clientData.origin, window.location.origin, "Origin is correct");
+ is(clientData.type, "webauthn.get", "Type is correct");
+
+ let attestation = await webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData);
+ ok(new Uint8Array(attestation.flags)[0] & flag_TUP == flag_TUP, "User presence flag must be set");
+ is(attestation.counter.byteLength, 4, "Counter must be 4 bytes");
+ let params = await deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, attestation);
+ console.log(params);
+ console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
+ console.log("ClientDataHash: ", hexEncode(params.challengeParam));
+ let signedData = await assembleSignedData(params.appParam, params.attestation.flags,
+ params.attestation.counter, params.challengeParam);
+ console.log(aPublicKey, signedData, aAssertion.response.signature);
+ return await verifySignature(aPublicKey, signedData, aAssertion.response.signature);
+ }
+
+ async function testMakeCredential() {
+ let rp = {id: document.domain, name: "none"};
+ let user = {id: new Uint8Array(), name: "none", displayName: "none"};
+ let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+ let makeCredentialOptions = {
+ rp,
+ user,
+ challenge: gCredentialChallenge,
+ pubKeyCredParams: [param],
+ attestation: "direct"
+ };
+ let credential = await credm.create({publicKey: makeCredentialOptions})
+ let decodedCredential = await decodeCreatedCredential(credential);
+ await testMakeDuplicate(decodedCredential);
+ }
+
+ async function testMakeDuplicate(aCredInfo) {
+ let rp = {id: document.domain, name: "none"};
+ let user = {id: new Uint8Array(), name: "none", displayName: "none"};
+ let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+ let makeCredentialOptions = {
+ rp,
+ user,
+ challenge: gCredentialChallenge,
+ pubKeyCredParams: [param],
+ excludeCredentials: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
+ transports: ["usb"]}]
+ };
+ await credm.create({publicKey: makeCredentialOptions})
+ .then(function() {
+ // We should have errored here!
+ ok(false, "The excludeList didn't stop a duplicate being created!");
+ }).catch((aReason) => {
+ ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason);
+ });
+ await testAssertion(aCredInfo);
+ }
+
+ async function testAssertion(aCredInfo) {
+ let newCredential = {
+ type: "public-key",
+ id: new Uint8Array(aCredInfo.rawId),
+ transports: ["usb"],
+ }
+
+ let publicKeyCredentialRequestOptions = {
+ challenge: gAssertionChallenge,
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: document.domain,
+ allowCredentials: [newCredential]
+ };
+ let assertion = await credm.get({publicKey: publicKeyCredentialRequestOptions});
+ let sigVerifyResult = await checkAssertionAndSigValid(aCredInfo.publicKeyHandle, assertion);
+ ok(sigVerifyResult, "Signing signature verified");
+ }
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_make_credential.html b/dom/webauthn/tests/test_webauthn_make_credential.html
new file mode 100644
index 0000000000..4812d0da0d
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_make_credential.html
@@ -0,0 +1,459 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for MakeCredential for W3C Web Authentication</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <h1>Test for MakeCredential for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+ let credm;
+ let gCredentialChallenge;
+ let rp;
+ let user;
+ let param;
+ let unsupportedParam;
+ let unknownParam;
+
+ // Setup test env
+ add_task(() => {
+ gCredentialChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(gCredentialChallenge);
+
+ rp = {id: document.domain, name: "none"};
+ user = {id: new Uint8Array(64), name: "none", displayName: "none"};
+ param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+ unsupportedParam = {type: "public-key", alg: cose_alg_ECDSA_w_SHA512};
+ unknownParam = {type: "SimplePassword", alg: "MaxLength=2"};
+ credm = navigator.credentials;
+ });
+ // Add tests
+ add_task(test_good_call);
+ add_task(test_empty_account);
+ add_task(test_without_rp_name);
+ add_task(test_without_user_id);
+ add_task(test_without_user_name);
+ add_task(test_without_user_displayname);
+ add_task(test_user_too_large);
+ add_task(test_empty_parameters);
+ add_task(test_without_parameters);
+ add_task(test_unsupported_parameter);
+ add_task(test_unsupported_but_one_param);
+ add_task(test_one_unknown_parameter);
+ add_task(test_one_unknown_one_unsupported_param);
+ add_task(test_one_of_each_parameters);
+ add_task(test_without_challenge);
+ add_task(test_invalid_challenge);
+ add_task(test_duplicate_pub_key);
+ add_task(test_invalid_rp_id);
+ add_task(test_invalid_rp_id_2);
+ add_task(test_missing_rp);
+ add_task(test_incorrect_user_id_type);
+ add_task(test_missing_user);
+ add_task(test_complete_account);
+ add_task(test_too_large_user_id);
+ add_task(test_excluding_unknown_transports);
+ add_task(test_unknown_attestation_type);
+ add_task(test_unknown_selection_criteria);
+ add_task(test_no_unexpected_extensions);
+ add_task(test_cred_props_with_rk_required);
+ add_task(test_cred_props_with_rk_discouraged);
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ return Promise.resolve();
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ return Promise.resolve();
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
+ return Promise.resolve();
+ }
+
+ function expectTypeError(aResult) {
+ ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
+ return Promise.resolve();
+ }
+
+ function expectNotSupportedError(aResult) {
+ ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError");
+ return Promise.resolve();
+ }
+
+ // Test basic good call
+ async function test_good_call() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test empty account
+ async function test_empty_account() {
+ let makeCredentialOptions = {
+ challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test without rp.name
+ async function test_without_rp_name() {
+ let rp1 = {id: document.domain};
+ let makeCredentialOptions = {
+ rp: rp1, user, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test without user.id
+ async function test_without_user_id() {
+ let user1 = {name: "none", displayName: "none"};
+ let makeCredentialOptions = {
+ rp, user: user1, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test without user.name
+ async function test_without_user_name() {
+ let user1 = {id: new Uint8Array(64), displayName: "none"};
+ let makeCredentialOptions = {
+ rp, user: user1, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test without user.displayName
+ async function test_without_user_displayname() {
+ let user1 = {id: new Uint8Array(64), name: "none"};
+ let makeCredentialOptions = {
+ rp, user: user1, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with a user handle that exceeds the max length
+ async function test_user_too_large() {
+ let user1 = {id: new Uint8Array(65), name: "none", displayName: "none"};
+ let makeCredentialOptions = {
+ rp, user: user1, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test without any parameters; this is acceptable meaning the RP ID is
+ // happy to accept either ECDSA-SHA256 or RSA-SHA256
+ async function test_empty_parameters() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: []
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test without a parameter array at all
+ async function test_without_parameters() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with an unsupported parameter
+ // The result depends on the tokens that are available, so we
+ // expect a NotAllowedError so as not to leak this.
+ async function test_unsupported_parameter() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [unsupportedParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with an unsupported parameter and a good one
+ async function test_unsupported_but_one_param() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param, unsupportedParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with one unknown (not "public-key") parameter.
+ async function test_one_unknown_parameter() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [unknownParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectNotSupportedError);
+ }
+
+ // Test with an unsupported parameter, and an unknown one
+ // The result depends on the tokens that are available, so we
+ // expect a NotAllowedError so as not to leak this.
+ async function test_one_unknown_one_unsupported_param() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge,
+ pubKeyCredParams: [unsupportedParam, unknownParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+
+ // Test with an unsupported parameter, an unknown one, and a good one. This
+ // should succeed, as the unsupported and unknown should be ignored.
+ async function test_one_of_each_parameters() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param, unsupportedParam, unknownParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test without a challenge
+ async function test_without_challenge() {
+ let makeCredentialOptions = {
+ rp, user, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with an invalid challenge
+ async function test_invalid_challenge() {
+ let makeCredentialOptions = {
+ rp, user, challenge: "begone, thou ill-fitting moist glove!",
+ pubKeyCredParams: [unsupportedParam]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with duplicate pubKeyCredParams
+ async function test_duplicate_pub_key() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param, param, param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with an RP ID that is not a valid domain string
+ async function test_invalid_rp_id() {
+ let rp1 = { id: document.domain + ":somejunk", name: "none"};
+ let makeCredentialOptions = {
+ rp: rp1, user, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(arrivingHereIsGood);
+ }
+
+ // Test with another RP ID that is not a valid domain string
+ async function test_invalid_rp_id_2() {
+ let rp1 = { id: document.domain + ":8888", name: "none"};
+ let makeCredentialOptions = {
+ rp: rp1, user, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(arrivingHereIsGood);
+ }
+
+ // Test with missing rp
+ async function test_missing_rp() {
+ let makeCredentialOptions = {
+ user, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with incorrect user ID type
+ async function test_incorrect_user_id_type() {
+ let invalidType = {id: "a string, which is not a buffer", name: "none", displayName: "none"};
+ let makeCredentialOptions = {
+ user: invalidType, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with missing user
+ async function test_missing_user() {
+ let makeCredentialOptions = {
+ rp, challenge: gCredentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test a complete account
+ async function test_complete_account() {
+ // the icon fields are deprecated, but including them should not cause an error
+ let completeRP = {id: document.domain, name: "Foxxy Name",
+ icon: "https://example.com/fox.svg"};
+ let completeUser = {id: string2buffer("foxes_are_the_best@example.com"),
+ name: "Fox F. Foxington",
+ icon: "https://example.com/fox.svg",
+ displayName: "Foxxy V"};
+ let makeCredentialOptions = {
+ rp: completeRP, user: completeUser, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ // Test with too-large user ID buffer
+ async function test_too_large_user_id() {
+ let hugeUser = {id: new Uint8Array(65),
+ name: "Fox F. Foxington",
+ displayName: "Foxxy V"};
+ let makeCredentialOptions = {
+ rp, user: hugeUser, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+
+ // Test with excluding unknown transports
+ async function test_excluding_unknown_transports() {
+ let completeRP = {id: document.domain, name: "Foxxy Name"};
+ let completeUser = {id: string2buffer("foxes_are_the_best@example.com"),
+ name: "Fox F. Foxington",
+ displayName: "Foxxy V"};
+ let excludedUnknownTransport = {type: "public-key",
+ id: string2buffer("123"),
+ transports: ["unknown", "usb"]};
+ let makeCredentialOptions = {
+ rp: completeRP, user: completeUser, challenge: gCredentialChallenge,
+ pubKeyCredParams: [param], excludeCredentials: [excludedUnknownTransport]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ async function test_unknown_attestation_type() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param],
+ attestation: "unknown"
+ };
+ return credm.create({publicKey: makeCredentialOptions })
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ async function test_unknown_selection_criteria() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param],
+ authenticatorSelection: {
+ userVerificationRequirement: "unknown UV requirement",
+ authenticatorAttachment: "unknown authenticator attachment type"
+ },
+ };
+ return credm.create({publicKey: makeCredentialOptions })
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+
+ async function test_no_unexpected_extensions() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param],
+ };
+ let cred = await credm.create({publicKey: makeCredentialOptions}).catch(arrivingHereIsBad);
+ let extensionResults = cred.getClientExtensionResults();
+ is(extensionResults.credProps, undefined, "no credProps output");
+ }
+
+ async function test_cred_props_with_rk_required() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param],
+ authenticatorSelection: {
+ authenticatorAttachment: "cross-platform",
+ residentKey: "required",
+ },
+ extensions: { credProps: true }
+ };
+ let cred = await credm.create({publicKey: makeCredentialOptions}).catch(arrivingHereIsBad);
+ let extensionResults = cred.getClientExtensionResults();
+ is(extensionResults.credProps?.rk, true, "rk is true");
+ }
+
+ async function test_cred_props_with_rk_discouraged() {
+ let makeCredentialOptions = {
+ rp, user, challenge: gCredentialChallenge, pubKeyCredParams: [param],
+ authenticatorSelection: {
+ authenticatorAttachment: "cross-platform",
+ residentKey: "discouraged",
+ },
+ extensions: { credProps: true }
+ };
+ let cred = await credm.create({publicKey: makeCredentialOptions}).catch(arrivingHereIsBad);
+ let extensionResults = cred.getClientExtensionResults();
+ is(extensionResults.credProps?.rk, false, "rk is false");
+ }
+
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_no_token.html b/dom/webauthn/tests/test_webauthn_no_token.html
new file mode 100644
index 0000000000..28fe6e43eb
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_no_token.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for W3C Web Authentication with no token</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Test for W3C Web Authentication with no token</h1>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+let credm;
+let credentialChallenge;
+let assertionChallenge;
+let credentialId;
+
+// Setup test env
+add_task(async () => {
+ credentialChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(credentialChallenge);
+ assertionChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(assertionChallenge);
+ credentialId = new Uint8Array(128);
+ window.crypto.getRandomValues(credentialId);
+ credm = navigator.credentials;
+ // Turn off all tokens. This should result in "not allowed" failures
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["security.webauth.webauthn_enable_softtoken", false],
+ ["security.webauth.webauthn_enable_usbtoken", false],
+ ]});
+});
+
+add_task(async function test_no_token_make_credential() {
+ let rp = {id: document.domain, name: "none"};
+ let user = {id: new Uint8Array(), name: "none", displayName: "none"};
+ let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+ let makeCredentialOptions = {
+ rp, user, challenge: credentialChallenge, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(function(aResult) {
+ ok(false, "Should have failed.");
+ })
+ .catch(function(aReason) {
+ ok(aReason.toString().startsWith("NotAllowedError"), aReason);
+ });
+});
+
+add_task(async function test_no_token_get_assertion() {
+ let newCredential = {
+ type: "public-key",
+ id: credentialId,
+ transports: ["usb"],
+ }
+ let publicKeyCredentialRequestOptions = {
+ challenge: assertionChallenge,
+ timeout: 5000, // the minimum timeout is actually 15 seconds
+ rpId: document.domain,
+ allowCredentials: [newCredential]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(function(aResult) {
+ ok(false, "Should have failed.");
+ })
+ .catch(function(aReason) {
+ ok(aReason.toString().startsWith("NotAllowedError"), aReason);
+ })
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_sameorigin.html b/dom/webauthn/tests/test_webauthn_sameorigin.html
new file mode 100644
index 0000000000..61a6430e87
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -0,0 +1,317 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for MakeCredential for W3C Web Authentication</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <h1>Test Same Origin Policy for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+ let credm;
+ let chall;
+ let user;
+ let param;
+ let gTrackedCredential;
+ add_task(() => {
+ credm = navigator.credentials;
+
+ chall = new Uint8Array(16);
+ window.crypto.getRandomValues(chall);
+
+ user = {id: new Uint8Array(16), name: "none", displayName: "none"};
+ param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+ gTrackedCredential = {};
+ });
+
+ add_task(test_basic_good);
+ add_task(test_rp_id_unset);
+ add_task(test_rp_name_unset);
+ add_task(test_origin_with_optional_fields);
+ add_task(test_blank_rp_id);
+ add_task(test_subdomain);
+ add_task(test_same_origin);
+ add_task(test_etld);
+ add_task(test_different_domain_same_tld);
+ add_task(test_assertion_basic_good);
+ add_task(test_assertion_rp_id_unset);
+ add_task(test_assertion_origin_with_optional_fields);
+ add_task(test_assertion_blank_rp_id);
+ add_task(test_assertion_subdomain);
+ add_task(test_assertion_same_origin);
+ add_task(test_assertion_etld);
+ add_task(test_assertion_different_domain_same_tld);
+ add_task(test_basic_good_with_origin);
+ add_task(test_assertion_basic_good_with_origin);
+ add_task(test_assertion_invalid_rp_id);
+ add_task(test_assertion_another_invalid_rp_id);
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectSecurityError(aResult) {
+ ok(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError");
+ }
+
+ function expectTypeError(aResult) {
+ ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
+ }
+
+ function keepThisPublicKeyCredential(aIdentifier) {
+ return function(aPublicKeyCredential) {
+ gTrackedCredential[aIdentifier] = {
+ type: "public-key",
+ id: new Uint8Array(aPublicKeyCredential.rawId),
+ transports: [ "usb" ],
+ }
+ return Promise.resolve(aPublicKeyCredential);
+ }
+ }
+
+ function test_basic_good() {
+ // Test basic good call
+ let rp = {id: document.domain, name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(keepThisPublicKeyCredential("basic"))
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_rp_id_unset() {
+ // Test rp.id being unset
+ let makeCredentialOptions = {
+ rp: {name: "none"}, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_rp_name_unset() {
+ // Test rp.name being unset
+ let makeCredentialOptions = {
+ rp: {id: document.domain}, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectTypeError);
+ }
+ function test_origin_with_optional_fields() {
+ // Test this origin with optional fields
+ let rp = {id: "user:pass@" + document.domain + ":8888", name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_blank_rp_id() {
+ // Test blank rp.id
+ let rp = {id: "", name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_subdomain() {
+ // Test subdomain of this origin
+ let rp = {id: "subdomain." + document.domain, name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_same_origin() {
+ // Test the same origin
+ let rp = {id: "example.com", name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_etld() {
+ // Test the eTLD
+ let rp = {id: "com", name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_different_domain_same_tld() {
+ // Test a different domain within the same TLD
+ let rp = {id: "alt.test", name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_basic_good() {
+ // Test basic good call
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: document.domain,
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_assertion_rp_id_unset() {
+ // Test rpId being unset
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_assertion_origin_with_optional_fields() {
+ // Test this origin with optional fields
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "user:pass@" + document.origin + ":8888",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_blank_rp_id() {
+ // Test blank rpId
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_subdomain() {
+ // Test subdomain of this origin
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "subdomain." + document.domain,
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_same_origin() {
+ // Test the same origin
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "example.com",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+ }
+ function test_assertion_etld() {
+ // Test the eTLD
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "com",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_different_domain_same_tld() {
+ // Test a different domain within the same TLD
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: "alt.test",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_basic_good_with_origin() {
+ // Test basic good Create call but using an origin (Bug 1380421)
+ let rp = {id: window.origin, name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ return credm.create({publicKey: makeCredentialOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_basic_good_with_origin() {
+ // Test basic good Get call but using an origin (Bug 1380421)
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: window.origin,
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(expectSecurityError);
+ }
+ function test_assertion_invalid_rp_id() {
+ // Test with an rpId that is not a valid domain string
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: document.domain + ":somejunk",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(arrivingHereIsGood);
+ }
+ function test_assertion_another_invalid_rp_id() {
+ // Test with another rpId that is not a valid domain string
+ let publicKeyCredentialRequestOptions = {
+ challenge: chall,
+ rpId: document.domain + ":8888",
+ allowCredentials: [gTrackedCredential.basic]
+ };
+ return credm.get({publicKey: publicKeyCredentialRequestOptions})
+ .then(arrivingHereIsBad)
+ .catch(arrivingHereIsGood);
+ }
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_sameoriginwithancestors.html b/dom/webauthn/tests/test_webauthn_sameoriginwithancestors.html
new file mode 100644
index 0000000000..276ac16bdd
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_sameoriginwithancestors.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Test for MakeCredential for W3C Web Authentication (sameOriginWithAncestors = false)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="u2futil.js"></script>
+ <script type="text/javascript" src="pkijs/common.js"></script>
+ <script type="text/javascript" src="pkijs/asn1.js"></script>
+ <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+ <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+ <h1>Test Same Origin Policy for W3C Web Authentication (sameOriginWithAncestors = false)</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1694639">Mozilla Bug 1694639</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ var gTrackedCredential = {};
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult == "NotAllowedError", "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ function keepThisPublicKeyCredential(aIdentifier) {
+ return function(aPublicKeyCredential) {
+ gTrackedCredential[aIdentifier] = {
+ type: "public-key",
+ id: new Uint8Array(aPublicKeyCredential.rawId),
+ transports: [ "usb" ],
+ }
+ return Promise.resolve(aPublicKeyCredential);
+ }
+ }
+
+ add_task(async function runTests() {
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://example.org";
+ document.body.appendChild(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve, {once: true}));
+
+ is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+ let credm = navigator.credentials;
+
+ let chall = new Uint8Array(16);
+ window.crypto.getRandomValues(chall);
+
+ let user = {id: new Uint8Array(16), name: "none", displayName: "none"};
+ let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
+
+ let rp = {id: document.domain, name: "none"};
+ let makeCredentialOptions = {
+ rp, user, challenge: chall, pubKeyCredParams: [param]
+ };
+ await credm.create({publicKey: makeCredentialOptions})
+ .then(keepThisPublicKeyCredential("basic"))
+ .catch(arrivingHereIsBad);
+
+ var testFuncs = [
+ function (args) {
+ // Test create when sameOriginWithAncestors = false
+ let credentialOptions = {
+ rp: args.rp, user: args.user, challenge: args.challenge, pubKeyCredParams: [args.param]
+ };
+ return this.content.window.navigator.credentials.create({publicKey: credentialOptions})
+ .catch(e => Promise.reject(e.name));
+ },
+ function (args) {
+ // Test get when sameOriginWithAncestors = false
+ let publicKeyCredentialRequestOptions = {
+ challenge: args.challenge,
+ rpId: args.rp.id,
+ allowCredentials: [args.trackedCredential.basic]
+ };
+ return this.content.window.navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions})
+ .catch(e => Promise.reject(e.name));
+ },
+ ];
+
+ let args = { user, param, rp, challenge: chall, trackedCredential: gTrackedCredential }
+ for(let func of testFuncs) {
+ await SpecialPowers.spawn(iframe, [args], func)
+ .then(arrivingHereIsBad)
+ .catch(expectNotAllowedError);
+ }
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_serialization.html b/dom/webauthn/tests/test_webauthn_serialization.html
new file mode 100644
index 0000000000..492749434f
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_serialization.html
@@ -0,0 +1,304 @@
+<!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", credProps: true, hmacCreateSecret: true, minPinLength: 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.credProps, true, "extensions.credProps should be true");
+ is(creationOptions.extensions.hmacCreateSecret, true, "extensions.hmacCreateSecret should be true");
+ is(creationOptions.extensions.minPinLength, true, "extensions.minPinLength 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}],
+ authenticatorSelection: { residentKey: "discouraged" },
+ extensions: { credProps: true }
+ };
+ let registrationResponse = await navigator.credentials.create({publicKey});
+ let registrationResponseJSON = registrationResponse.toJSON();
+ is(Object.keys(registrationResponseJSON).length, 6, "registrationResponseJSON should have 6 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], "internal", "registrationResponseJSON.response.transports[0] should be internal");
+ is(registrationResponseJSON.authenticatorAttachment, "platform", "registrationResponseJSON.authenticatorAttachment should be platform");
+ is(registrationResponseJSON.clientExtensionResults?.credProps?.rk, false, "registrationResponseJSON.clientExtensionResults.credProps.rk should be false");
+ 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, 6, "assertionResponseJSON should have 6 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(assertionResponseJSON.authenticatorAttachment, "platform", "assertionResponseJSON.authenticatorAttachment should be platform");
+ 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>
diff --git a/dom/webauthn/tests/test_webauthn_store_credential.html b/dom/webauthn/tests/test_webauthn_store_credential.html
new file mode 100644
index 0000000000..92782acc47
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_store_credential.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for Store for W3C Web Authentication</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 for Store for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ add_task(async () => {
+ await addVirtualAuthenticator();
+ });
+
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+ isnot(navigator.credentials.store, undefined, "CredentialManagement store API endpoint must exist");
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ return Promise.resolve();
+ }
+
+ function expectNotSupportedError(aResult) {
+ ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError, received: " + aResult);
+ return Promise.resolve();
+ }
+
+ add_task(async function test_store_credential() {
+ let credentialChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(credentialChallenge);
+
+ let rp = {id: document.domain, name: "none"};
+ let user = {id: new Uint8Array(64), name: "none", displayName: "none"};
+ let params = [ {type: "public-key", alg: "es256"}, {type: "public-key", alg: -7} ]
+
+ let makeCredentialOptions = {
+ rp, user, challenge: credentialChallenge, pubKeyCredParams: params
+ };
+
+ let credential = await navigator.credentials.create({publicKey: makeCredentialOptions})
+ .catch(arrivingHereIsBad);
+
+ await navigator.credentials.store(credential)
+ .then(arrivingHereIsBad)
+ .catch(expectNotSupportedError);
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/test_webauthn_webdriver_virtual_authenticator.html b/dom/webauthn/tests/test_webauthn_webdriver_virtual_authenticator.html
new file mode 100644
index 0000000000..cb00f80e88
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_webdriver_virtual_authenticator.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for WebDriver Virtual Authenticator Extension for W3C Web Authentication</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 for WebDriver Virtual Authenticator Extension for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460986">Mozilla Bug 1460986</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ function arrivingHereIsGood(aResult) {
+ ok(true, "Good result! Received a: " + aResult);
+ }
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ }
+
+ function expectNotAllowedError(aResult) {
+ ok(aResult == "NotAllowedError", "Expecting a NotAllowedError, got " + aResult);
+ }
+
+ function getAssertion(id) {
+ let chall = new Uint8Array(16);
+ crypto.getRandomValues(chall);
+
+ let options = {
+ challenge: chall,
+ allowCredentials: [ { type: "public-key", id } ],
+ };
+
+ return navigator.credentials.get({publicKey: options});
+ }
+
+ add_task(async function test_add_and_remove_credential() {
+ let authenticatorId = await addVirtualAuthenticator();
+ let credIdB64 = await addCredential(authenticatorId, document.domain);
+ let credId = base64ToBytesUrlSafe(credIdB64);
+
+ await getAssertion(credId)
+ .then(arrivingHereIsGood)
+ .catch(arrivingHereIsBad);
+
+ await removeCredential(authenticatorId, credIdB64);
+ await getAssertion(credId)
+ .then(arrivingHereIsBad)
+ .catch(e => expectNotAllowedError(e.name));
+ });
+ </script>
+
+</body>
+</html>
diff --git a/dom/webauthn/tests/u2futil.js b/dom/webauthn/tests/u2futil.js
new file mode 100644
index 0000000000..e8026e8e59
--- /dev/null
+++ b/dom/webauthn/tests/u2futil.js
@@ -0,0 +1,511 @@
+// Used by local_addTest() / local_completeTest()
+var _countCompletions = 0;
+var _expectedCompletions = 0;
+
+const flag_TUP = 0x01;
+const flag_UV = 0x04;
+const flag_AT = 0x40;
+
+const cose_kty = 1;
+const cose_kty_ec2 = 2;
+const cose_alg = 3;
+const cose_alg_ECDSA_w_SHA256 = -7;
+const cose_alg_ECDSA_w_SHA512 = -36;
+const cose_crv = -1;
+const cose_crv_P256 = 1;
+const cose_crv_x = -2;
+const cose_crv_y = -3;
+
+var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+async function addVirtualAuthenticator(
+ protocol = "ctap2_1",
+ transport = "internal",
+ hasResidentKey = true,
+ hasUserVerification = true,
+ isUserConsenting = true,
+ isUserVerified = true
+) {
+ let id = await SpecialPowers.spawnChrome(
+ [
+ protocol,
+ transport,
+ hasResidentKey,
+ hasUserVerification,
+ isUserConsenting,
+ isUserVerified,
+ ],
+ (
+ protocol,
+ transport,
+ hasResidentKey,
+ hasUserVerification,
+ isUserConsenting,
+ isUserVerified
+ ) => {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+ let id = webauthnService.addVirtualAuthenticator(
+ protocol,
+ transport,
+ hasResidentKey,
+ hasUserVerification,
+ isUserConsenting,
+ isUserVerified
+ );
+ return id;
+ }
+ );
+
+ SimpleTest.registerCleanupFunction(async () => {
+ await SpecialPowers.spawnChrome([id], id => {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+ webauthnService.removeVirtualAuthenticator(id);
+ });
+ });
+
+ return id;
+}
+
+function handleEventMessage(event) {
+ if ("test" in event.data) {
+ let summary = event.data.test + ": " + event.data.msg;
+ log(event.data.status + ": " + summary);
+ ok(event.data.status, summary);
+ } else if ("done" in event.data) {
+ SimpleTest.finish();
+ } else {
+ ok(false, "Unexpected message in the test harness: " + event.data);
+ }
+}
+
+function log(msg) {
+ console.log(msg);
+ let logBox = document.getElementById("log");
+ if (logBox) {
+ logBox.textContent += "\n" + msg;
+ }
+}
+
+function local_is(value, expected, message) {
+ if (value === expected) {
+ local_ok(true, message);
+ } else {
+ local_ok(false, message + " unexpectedly: " + value + " !== " + expected);
+ }
+}
+
+function local_isnot(value, expected, message) {
+ if (value !== expected) {
+ local_ok(true, message);
+ } else {
+ local_ok(false, message + " unexpectedly: " + value + " === " + expected);
+ }
+}
+
+function local_ok(expression, message) {
+ let body = { test: this.location.pathname, status: expression, msg: message };
+ parent.postMessage(body, "http://mochi.test:8888");
+}
+
+function local_doesThrow(fn, name) {
+ let gotException = false;
+ try {
+ fn();
+ } catch (ex) {
+ gotException = true;
+ }
+ local_ok(gotException, name);
+}
+
+function local_expectThisManyTests(count) {
+ if (_expectedCompletions > 0) {
+ local_ok(
+ false,
+ "Error: local_expectThisManyTests should only be called once."
+ );
+ }
+ _expectedCompletions = count;
+}
+
+function local_completeTest() {
+ _countCompletions += 1;
+ if (_countCompletions == _expectedCompletions) {
+ log("All tests completed.");
+ local_finished();
+ }
+ if (_countCompletions > _expectedCompletions) {
+ local_ok(
+ false,
+ "Error: local_completeTest called more than local_addTest."
+ );
+ }
+}
+
+function local_finished() {
+ parent.postMessage({ done: true }, "http://mochi.test:8888");
+}
+
+function string2buffer(str) {
+ return new Uint8Array(str.length).map((x, i) => str.charCodeAt(i));
+}
+
+function buffer2string(buf) {
+ let str = "";
+ if (!(buf.constructor === Uint8Array)) {
+ buf = new Uint8Array(buf);
+ }
+ buf.map(function (x) {
+ return (str += String.fromCharCode(x));
+ });
+ return str;
+}
+
+function bytesToBase64(u8a) {
+ let CHUNK_SZ = 0x8000;
+ let c = [];
+ let array = new Uint8Array(u8a);
+ for (let i = 0; i < array.length; i += CHUNK_SZ) {
+ c.push(String.fromCharCode.apply(null, array.subarray(i, i + CHUNK_SZ)));
+ }
+ return window.btoa(c.join(""));
+}
+
+function base64ToBytes(b64encoded) {
+ return new Uint8Array(
+ window
+ .atob(b64encoded)
+ .split("")
+ .map(function (c) {
+ return c.charCodeAt(0);
+ })
+ );
+}
+
+function bytesToBase64UrlSafe(buf) {
+ return bytesToBase64(buf)
+ .replace(/\+/g, "-")
+ .replace(/\//g, "_")
+ .replace(/=/g, "");
+}
+
+function base64ToBytesUrlSafe(str) {
+ if (str.length % 4 == 1) {
+ throw "Improper b64 string";
+ }
+
+ var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
+ while (b64.length % 4 != 0) {
+ b64 += "=";
+ }
+ return base64ToBytes(b64);
+}
+
+function hexEncode(buf) {
+ return Array.from(buf)
+ .map(x => ("0" + x.toString(16)).substr(-2))
+ .join("");
+}
+
+function hexDecode(str) {
+ return new Uint8Array(str.match(/../g).map(x => parseInt(x, 16)));
+}
+
+function hasOnlyKeys(obj, ...keys) {
+ let okeys = new Set(Object.keys(obj));
+ return keys.length == okeys.size && keys.every(k => okeys.has(k));
+}
+
+function webAuthnDecodeCBORAttestation(aCborAttBuf) {
+ let attObj = CBOR.decode(aCborAttBuf);
+ console.log(":: Attestation CBOR Object ::");
+ if (!hasOnlyKeys(attObj, "authData", "fmt", "attStmt")) {
+ return Promise.reject("Invalid CBOR Attestation Object");
+ }
+ if (attObj.fmt == "fido-u2f" && !hasOnlyKeys(attObj.attStmt, "sig", "x5c")) {
+ return Promise.reject("Invalid CBOR Attestation Statement");
+ }
+ if (
+ attObj.fmt == "packed" &&
+ !(
+ hasOnlyKeys(attObj.attStmt, "alg", "sig") ||
+ hasOnlyKeys(attObj.attStmt, "alg", "sig", "x5c")
+ )
+ ) {
+ return Promise.reject("Invalid CBOR Attestation Statement");
+ }
+ if (attObj.fmt == "none" && Object.keys(attObj.attStmt).length) {
+ return Promise.reject("Invalid CBOR Attestation Statement");
+ }
+
+ return webAuthnDecodeAuthDataArray(new Uint8Array(attObj.authData)).then(
+ function (aAuthDataObj) {
+ attObj.authDataObj = aAuthDataObj;
+ return Promise.resolve(attObj);
+ }
+ );
+}
+
+function webAuthnDecodeAuthDataArray(aAuthData) {
+ let rpIdHash = aAuthData.slice(0, 32);
+ let flags = aAuthData.slice(32, 33);
+ let counter = aAuthData.slice(33, 37);
+
+ console.log(":: Authenticator Data ::");
+ console.log("RP ID Hash: " + hexEncode(rpIdHash));
+ console.log("Counter: " + hexEncode(counter) + " Flags: " + flags);
+
+ if ((flags & flag_AT) == 0x00) {
+ // No Attestation Data, so we're done.
+ return Promise.resolve({
+ rpIdHash,
+ flags,
+ counter,
+ });
+ }
+
+ if (aAuthData.length < 38) {
+ return Promise.reject(
+ "Authenticator Data flag was set, but not enough data passed in!"
+ );
+ }
+
+ let attData = {};
+ attData.aaguid = aAuthData.slice(37, 53);
+ attData.credIdLen = (aAuthData[53] << 8) + aAuthData[54];
+ attData.credId = aAuthData.slice(55, 55 + attData.credIdLen);
+
+ console.log(":: Authenticator Data ::");
+ console.log("AAGUID: " + hexEncode(attData.aaguid));
+
+ let cborPubKey = aAuthData.slice(55 + attData.credIdLen);
+ var pubkeyObj = CBOR.decode(cborPubKey.buffer);
+ if (
+ !(
+ cose_kty in pubkeyObj &&
+ cose_alg in pubkeyObj &&
+ cose_crv in pubkeyObj &&
+ cose_crv_x in pubkeyObj &&
+ cose_crv_y in pubkeyObj
+ )
+ ) {
+ throw "Invalid CBOR Public Key Object";
+ }
+ if (pubkeyObj[cose_kty] != cose_kty_ec2) {
+ throw "Unexpected key type";
+ }
+ if (pubkeyObj[cose_alg] != cose_alg_ECDSA_w_SHA256) {
+ throw "Unexpected public key algorithm";
+ }
+ if (pubkeyObj[cose_crv] != cose_crv_P256) {
+ throw "Unexpected curve";
+ }
+
+ let pubKeyBytes = assemblePublicKeyBytesData(
+ pubkeyObj[cose_crv_x],
+ pubkeyObj[cose_crv_y]
+ );
+ console.log(":: CBOR Public Key Object Data ::");
+ console.log("kty: " + pubkeyObj[cose_kty] + " (EC2)");
+ console.log("alg: " + pubkeyObj[cose_alg] + " (ES256)");
+ console.log("crv: " + pubkeyObj[cose_crv] + " (P256)");
+ console.log("X: " + pubkeyObj[cose_crv_x]);
+ console.log("Y: " + pubkeyObj[cose_crv_y]);
+ console.log("Uncompressed (hex): " + hexEncode(pubKeyBytes));
+
+ return importPublicKey(pubKeyBytes).then(function (aKeyHandle) {
+ return Promise.resolve({
+ rpIdHash,
+ flags,
+ counter,
+ attestationAuthData: attData,
+ publicKeyBytes: pubKeyBytes,
+ publicKeyHandle: aKeyHandle,
+ });
+ });
+}
+
+function importPublicKey(keyBytes) {
+ if (keyBytes[0] != 0x04 || keyBytes.byteLength != 65) {
+ throw "Bad public key octet string";
+ }
+ var jwk = {
+ kty: "EC",
+ crv: "P-256",
+ x: bytesToBase64UrlSafe(keyBytes.slice(1, 33)),
+ y: bytesToBase64UrlSafe(keyBytes.slice(33)),
+ };
+ return crypto.subtle.importKey(
+ "jwk",
+ jwk,
+ { name: "ECDSA", namedCurve: "P-256" },
+ true,
+ ["verify"]
+ );
+}
+
+function deriveAppAndChallengeParam(appId, clientData, attestation) {
+ var appIdBuf = string2buffer(appId);
+ return Promise.all([
+ crypto.subtle.digest("SHA-256", appIdBuf),
+ crypto.subtle.digest("SHA-256", clientData),
+ ]).then(function (digests) {
+ return {
+ appParam: new Uint8Array(digests[0]),
+ challengeParam: new Uint8Array(digests[1]),
+ attestation,
+ };
+ });
+}
+
+function assemblePublicKeyBytesData(xCoord, yCoord) {
+ // Produce an uncompressed EC key point. These start with 0x04, and then
+ // two 32-byte numbers denoting X and Y.
+ if (xCoord.length != 32 || yCoord.length != 32) {
+ throw "Coordinates must be 32 bytes long";
+ }
+ let keyBytes = new Uint8Array(65);
+ keyBytes[0] = 0x04;
+ xCoord.map((x, i) => (keyBytes[1 + i] = x));
+ yCoord.map((x, i) => (keyBytes[33 + i] = x));
+ return keyBytes;
+}
+
+function assembleSignedData(appParam, flags, counter, challengeParam) {
+ let signedData = new Uint8Array(32 + 1 + 4 + 32);
+ new Uint8Array(appParam).map((x, i) => (signedData[0 + i] = x));
+ signedData[32] = new Uint8Array(flags)[0];
+ new Uint8Array(counter).map((x, i) => (signedData[33 + i] = x));
+ new Uint8Array(challengeParam).map((x, i) => (signedData[37 + i] = x));
+ return signedData;
+}
+
+function assembleRegistrationSignedData(
+ appParam,
+ challengeParam,
+ keyHandle,
+ pubKey
+) {
+ let signedData = new Uint8Array(1 + 32 + 32 + keyHandle.length + 65);
+ signedData[0] = 0x00;
+ new Uint8Array(appParam).map((x, i) => (signedData[1 + i] = x));
+ new Uint8Array(challengeParam).map((x, i) => (signedData[33 + i] = x));
+ new Uint8Array(keyHandle).map((x, i) => (signedData[65 + i] = x));
+ new Uint8Array(pubKey).map(
+ (x, i) => (signedData[65 + keyHandle.length + i] = x)
+ );
+ return signedData;
+}
+
+function sanitizeSigArray(arr) {
+ // ECDSA signature fields into WebCrypto must be exactly 32 bytes long, so
+ // this method strips leading padding bytes, if added, and also appends
+ // padding zeros, if needed.
+ if (arr.length > 32) {
+ arr = arr.slice(arr.length - 32);
+ }
+ let ret = new Uint8Array(32);
+ ret.set(arr, ret.length - arr.length);
+ return ret;
+}
+
+function verifySignature(key, data, derSig) {
+ if (derSig.byteLength < 68) {
+ return Promise.reject(
+ "Invalid signature (length=" +
+ derSig.byteLength +
+ "): " +
+ hexEncode(new Uint8Array(derSig))
+ );
+ }
+
+ // Copy signature data into the current context.
+ let derSigCopy = new ArrayBuffer(derSig.byteLength);
+ new Uint8Array(derSigCopy).set(new Uint8Array(derSig));
+
+ let sigAsn1 = org.pkijs.fromBER(derSigCopy);
+
+ // pkijs.asn1 seems to erroneously set an error code when calling some
+ // internal function. The test suite doesn't like dangling globals.
+ delete window.error;
+
+ let sigR = new Uint8Array(
+ sigAsn1.result.value_block.value[0].value_block.value_hex
+ );
+ let sigS = new Uint8Array(
+ sigAsn1.result.value_block.value[1].value_block.value_hex
+ );
+
+ // The resulting R and S values from the ASN.1 Sequence must be fit into 32
+ // bytes. Sometimes they have leading zeros, sometimes they're too short, it
+ // all depends on what lib generated the signature.
+ let R = sanitizeSigArray(sigR);
+ let S = sanitizeSigArray(sigS);
+
+ console.log("Verifying these bytes: " + bytesToBase64UrlSafe(data));
+
+ let sigData = new Uint8Array(R.length + S.length);
+ sigData.set(R);
+ sigData.set(S, R.length);
+
+ let alg = { name: "ECDSA", hash: "SHA-256" };
+ return crypto.subtle.verify(alg, key, sigData, data);
+}
+
+async function addCredential(authenticatorId, rpId) {
+ let keyPair = await crypto.subtle.generateKey(
+ {
+ name: "ECDSA",
+ namedCurve: "P-256",
+ },
+ true,
+ ["sign"]
+ );
+
+ let credId = new Uint8Array(32);
+ crypto.getRandomValues(credId);
+ credId = bytesToBase64UrlSafe(credId);
+
+ let privateKey = await crypto.subtle
+ .exportKey("pkcs8", keyPair.privateKey)
+ .then(privateKey => bytesToBase64UrlSafe(privateKey));
+
+ await SpecialPowers.spawnChrome(
+ [authenticatorId, credId, rpId, privateKey],
+ (authenticatorId, credId, rpId, privateKey) => {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+
+ webauthnService.addCredential(
+ authenticatorId,
+ credId,
+ true, // resident key
+ rpId,
+ privateKey,
+ "VGVzdCBVc2Vy", // "Test User"
+ 0 // sign count
+ );
+ }
+ );
+
+ return credId;
+}
+
+async function removeCredential(authenticatorId, credId) {
+ await SpecialPowers.spawnChrome(
+ [authenticatorId, credId],
+ (authenticatorId, credId) => {
+ let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
+ Ci.nsIWebAuthnService
+ );
+
+ webauthnService.removeCredential(authenticatorId, credId);
+ }
+ );
+}
diff --git a/dom/webauthn/winwebauthn/.gitignore b/dom/webauthn/winwebauthn/.gitignore
new file mode 100644
index 0000000000..3e759b75bf
--- /dev/null
+++ b/dom/webauthn/winwebauthn/.gitignore
@@ -0,0 +1,330 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
diff --git a/dom/webauthn/winwebauthn/LICENSE b/dom/webauthn/winwebauthn/LICENSE
new file mode 100644
index 0000000000..21071075c2
--- /dev/null
+++ b/dom/webauthn/winwebauthn/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation. All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/dom/webauthn/winwebauthn/README.md b/dom/webauthn/winwebauthn/README.md
new file mode 100644
index 0000000000..7aaa69079b
--- /dev/null
+++ b/dom/webauthn/winwebauthn/README.md
@@ -0,0 +1,26 @@
+# Description
+
+This project includes Win32 headers for communicating to Windows Hello and external secruity keys as part of WebAuthn and CTAP specification.
+
+For more details about the standards, please follow these links:
+* WebAuthn: https://w3c.github.io/webauthn/
+* CTAP: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html
+
+
+# Having Issues?
+If you have any issues in adopting these APIs or need some clarification, please contact fido-dev@microsoft.com.
+
+
+# Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you to agree to a
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
+the rights to use your contribution. For details, visit https://cla.microsoft.com.
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
+a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
+provided by the bot. You will only need to do this once across all repos using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/dom/webauthn/winwebauthn/webauthn.h b/dom/webauthn/winwebauthn/webauthn.h
new file mode 100644
index 0000000000..71fbc4b8b1
--- /dev/null
+++ b/dom/webauthn/winwebauthn/webauthn.h
@@ -0,0 +1,1145 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#ifndef __WEBAUTHN_H_
+#define __WEBAUTHN_H_
+
+#pragma once
+
+#include <winapifamily.h>
+
+#pragma region Desktop Family or OneCore Family
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+#ifndef INITGUID
+#define INITGUID
+#include <guiddef.h>
+#undef INITGUID
+#else
+#include <guiddef.h>
+#endif
+
+//+------------------------------------------------------------------------------------------
+// API Version Information.
+// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs
+// and features for their usage.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_API_VERSION_1 1
+// WEBAUTHN_API_VERSION_1 : Baseline Version
+// Data Structures and their sub versions:
+// - WEBAUTHN_RP_ENTITY_INFORMATION : 1
+// - WEBAUTHN_USER_ENTITY_INFORMATION : 1
+// - WEBAUTHN_CLIENT_DATA : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable
+// - WEBAUTHN_CREDENTIAL : 1
+// - WEBAUTHN_CREDENTIALS : Not Applicable
+// - WEBAUTHN_CREDENTIAL_EX : 1
+// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable
+// - WEBAUTHN_EXTENSION : Not Applicable
+// - WEBAUTHN_EXTENSIONS : Not Applicable
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4
+// - WEBAUTHN_COMMON_ATTESTATION : 1
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3
+// - WEBAUTHN_ASSERTION : 1
+// Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// APIs:
+// - WebAuthNGetApiVersionNumber
+// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable
+// - WebAuthNAuthenticatorMakeCredential
+// - WebAuthNAuthenticatorGetAssertion
+// - WebAuthNFreeCredentialAttestation
+// - WebAuthNFreeAssertion
+// - WebAuthNGetCancellationId
+// - WebAuthNCancelCurrentOperation
+// - WebAuthNGetErrorName
+// - WebAuthNGetW3CExceptionDOMError
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_USB
+// - WEBAUTHN_CTAP_TRANSPORT_NFC
+// - WEBAUTHN_CTAP_TRANSPORT_BLE
+// - WEBAUTHN_CTAP_TRANSPORT_INTERNAL
+
+#define WEBAUTHN_API_VERSION_2 2
+// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+//
+
+#define WEBAUTHN_API_VERSION_3 3
+// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4
+// - WEBAUTHN_ASSERTION : 2
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+//
+
+#define WEBAUTHN_API_VERSION_4 4
+// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6
+// - WEBAUTHN_ASSERTION : 3
+// - WEBAUTHN_CREDENTIAL_DETAILS : 1
+// APIs:
+// - WebAuthNGetPlatformCredentialList
+// - WebAuthNFreePlatformCredentialList
+// - WebAuthNDeletePlatformCredential
+//
+
+#define WEBAUTHN_API_VERSION_5 5
+// WEBAUTHN_API_VERSION_5 : Delta From WEBAUTHN_API_VERSION_4
+// Data Structures and their sub versions:
+// - WEBAUTHN_CREDENTIAL_DETAILS : 2
+// Extension Changes:
+// - Enabled LARGE_BLOB Support
+//
+
+#define WEBAUTHN_API_VERSION_6 6
+// WEBAUTHN_API_VERSION_6 : Delta From WEBAUTHN_API_VERSION_5
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 6
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 5
+// - WEBAUTHN_ASSERTION : 4
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_HYBRID
+
+#define WEBAUTHN_API_VERSION_7 7
+// WEBAUTHN_API_VERSION_7 : Delta From WEBAUTHN_API_VERSION_6
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 7
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 7
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 6
+// - WEBAUTHN_ASSERTION : 5
+
+#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_7
+
+//+------------------------------------------------------------------------------------------
+// Information about an RP Entity
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_RP_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the RP. This field is required.
+ PCWSTR pwszId;
+
+ // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site".
+ // This field is required.
+ PCWSTR pwszName;
+
+ // Optional URL pointing to RP's logo.
+ PCWSTR pwszIcon;
+} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION;
+typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about an User Entity
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_MAX_USER_ID_LENGTH 64
+
+#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_USER_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the User. This field is required.
+ DWORD cbId;
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Contains a detailed name for this account, such as "john.p.smith@example.com".
+ PCWSTR pwszName;
+
+ // Optional URL that can be used to retrieve an image containing the user's current avatar,
+ // or a data URI that contains the image data.
+ PCWSTR pwszIcon;
+
+ // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith".
+ PCWSTR pwszDisplayName;
+} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION;
+typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about client data.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512"
+
+#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CLIENT_DATA {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Size of the pbClientDataJSON field.
+ DWORD cbClientDataJSON;
+ // UTF-8 encoded JSON serialization of the client data.
+ _Field_size_bytes_(cbClientDataJSON)
+ PBYTE pbClientDataJSON;
+
+ // Hash algorithm ID used to hash the pbClientDataJSON field.
+ LPCWSTR pwszHashAlgId;
+} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA;
+typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential parameters.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key"
+
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36
+
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259
+
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39
+
+#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Well-known credential type specifying a credential to create.
+ LPCWSTR pwszCredentialType;
+
+ // Well-known COSE algorithm specifying the algorithm to use for the credential.
+ LONG lAlg;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
+ DWORD cCredentialParameters;
+ _Field_size_(cCredentialParameters)
+ PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential.
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL;
+typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL;
+
+typedef struct _WEBAUTHN_CREDENTIALS {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL pCredentials;
+} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS;
+typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential with extra information, such as, dwTransports
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001
+#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002
+#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004
+#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008
+#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010
+#define WEBAUTHN_CTAP_TRANSPORT_HYBRID 0x00000020
+#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000003F
+
+#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL_EX {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+
+ // Transports. 0 implies no transport restrictions.
+ DWORD dwTransports;
+} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX;
+typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential list with extra information
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CREDENTIAL_LIST {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL_EX *ppCredentials;
+} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST;
+typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST;
+
+//+------------------------------------------------------------------------------------------
+// Information about linked devices
+//-------------------------------------------------------------------------------------------
+
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 1
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_CURRENT_VERSION CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1
+
+typedef struct _CTAPCBOR_HYBRID_STORAGE_LINKED_DATA
+{
+ // Version
+ DWORD dwVersion;
+
+ // Contact Id
+ DWORD cbContactId;
+ _Field_size_bytes_(cbContactId)
+ PBYTE pbContactId;
+
+ // Link Id
+ DWORD cbLinkId;
+ _Field_size_bytes_(cbLinkId)
+ PBYTE pbLinkId;
+
+ // Link secret
+ DWORD cbLinkSecret;
+ _Field_size_bytes_(cbLinkSecret)
+ PBYTE pbLinkSecret;
+
+ // Authenticator Public Key
+ DWORD cbPublicKey;
+ _Field_size_bytes_(cbPublicKey)
+ PBYTE pbPublicKey;
+
+ // Authenticator Name
+ PCWSTR pwszAuthenticatorName;
+
+ // Tunnel server domain
+ WORD wEncodedTunnelServerDomain;
+} CTAPCBOR_HYBRID_STORAGE_LINKED_DATA, *PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+typedef const CTAPCBOR_HYBRID_STORAGE_LINKED_DATA *PCCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Credential Information for WebAuthNGetPlatformCredentialList API
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbCredentialID.
+ DWORD cbCredentialID;
+ _Field_size_bytes_(cbCredentialID)
+ PBYTE pbCredentialID;
+
+ // RP Info
+ PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation;
+
+ // User Info
+ PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation;
+
+ // Removable or not.
+ BOOL bRemovable;
+
+ //
+ // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+ //
+
+ // Backed Up or not.
+ BOOL bBackedUp;
+} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS;
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST {
+ DWORD cCredentialDetails;
+ _Field_size_(cCredentialDetails)
+ PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails;
+} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1
+
+typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Optional.
+ LPCWSTR pwszRpId;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+
+//+------------------------------------------------------------------------------------------
+// PRF values.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32
+
+// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension.
+// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value)
+//
+// Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
+// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case,
+// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size.
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT {
+ // Size of pbFirst.
+ DWORD cbFirst;
+ _Field_size_bytes_(cbFirst)
+ PBYTE pbFirst; // Required
+
+ // Size of pbSecond.
+ DWORD cbSecond;
+ _Field_size_bytes_(cbSecond)
+ PBYTE pbSecond;
+} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT {
+ // Size of pbCredID.
+ DWORD cbCredID;
+ _Field_size_bytes_(cbCredID)
+ PBYTE pbCredID; // Required
+
+ // PRF Values for above credential
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required
+} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES {
+ PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt;
+
+ DWORD cCredWithHmacSecretSaltList;
+ _Field_size_(cCredWithHmacSecretSaltList)
+ PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList;
+} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+
+//+------------------------------------------------------------------------------------------
+// Hmac-Secret extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credential
+// was successfully created with HMAC_SECRET.
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credProtect extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_USER_VERIFICATION_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3
+
+typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN {
+ // One of the above WEBAUTHN_USER_VERIFICATION_* values
+ DWORD dwCredProtect;
+ // Set the following to TRUE to require authenticator support for the credProtect extension
+ BOOL bRequireCredProtect;
+} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN.
+// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values
+// if credential was successfully created with CRED_PROTECT.
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credBlob extension
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION {
+ // Size of pbCredBlob.
+ DWORD cbCredBlob;
+ _Field_size_bytes_(cbCredBlob)
+ PBYTE pbCredBlob;
+} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION;
+typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct
+// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the credBlob.
+// - cbExtension must contain the sizeof(BOOL).
+// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator
+// returns the credBlob in the signed extensions
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+
+//+------------------------------------------------------------------------------------------
+// minPinLength extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// Information about Extensions.
+//-------------------------------------------------------------------------------------------
+typedef struct _WEBAUTHN_EXTENSION {
+ LPCWSTR pwszExtensionIdentifier;
+ DWORD cbExtension;
+ PVOID pvExtension;
+} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION;
+typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION;
+
+typedef struct _WEBAUTHN_EXTENSIONS {
+ DWORD cExtensions;
+ _Field_size_(cExtensions)
+ PWEBAUTHN_EXTENSION pExtensions;
+} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS;
+typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS;
+
+//+------------------------------------------------------------------------------------------
+// Options.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3
+
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3
+
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3
+
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2
+
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2
+
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Credentials used for exclusion.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // Optional. Require key to be resident or not. Defaulting to FALSE.
+ BOOL bRequireResidentKey;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Attestation Conveyance Preference.
+ DWORD dwAttestationConveyancePreference;
+
+ // Reserved for future Use
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3
+ //
+
+ // Exclude Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4
+ //
+
+ // Enterprise Attestation
+ DWORD dwEnterpriseAttestation;
+
+ // Large Blob Support: none, required or preferred
+ //
+ // NTE_INVALID_PARAMETER when large blob required or preferred and
+ // bRequireResidentKey isn't set to TRUE
+ DWORD dwLargeBlobSupport;
+
+ // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE,
+ // overrides the above bRequireResidentKey.
+ BOOL bPreferResidentKey;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5
+ //
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6
+ //
+
+ // Enable PRF
+ BOOL bEnablePrf;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3
+
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+
+/*
+ Information about flags.
+*/
+
+#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Allowed Credentials List.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Flags
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2
+ //
+
+ // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased.
+ PCWSTR pwszU2fAppId;
+
+ // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of
+ // PCWSTR pwszRpId;
+ BOOL *pbU2fAppId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4
+ //
+
+ // Allow Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5
+ //
+
+ DWORD dwCredLargeBlobOperation;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
+ //
+
+ // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec.
+ PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport.
+ BOOL bAutoFill;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+
+
+//+------------------------------------------------------------------------------------------
+// Attestation Info.
+//
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_ATTESTATION_DECODE_NONE 0
+#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1
+// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types
+// L"packed"
+// L"fido-u2f"
+
+#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0"
+
+typedef struct _WEBAUTHN_X5C {
+ // Length of X.509 encoded certificate
+ DWORD cbData;
+ // X.509 encoded certificate bytes
+ _Field_size_bytes_(cbData)
+ PBYTE pbData;
+} WEBAUTHN_X5C, *PWEBAUTHN_X5C;
+
+// Supports either Self or Full Basic Attestation
+
+// Note, new fields will be added to the following data structure to
+// support additional attestation format types, such as, TPM.
+// When fields are added, the dwVersion will be incremented.
+//
+// Therefore, your code must make the following check:
+// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)"
+
+#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COMMON_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Hash and Padding Algorithm
+ //
+ // The following won't be set for "fido-u2f" which assumes "ES256".
+ PCWSTR pwszAlg;
+ LONG lAlg; // COSE algorithm
+
+ // Signature that was generated for this attestation.
+ DWORD cbSignature;
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation.
+ // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate.
+ DWORD cX5c;
+ _Field_size_(cX5c)
+ PWEBAUTHN_X5C pX5c;
+
+ // Following are also set for tpm
+ PCWSTR pwszVer; // L"2.0"
+ DWORD cbCertInfo;
+ _Field_size_bytes_(cbCertInfo)
+ PBYTE pbCertInfo;
+ DWORD cbPubArea;
+ _Field_size_bytes_(cbPubArea)
+ PBYTE pbPubArea;
+} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION;
+typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION;
+
+#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed"
+#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f"
+#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm"
+#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none"
+
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 5
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 6
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+
+typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Attestation format type
+ PCWSTR pwszFormatType;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this credential.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of CBOR encoded attestation information
+ //0 => encoded as CBOR null value.
+ DWORD cbAttestation;
+ //Encoded CBOR attestation information
+ _Field_size_bytes_(cbAttestation)
+ PBYTE pbAttestation;
+
+ DWORD dwAttestationDecodeType;
+ // Following depends on the dwAttestationDecodeType
+ // WEBAUTHN_ATTESTATION_DECODE_NONE
+ // NULL - not able to decode the CBOR attestation information
+ // WEBAUTHN_ATTESTATION_DECODE_COMMON
+ // PWEBAUTHN_COMMON_ATTESTATION;
+ PVOID pvAttestationDecode;
+
+ // The CBOR encoded Attestation Object to be returned to the RP.
+ DWORD cbAttestationObject;
+ _Field_size_bytes_(cbAttestationObject)
+ PBYTE pbAttestationObject;
+
+ // The CredentialId bytes extracted from the Authenticator Data.
+ // Used by Edge to return to the RP.
+ DWORD cbCredentialId;
+ _Field_size_bytes_(cbCredentialId)
+ PBYTE pbCredentialId;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
+ //
+
+ BOOL bEpAtt;
+ BOOL bLargeBlobSupported;
+ BOOL bResidentKey;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
+ //
+
+ BOOL bPrfEnabled;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION;
+typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION;
+
+
+//+------------------------------------------------------------------------------------------
+// authenticatorGetAssertion output.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9
+
+#define WEBAUTHN_ASSERTION_VERSION_1 1
+#define WEBAUTHN_ASSERTION_VERSION_2 2
+#define WEBAUTHN_ASSERTION_VERSION_3 3
+#define WEBAUTHN_ASSERTION_VERSION_4 4
+#define WEBAUTHN_ASSERTION_VERSION_5 5
+#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_5
+
+typedef struct _WEBAUTHN_ASSERTION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this assertion.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of pbSignature.
+ DWORD cbSignature;
+ // Signature that was generated for this assertion.
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Credential that was used for this assertion.
+ WEBAUTHN_CREDENTIAL Credential;
+
+ // Size of User Id
+ DWORD cbUserId;
+ // UserId
+ _Field_size_bytes_(cbUserId)
+ PBYTE pbUserId;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ DWORD dwCredLargeBlobStatus;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3
+ //
+
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_4
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_5
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION;
+typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION;
+
+//+------------------------------------------------------------------------------------------
+// APIs.
+//-------------------------------------------------------------------------------------------
+
+DWORD
+WINAPI
+WebAuthNGetApiVersionNumber();
+
+HRESULT
+WINAPI
+WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(
+ _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorMakeCredential(
+ _In_ HWND hWnd,
+ _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation,
+ _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation,
+ _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorGetAssertion(
+ _In_ HWND hWnd,
+ _In_ LPCWSTR pwszRpId,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion);
+
+void
+WINAPI
+WebAuthNFreeCredentialAttestation(
+ _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation);
+
+void
+WINAPI
+WebAuthNFreeAssertion(
+ _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion);
+
+HRESULT
+WINAPI
+WebAuthNGetCancellationId(
+ _Out_ GUID* pCancellationId);
+
+HRESULT
+WINAPI
+WebAuthNCancelCurrentOperation(
+ _In_ const GUID* pCancellationId);
+
+// Returns NTE_NOT_FOUND when credentials are not found.
+HRESULT
+WINAPI
+WebAuthNGetPlatformCredentialList(
+ _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList);
+
+void
+WINAPI
+WebAuthNFreePlatformCredentialList(
+ _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList);
+
+HRESULT
+WINAPI
+WebAuthNDeletePlatformCredential(
+ _In_ DWORD cbCredentialId,
+ _In_reads_bytes_(cbCredentialId) const BYTE *pbCredentialId
+ );
+
+//
+// Returns the following Error Names:
+// L"Success" - S_OK
+// L"InvalidStateError" - NTE_EXISTS
+// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
+// NTE_NOT_SUPPORTED,
+// NTE_TOKEN_KEYSET_STORAGE_FULL
+// L"NotSupportedError" - NTE_INVALID_PARAMETER
+// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND,
+// NTE_NOT_FOUND,
+// HRESULT_FROM_WIN32(ERROR_CANCELLED),
+// NTE_USER_CANCELLED,
+// HRESULT_FROM_WIN32(ERROR_TIMEOUT)
+// L"UnknownError" - All other hr values
+//
+PCWSTR
+WINAPI
+WebAuthNGetErrorName(
+ _In_ HRESULT hr);
+
+HRESULT
+WINAPI
+WebAuthNGetW3CExceptionDOMError(
+ _In_ HRESULT hr);
+
+
+#ifdef __cplusplus
+} // Balance extern "C" above
+#endif
+
+#endif // WINAPI_FAMILY_PARTITION
+#pragma endregion
+
+#endif // __WEBAUTHN_H_