summaryrefslogtreecommitdiffstats
path: root/security/manager/pki
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/pki')
-rw-r--r--security/manager/pki/components.conf21
-rw-r--r--security/manager/pki/moz.build34
-rw-r--r--security/manager/pki/nsIASN1Tree.idl24
-rw-r--r--security/manager/pki/nsNSSDialogHelper.cpp45
-rw-r--r--security/manager/pki/nsNSSDialogHelper.h39
-rw-r--r--security/manager/pki/nsNSSDialogs.cpp314
-rw-r--r--security/manager/pki/nsNSSDialogs.h40
-rw-r--r--security/manager/pki/resources/content/certManager.css34
-rw-r--r--security/manager/pki/resources/content/certManager.js769
-rw-r--r--security/manager/pki/resources/content/certManager.xhtml330
-rw-r--r--security/manager/pki/resources/content/changepassword.js212
-rw-r--r--security/manager/pki/resources/content/changepassword.xhtml77
-rw-r--r--security/manager/pki/resources/content/clientauthask.js178
-rw-r--r--security/manager/pki/resources/content/clientauthask.xhtml55
-rw-r--r--security/manager/pki/resources/content/deletecert.css19
-rw-r--r--security/manager/pki/resources/content/deletecert.js121
-rw-r--r--security/manager/pki/resources/content/deletecert.xhtml33
-rw-r--r--security/manager/pki/resources/content/device_manager.js433
-rw-r--r--security/manager/pki/resources/content/device_manager.xhtml113
-rw-r--r--security/manager/pki/resources/content/downloadcert.js83
-rw-r--r--security/manager/pki/resources/content/downloadcert.xhtml66
-rw-r--r--security/manager/pki/resources/content/editcacert.js52
-rw-r--r--security/manager/pki/resources/content/editcacert.xhtml35
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.css35
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.js334
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.xhtml88
-rw-r--r--security/manager/pki/resources/content/load_device.js75
-rw-r--r--security/manager/pki/resources/content/load_device.xhtml55
-rw-r--r--security/manager/pki/resources/content/pippki.js300
-rw-r--r--security/manager/pki/resources/content/resetpassword.js28
-rw-r--r--security/manager/pki/resources/content/resetpassword.xhtml49
-rw-r--r--security/manager/pki/resources/content/setp12password.js127
-rw-r--r--security/manager/pki/resources/content/setp12password.xhtml56
-rw-r--r--security/manager/pki/resources/jar.mn32
-rw-r--r--security/manager/pki/resources/moz.build7
35 files changed, 4313 insertions, 0 deletions
diff --git a/security/manager/pki/components.conf b/security/manager/pki/components.conf
new file mode 100644
index 0000000000..e381806925
--- /dev/null
+++ b/security/manager/pki/components.conf
@@ -0,0 +1,21 @@
+# -*- 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': '{518e071f-1dd2-11b2-937e-c45f14def778}',
+ 'contract_ids': [
+ '@mozilla.org/nsCertificateDialogs;1',
+ '@mozilla.org/nsClientAuthDialogs;1',
+ '@mozilla.org/nsGeneratingKeypairInfoDialogs;1',
+ '@mozilla.org/nsTokenDialogs;1',
+ '@mozilla.org/nsTokenPasswordDialogs;1',
+ ],
+ 'type': 'nsNSSDialogs',
+ 'headers': ['/security/manager/pki/nsNSSDialogs.h'],
+ 'init_method': 'Init',
+ },
+]
diff --git a/security/manager/pki/moz.build b/security/manager/pki/moz.build
new file mode 100644
index 0000000000..5f0bf95738
--- /dev/null
+++ b/security/manager/pki/moz.build
@@ -0,0 +1,34 @@
+# -*- 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/.
+
+DIRS += ["resources"]
+
+UNIFIED_SOURCES += [
+ "nsNSSDialogHelper.cpp",
+ "nsNSSDialogs.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+LOCAL_INCLUDES += [
+ "!/dist/public/nss",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += [
+ "-Wextra",
+ # -Wextra enables this warning, but it's too noisy to be useful.
+ "-Wno-missing-field-initializers",
+ ]
+
+ # Gecko headers aren't warning-free enough for us to enable these warnings.
+ CXXFLAGS += [
+ "-Wno-unused-parameter",
+ ]
diff --git a/security/manager/pki/nsIASN1Tree.idl b/security/manager/pki/nsIASN1Tree.idl
new file mode 100644
index 0000000000..b44362e5b2
--- /dev/null
+++ b/security/manager/pki/nsIASN1Tree.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-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/. */
+
+#include "nsISupports.idl"
+#include "nsITreeView.idl"
+#include "nsIX509Cert.idl"
+
+[scriptable, uuid(de142307-7b88-4e0a-b232-250f310e25d8)]
+interface nsIASN1Tree : nsITreeView {
+ [must_use]
+ void loadASN1Structure(in nsIASN1Object asn1Object);
+
+ [must_use]
+ AString getDisplayData(in unsigned long index);
+};
+
+%{C++
+
+#define NS_ASN1TREE_CONTRACTID "@mozilla.org/security/nsASN1Tree;1"
+
+%}
diff --git a/security/manager/pki/nsNSSDialogHelper.cpp b/security/manager/pki/nsNSSDialogHelper.cpp
new file mode 100644
index 0000000000..18691aa6e9
--- /dev/null
+++ b/security/manager/pki/nsNSSDialogHelper.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-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/. */
+
+#include "nsNSSDialogHelper.h"
+
+#include "mozIDOMWindow.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsCOMPtr.h"
+#include "nsIWindowWatcher.h"
+#include "nsServiceManagerUtils.h"
+
+static const char kOpenDialogParam[] = "centerscreen,chrome,modal,titlebar";
+static const char kOpenWindowParam[] = "centerscreen,chrome,titlebar";
+
+nsresult nsNSSDialogHelper::openDialog(mozIDOMWindowProxy* window,
+ const char* url, nsISupports* params,
+ bool modal) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> windowWatcher =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<mozIDOMWindowProxy> parent = window;
+
+ if (!parent) {
+ windowWatcher->GetActiveWindow(getter_AddRefs(parent));
+ }
+
+ // We're loading XUL into this window, and it's happening on behalf of the
+ // system, not on behalf of content. Make sure the initial about:blank window
+ // gets a system principal, otherwise we'll bork when trying to wrap the
+ // nsIKeyGenThread |arguments| property into the unprivileged scoope.
+ MOZ_ASSERT(!strncmp("chrome://", url, strlen("chrome://")));
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = windowWatcher->OpenWindow(
+ parent, nsDependentCString(url), "_blank"_ns,
+ nsDependentCString(modal ? kOpenDialogParam : kOpenWindowParam), params,
+ getter_AddRefs(newWindow));
+ return rv;
+}
diff --git a/security/manager/pki/nsNSSDialogHelper.h b/security/manager/pki/nsNSSDialogHelper.h
new file mode 100644
index 0000000000..a7f3d5e474
--- /dev/null
+++ b/security/manager/pki/nsNSSDialogHelper.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-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/. */
+
+#ifndef nsNSSDialogHelper_h
+#define nsNSSDialogHelper_h
+
+#include "nsError.h"
+
+class mozIDOMWindowProxy;
+class nsISupports;
+
+/**
+ * Helper class that uses the window watcher service to open a standard dialog,
+ * with or without a parent context.
+ */
+class nsNSSDialogHelper {
+ public:
+ /**
+ * Opens a XUL dialog.
+ *
+ * @param window
+ * Parent window of the dialog, or nullptr to signal no parent.
+ * @param url
+ * URL to the XUL dialog.
+ * @param params
+ * Parameters to pass to the dialog. Same semantics as the
+ * nsIWindowWatcher.openWindow() |aArguments| parameter.
+ * @param modal
+ * true if the dialog should be modal, false otherwise.
+ * @return The result of opening the dialog.
+ */
+ static nsresult openDialog(mozIDOMWindowProxy* window, const char* url,
+ nsISupports* params, bool modal = true);
+};
+
+#endif // nsNSSDialogHelper_h
diff --git a/security/manager/pki/nsNSSDialogs.cpp b/security/manager/pki/nsNSSDialogs.cpp
new file mode 100644
index 0000000000..b1c1a939d5
--- /dev/null
+++ b/security/manager/pki/nsNSSDialogs.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-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/. */
+
+/*
+ * Dialog services for PIP.
+ */
+
+#include "nsNSSDialogs.h"
+
+#include "mozIDOMWindow.h"
+#include "nsArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsEmbedCID.h"
+#include "nsHashPropertyBag.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPK11Token.h"
+#include "nsIPromptService.h"
+#include "nsIWindowWatcher.h"
+#include "nsIX509CertDB.h"
+#include "nsIX509Cert.h"
+#include "nsNSSDialogHelper.h"
+#include "nsPromiseFlatString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsVariant.h"
+
+#define PIPSTRING_BUNDLE_URL "chrome://pippki/locale/pippki.properties"
+
+nsNSSDialogs::nsNSSDialogs() = default;
+
+nsNSSDialogs::~nsNSSDialogs() = default;
+
+NS_IMPL_ISUPPORTS(nsNSSDialogs, nsITokenPasswordDialogs, nsICertificateDialogs,
+ nsIClientAuthDialogs)
+
+nsresult nsNSSDialogs::Init() {
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> service =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = service->CreateBundle(PIPSTRING_BUNDLE_URL,
+ getter_AddRefs(mPIPStringBundle));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNSSDialogs::SetPassword(nsIInterfaceRequestor* ctx, nsIPK11Token* token,
+ /*out*/ bool* canceled) {
+ // |ctx| is allowed to be null.
+ NS_ENSURE_ARG(canceled);
+
+ *canceled = false;
+
+ // Get the parent window for the dialog
+ nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
+
+ nsCOMPtr<nsIDialogParamBlock> block =
+ do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
+ if (!block) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMutableArray> objects = nsArrayBase::Create();
+ if (!objects) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = objects->AppendElement(token);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = block->SetObjects(objects);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = nsNSSDialogHelper::openDialog(
+ parent, "chrome://pippki/content/changepassword.xhtml", block);
+
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t status;
+
+ rv = block->GetInt(1, &status);
+ if (NS_FAILED(rv)) return rv;
+
+ *canceled = (status == 0);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNSSDialogs::ConfirmDownloadCACert(nsIInterfaceRequestor* ctx,
+ nsIX509Cert* cert,
+ /*out*/ uint32_t* trust,
+ /*out*/ bool* importConfirmed) {
+ // |ctx| is allowed to be null.
+ NS_ENSURE_ARG(cert);
+ NS_ENSURE_ARG(trust);
+ NS_ENSURE_ARG(importConfirmed);
+
+ nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
+ if (!argArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = argArray->AppendElement(cert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
+ rv = argArray->AppendElement(retVals);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Get the parent window for the dialog
+ nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
+ rv = nsNSSDialogHelper::openDialog(
+ parent, "chrome://pippki/content/downloadcert.xhtml", argArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = retVals->GetPropertyAsBool(u"importConfirmed"_ns, importConfirmed);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *trust = nsIX509CertDB::UNTRUSTED;
+ if (!*importConfirmed) {
+ return NS_OK;
+ }
+
+ bool trustForSSL = false;
+ rv = retVals->GetPropertyAsBool(u"trustForSSL"_ns, &trustForSSL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ bool trustForEmail = false;
+ rv = retVals->GetPropertyAsBool(u"trustForEmail"_ns, &trustForEmail);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *trust |= trustForSSL ? nsIX509CertDB::TRUSTED_SSL : 0;
+ *trust |= trustForEmail ? nsIX509CertDB::TRUSTED_EMAIL : 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSDialogs::ChooseCertificate(const nsACString& hostname, int32_t port,
+ const nsACString& organization,
+ const nsACString& issuerOrg, nsIArray* certList,
+ /*out*/ uint32_t* selectedIndex,
+ /*out*/ bool* rememberClientAuthCertificate,
+ /*out*/ bool* certificateChosen) {
+ NS_ENSURE_ARG_POINTER(certList);
+ NS_ENSURE_ARG_POINTER(selectedIndex);
+ NS_ENSURE_ARG_POINTER(rememberClientAuthCertificate);
+ NS_ENSURE_ARG_POINTER(certificateChosen);
+
+ *certificateChosen = false;
+ *rememberClientAuthCertificate = false;
+
+ nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
+ if (!argArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWritableVariant> hostnameVariant = new nsVariant();
+ nsresult rv = hostnameVariant->SetAsAUTF8String(hostname);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = argArray->AppendElement(hostnameVariant);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWritableVariant> organizationVariant = new nsVariant();
+ rv = organizationVariant->SetAsAUTF8String(organization);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = argArray->AppendElement(organizationVariant);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWritableVariant> issuerOrgVariant = new nsVariant();
+ rv = issuerOrgVariant->SetAsAUTF8String(issuerOrg);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = argArray->AppendElement(issuerOrgVariant);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWritableVariant> portVariant = new nsVariant();
+ rv = portVariant->SetAsInt32(port);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = argArray->AppendElement(portVariant);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = argArray->AppendElement(certList);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
+ rv = argArray->AppendElement(retVals);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = nsNSSDialogHelper::openDialog(
+ nullptr, "chrome://pippki/content/clientauthask.xhtml", argArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = retVals->GetPropertyAsBool(u"rememberSelection"_ns,
+ rememberClientAuthCertificate);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = retVals->GetPropertyAsBool(u"certChosen"_ns, certificateChosen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*certificateChosen) {
+ rv = retVals->GetPropertyAsUint32(u"selectedIndex"_ns, selectedIndex);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSDialogs::SetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
+ /*out*/ nsAString& password,
+ /*out*/ bool* confirmedPassword) {
+ // |ctx| is allowed to be null.
+ NS_ENSURE_ARG(confirmedPassword);
+
+ // Get the parent window for the dialog
+ nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
+ nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
+ nsresult rv = nsNSSDialogHelper::openDialog(
+ parent, "chrome://pippki/content/setp12password.xhtml", retVals);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = retVals->GetPropertyAsBool(u"confirmedPassword"_ns, confirmedPassword);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!*confirmedPassword) {
+ return NS_OK;
+ }
+
+ return retVals->GetPropertyAsAString(u"password"_ns, password);
+}
+
+NS_IMETHODIMP
+nsNSSDialogs::GetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
+ nsAString& _password, bool* _retval) {
+ *_retval = false;
+
+ nsCOMPtr<nsIPromptService> promptSvc(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID));
+ if (!promptSvc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString msg;
+ nsresult rv =
+ mPIPStringBundle->GetStringFromName("getPKCS12FilePasswordMessage", msg);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Get the parent window for the dialog
+ nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
+ char16_t* pwTemp = nullptr;
+ rv = promptSvc->PromptPassword(parent, nullptr, msg.get(), &pwTemp, _retval);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*_retval) {
+ _password.Assign(pwTemp);
+ free(pwTemp);
+ }
+
+ return NS_OK;
+}
diff --git a/security/manager/pki/nsNSSDialogs.h b/security/manager/pki/nsNSSDialogs.h
new file mode 100644
index 0000000000..3c687ac667
--- /dev/null
+++ b/security/manager/pki/nsNSSDialogs.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-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/. */
+
+#ifndef nsNSSDialogs_h
+#define nsNSSDialogs_h
+
+#include "nsCOMPtr.h"
+#include "nsICertificateDialogs.h"
+#include "nsIClientAuthDialogs.h"
+#include "nsIStringBundle.h"
+#include "nsITokenPasswordDialogs.h"
+
+#define NS_NSSDIALOGS_CID \
+ { \
+ 0x518e071f, 0x1dd2, 0x11b2, { \
+ 0x93, 0x7e, 0xc4, 0x5f, 0x14, 0xde, 0xf7, 0x78 \
+ } \
+ }
+
+class nsNSSDialogs : public nsICertificateDialogs,
+ public nsIClientAuthDialogs,
+ public nsITokenPasswordDialogs {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITOKENPASSWORDDIALOGS
+ NS_DECL_NSICERTIFICATEDIALOGS
+ NS_DECL_NSICLIENTAUTHDIALOGS
+ nsNSSDialogs();
+
+ nsresult Init();
+
+ protected:
+ virtual ~nsNSSDialogs();
+ nsCOMPtr<nsIStringBundle> mPIPStringBundle;
+};
+
+#endif // nsNSSDialogs_h
diff --git a/security/manager/pki/resources/content/certManager.css b/security/manager/pki/resources/content/certManager.css
new file mode 100644
index 0000000000..9259472771
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.css
@@ -0,0 +1,34 @@
+/* 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/. */
+
+/* Good enough support for equalsize=always for the cert manager use cases.
+ * You probably shouldn't use this as-is elsewhere, this selector is somewhat
+ * slow, it relies on stuff having display: flex, and you probably can use
+ * something simpler if you need this */
+[equalsize="always"] > * {
+ flex: 1;
+ contain: inline-size;
+}
+
+treecol {
+ flex: 1 auto;
+ width: 0; /* Don't let intrinsic sizes affect our minimum size. */
+}
+
+#certmanager {
+ /* This prevents horizontal scrollbars due to <tree> and non-XUL layout
+ * interactions */
+ padding: 0;
+}
+
+/* This matches the <tree> height from dialog.css */
+richlistbox {
+ min-height: 15em;
+ contain: size;
+}
+
+richlistbox,
+richlistitem {
+ min-height: 30px;
+}
diff --git a/security/manager/pki/resources/content/certManager.js b/security/manager/pki/resources/content/certManager.js
new file mode 100644
index 0000000000..00bb29f307
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.js
@@ -0,0 +1,769 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+var key;
+
+var certdialogs = Cc["@mozilla.org/nsCertificateDialogs;1"].getService(
+ Ci.nsICertificateDialogs
+);
+
+/**
+ * List of certs currently selected in the active tab.
+ *
+ * @type {nsIX509Cert[]}
+ */
+var selected_certs = [];
+var selected_tree_items = [];
+var selected_index = [];
+var certdb;
+
+/**
+ * Cert tree for the "Authorities" tab.
+ *
+ * @type {nsICertTree}
+ */
+var caTreeView;
+/**
+ * Cert tree for the "Servers" tab.
+ *
+ * @type {nsICertTree}
+ */
+var serverTreeView;
+
+var overrideService;
+
+function createRichlistItem(item) {
+ let innerHbox = document.createXULElement("hbox");
+ innerHbox.setAttribute("align", "center");
+ innerHbox.setAttribute("flex", "1");
+
+ let row = document.createXULElement("label");
+ row.setAttribute("flex", "1");
+ row.setAttribute("crop", "end");
+ row.setAttribute("style", "margin-inline-start: 15px;");
+ if ("raw" in item) {
+ row.setAttribute("value", item.raw);
+ } else {
+ document.l10n.setAttributes(row, item.l10nid);
+ }
+ row.setAttribute("ordinal", "1");
+ innerHbox.appendChild(row);
+
+ return innerHbox;
+}
+
+var serverRichList = {
+ richlist: undefined,
+
+ buildRichList() {
+ let overrides = overrideService.getOverrides().map(item => {
+ return {
+ hostPort: item.hostPort,
+ asciiHost: item.asciiHost,
+ port: item.port,
+ originAttributes: item.originAttributes,
+ fingerprint: item.fingerprint,
+ };
+ });
+ overrides.sort((a, b) => {
+ let criteria = ["hostPort", "fingerprint"];
+ for (let c of criteria) {
+ let res = a[c].localeCompare(b[c]);
+ if (res !== 0) {
+ return res;
+ }
+ }
+ return 0;
+ });
+
+ this.richlist.textContent = "";
+ this.richlist.clearSelection();
+
+ let frag = document.createDocumentFragment();
+ for (let override of overrides) {
+ let richlistitem = this._richBoxAddItem(override);
+ frag.appendChild(richlistitem);
+ }
+ this.richlist.appendChild(frag);
+
+ this._setButtonState();
+ this.richlist.addEventListener("select", () => this._setButtonState());
+ },
+
+ _richBoxAddItem(item) {
+ let richlistitem = document.createXULElement("richlistitem");
+
+ richlistitem.setAttribute("host", item.asciiHost);
+ richlistitem.setAttribute("port", item.port);
+ richlistitem.setAttribute("hostPort", item.hostPort);
+ richlistitem.setAttribute("fingerprint", item.fingerprint);
+ richlistitem.setAttribute(
+ "originAttributes",
+ JSON.stringify(item.originAttributes)
+ );
+
+ let hbox = document.createXULElement("hbox");
+ hbox.setAttribute("flex", "1");
+ hbox.setAttribute("equalsize", "always");
+
+ hbox.appendChild(createRichlistItem({ raw: item.hostPort }));
+ hbox.appendChild(createRichlistItem({ raw: item.fingerprint }));
+
+ richlistitem.appendChild(hbox);
+
+ return richlistitem;
+ },
+
+ deleteSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ if (!selectedItem) {
+ return;
+ }
+
+ let retVals = {
+ deleteConfirmed: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/deletecert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ "websites_tab",
+ [
+ {
+ hostPort: selectedItem.attributes.hostPort.value,
+ },
+ ],
+ retVals
+ );
+
+ if (retVals.deleteConfirmed) {
+ overrideService.clearValidityOverride(
+ selectedItem.attributes.host.value,
+ selectedItem.attributes.port.value,
+ JSON.parse(selectedItem.attributes.originAttributes.value)
+ );
+ this.buildRichList();
+ }
+ },
+
+ addException() {
+ let retval = {
+ exceptionAdded: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/exceptionDialog.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ retval
+ );
+ if (retval.exceptionAdded) {
+ this.buildRichList();
+ }
+ },
+
+ _setButtonState() {
+ let websiteDeleteButton = document.getElementById("websites_deleteButton");
+ websiteDeleteButton.disabled = this.richlist.selectedIndex < 0;
+ },
+};
+/**
+ * Cert tree for the "People" tab.
+ *
+ * @type {nsICertTree}
+ */
+var emailTreeView;
+/**
+ * Cert tree for the "Your Certificates" tab.
+ *
+ * @type {nsICertTree}
+ */
+var userTreeView;
+
+var clientAuthRememberService;
+
+var rememberedDecisionsRichList = {
+ richlist: undefined,
+
+ buildRichList() {
+ let rememberedDecisions = clientAuthRememberService.getDecisions();
+
+ let oldItems = this.richlist.querySelectorAll("richlistitem");
+ for (let item of oldItems) {
+ item.remove();
+ }
+
+ let frag = document.createDocumentFragment();
+ for (let decision of rememberedDecisions) {
+ let richlistitem = this._richBoxAddItem(decision);
+ frag.appendChild(richlistitem);
+ }
+ this.richlist.appendChild(frag);
+
+ this.richlist.addEventListener("select", () => this.setButtonState());
+ },
+
+ _richBoxAddItem(item) {
+ let richlistitem = document.createXULElement("richlistitem");
+
+ richlistitem.setAttribute("entryKey", item.entryKey);
+ richlistitem.setAttribute("dbKey", item.dbKey);
+
+ let hbox = document.createXULElement("hbox");
+ hbox.setAttribute("flex", "1");
+ hbox.setAttribute("equalsize", "always");
+
+ hbox.appendChild(createRichlistItem({ raw: item.asciiHost }));
+ if (item.dbKey == "") {
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "send-no-client-certificate" })
+ );
+
+ hbox.appendChild(createRichlistItem({ raw: "" }));
+ } else {
+ let tmpCert = certdb.findCertByDBKey(item.dbKey);
+ // The certificate corresponding to this item's dbKey may not be
+ // available (for example, if it was stored on a token that's been
+ // removed, or if it was deleted).
+ if (tmpCert) {
+ hbox.appendChild(createRichlistItem({ raw: tmpCert.commonName }));
+ hbox.appendChild(createRichlistItem({ raw: tmpCert.serialNumber }));
+ } else {
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "certificate-not-available" })
+ );
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "certificate-not-available" })
+ );
+ }
+ }
+
+ richlistitem.appendChild(hbox);
+
+ return richlistitem;
+ },
+
+ deleteSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ let index = this.richlist.selectedIndex;
+ if (index < 0) {
+ return;
+ }
+
+ clientAuthRememberService.forgetRememberedDecision(
+ selectedItem.attributes.entryKey.value
+ );
+
+ this.buildRichList();
+ this.setButtonState();
+ },
+
+ viewSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ let index = this.richlist.selectedIndex;
+ if (index < 0) {
+ return;
+ }
+
+ if (selectedItem.attributes.dbKey.value != "") {
+ let cert = certdb.findCertByDBKey(selectedItem.attributes.dbKey.value);
+ viewCertHelper(window, cert);
+ }
+ },
+
+ setButtonState() {
+ let rememberedDeleteButton = document.getElementById(
+ "remembered_deleteButton"
+ );
+ let rememberedViewButton = document.getElementById("remembered_viewButton");
+
+ rememberedDeleteButton.disabled = this.richlist.selectedIndex < 0;
+ rememberedViewButton.disabled =
+ this.richlist.selectedItem == null
+ ? true
+ : this.richlist.selectedItem.attributes.dbKey.value == "";
+ },
+};
+
+function LoadCerts() {
+ certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ var certcache = certdb.getCerts();
+
+ caTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
+ Ci.nsICertTree
+ );
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ document.getElementById("ca-tree").view = caTreeView;
+
+ emailTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
+ Ci.nsICertTree
+ );
+ emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
+ document.getElementById("email-tree").view = emailTreeView;
+
+ userTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
+ Ci.nsICertTree
+ );
+ userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
+ document.getElementById("user-tree").view = userTreeView;
+
+ clientAuthRememberService = Cc[
+ "@mozilla.org/security/clientAuthRememberService;1"
+ ].getService(Ci.nsIClientAuthRememberService);
+
+ overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
+ Ci.nsICertOverrideService
+ );
+
+ rememberedDecisionsRichList.richlist =
+ document.getElementById("rememberedList");
+ serverRichList.richlist = document.getElementById("serverList");
+
+ rememberedDecisionsRichList.buildRichList();
+ serverRichList.buildRichList();
+
+ rememberedDecisionsRichList.setButtonState();
+
+ enableBackupAllButton();
+}
+
+function enableBackupAllButton() {
+ let backupAllButton = document.getElementById("mine_backupAllButton");
+ backupAllButton.disabled = userTreeView.rowCount < 1;
+}
+
+function getSelectedCerts() {
+ var ca_tab = document.getElementById("ca_tab");
+ var mine_tab = document.getElementById("mine_tab");
+ var others_tab = document.getElementById("others_tab");
+ var items = null;
+ if (ca_tab.selected) {
+ items = caTreeView.selection;
+ } else if (mine_tab.selected) {
+ items = userTreeView.selection;
+ } else if (others_tab.selected) {
+ items = emailTreeView.selection;
+ }
+ selected_certs = [];
+ var cert = null;
+ var nr = 0;
+ if (items != null) {
+ nr = items.getRangeCount();
+ }
+ if (nr > 0) {
+ for (let i = 0; i < nr; i++) {
+ var o1 = {};
+ var o2 = {};
+ items.getRangeAt(i, o1, o2);
+ var min = o1.value;
+ var max = o2.value;
+ for (let j = min; j <= max; j++) {
+ if (ca_tab.selected) {
+ cert = caTreeView.getCert(j);
+ } else if (mine_tab.selected) {
+ cert = userTreeView.getCert(j);
+ } else if (others_tab.selected) {
+ cert = emailTreeView.getCert(j);
+ }
+ if (cert) {
+ var sc = selected_certs.length;
+ selected_certs[sc] = cert;
+ selected_index[sc] = j;
+ }
+ }
+ }
+ }
+}
+
+function getSelectedTreeItems() {
+ var ca_tab = document.getElementById("ca_tab");
+ var mine_tab = document.getElementById("mine_tab");
+ var others_tab = document.getElementById("others_tab");
+ var items = null;
+ if (ca_tab.selected) {
+ items = caTreeView.selection;
+ } else if (mine_tab.selected) {
+ items = userTreeView.selection;
+ } else if (others_tab.selected) {
+ items = emailTreeView.selection;
+ }
+ selected_certs = [];
+ selected_tree_items = [];
+ selected_index = [];
+ var tree_item = null;
+ var nr = 0;
+ if (items != null) {
+ nr = items.getRangeCount();
+ }
+ if (nr > 0) {
+ for (let i = 0; i < nr; i++) {
+ var o1 = {};
+ var o2 = {};
+ items.getRangeAt(i, o1, o2);
+ var min = o1.value;
+ var max = o2.value;
+ for (let j = min; j <= max; j++) {
+ if (ca_tab.selected) {
+ tree_item = caTreeView.getTreeItem(j);
+ } else if (mine_tab.selected) {
+ tree_item = userTreeView.getTreeItem(j);
+ } else if (others_tab.selected) {
+ tree_item = emailTreeView.getTreeItem(j);
+ }
+ if (tree_item) {
+ var sc = selected_tree_items.length;
+ selected_tree_items[sc] = tree_item;
+ selected_index[sc] = j;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Returns true if nothing in the given cert tree is selected or if the
+ * selection includes a container. Returns false otherwise.
+ *
+ * @param {nsICertTree} certTree
+ * @returns {boolean}
+ */
+function nothingOrContainerSelected(certTree) {
+ var certTreeSelection = certTree.selection;
+ var numSelectionRanges = certTreeSelection.getRangeCount();
+
+ if (numSelectionRanges == 0) {
+ return true;
+ }
+
+ for (var i = 0; i < numSelectionRanges; i++) {
+ var o1 = {};
+ var o2 = {};
+ certTreeSelection.getRangeAt(i, o1, o2);
+ var minIndex = o1.value;
+ var maxIndex = o2.value;
+ for (var j = minIndex; j <= maxIndex; j++) {
+ if (certTree.isContainer(j)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+async function promptError(aErrorCode) {
+ if (aErrorCode != Ci.nsIX509CertDB.Success) {
+ let msgName = "pkcs12-unknown-err";
+ switch (aErrorCode) {
+ case Ci.nsIX509CertDB.ERROR_PKCS12_NOSMARTCARD_EXPORT:
+ msgName = "pkcs12-info-no-smartcard-backup";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_RESTORE_FAILED:
+ msgName = "pkcs12-unknown-err-restore";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED:
+ msgName = "pkcs12-unknown-err-backup";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_CERT_COLLISION:
+ case Ci.nsIX509CertDB.ERROR_PKCS12_DUPLICATE_DATA:
+ msgName = "pkcs12-dup-data";
+ break;
+ case Ci.nsIX509CertDB.ERROR_BAD_PASSWORD:
+ msgName = "pk11-bad-password";
+ break;
+ case Ci.nsIX509CertDB.ERROR_DECODE_ERROR:
+ msgName = "pkcs12-decode-err";
+ break;
+ default:
+ break;
+ }
+ let [message] = await document.l10n.formatValues([{ id: msgName }]);
+ let prompter = Services.ww.getNewPrompter(window);
+ prompter.alert(null, message);
+ }
+}
+
+/**
+ * Enables or disables buttons corresponding to a cert tree depending on what
+ * is selected in the cert tree.
+ *
+ * @param {nsICertTree} certTree
+ * @param {Array} idList A list of string identifiers for button elements to
+ * enable or disable.
+ */
+function enableButtonsForCertTree(certTree, idList) {
+ let disableButtons = nothingOrContainerSelected(certTree);
+
+ for (let id of idList) {
+ document.getElementById(id).setAttribute("disabled", disableButtons);
+ }
+}
+
+function ca_enableButtons() {
+ let idList = [
+ "ca_viewButton",
+ "ca_editButton",
+ "ca_exportButton",
+ "ca_deleteButton",
+ ];
+ enableButtonsForCertTree(caTreeView, idList);
+}
+
+function mine_enableButtons() {
+ let idList = ["mine_viewButton", "mine_backupButton", "mine_deleteButton"];
+ enableButtonsForCertTree(userTreeView, idList);
+}
+
+function email_enableButtons() {
+ let idList = ["email_viewButton", "email_exportButton", "email_deleteButton"];
+ enableButtonsForCertTree(emailTreeView, idList);
+}
+
+async function backupCerts() {
+ getSelectedCerts();
+ var numcerts = selected_certs.length;
+ if (numcerts == 0) {
+ return;
+ }
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [backupFileDialog, filePkcs12Spec] = await document.l10n.formatValues([
+ { id: "choose-p12-backup-file-dialog" },
+ { id: "file-browse-pkcs12-spec" },
+ ]);
+ fp.init(window, backupFileDialog, Ci.nsIFilePicker.modeSave);
+ fp.appendFilter(filePkcs12Spec, "*.p12");
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.defaultExtension = "p12";
+ fp.open(rv => {
+ if (
+ rv == Ci.nsIFilePicker.returnOK ||
+ rv == Ci.nsIFilePicker.returnReplace
+ ) {
+ let password = {};
+ if (certdialogs.setPKCS12FilePassword(window, password)) {
+ let errorCode = certdb.exportPKCS12File(
+ fp.file,
+ selected_certs,
+ password.value
+ );
+ promptError(errorCode);
+ }
+ }
+ });
+}
+
+function backupAllCerts() {
+ // Select all rows, then call doBackup()
+ userTreeView.selection.selectAll();
+ backupCerts();
+}
+
+function editCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/editcacert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ cert
+ );
+ }
+}
+
+async function restoreCerts() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [restoreFileDialog, filePkcs12Spec, fileCertSpec] =
+ await document.l10n.formatValues([
+ { id: "choose-p12-restore-file-dialog" },
+ { id: "file-browse-pkcs12-spec" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, restoreFileDialog, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(filePkcs12Spec, "*.p12; *.pfx");
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ // If this is an X509 user certificate, import it as one.
+
+ var isX509FileType = false;
+ var fileTypesList = gCertFileTypes.slice(1).split("; *");
+ for (var type of fileTypesList) {
+ if (fp.file.path.endsWith(type)) {
+ isX509FileType = true;
+ break;
+ }
+ }
+
+ if (isX509FileType) {
+ let fstream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ fstream.init(fp.file, -1, 0, 0);
+ let dataString = NetUtil.readInputStreamToString(
+ fstream,
+ fstream.available()
+ );
+ let dataArray = [];
+ for (let i = 0; i < dataString.length; i++) {
+ dataArray.push(dataString.charCodeAt(i));
+ }
+ fstream.close();
+ let prompter = Services.ww.getNewPrompter(window);
+ let interfaceRequestor = {
+ getInterface() {
+ return prompter;
+ },
+ };
+ certdb.importUserCertificate(
+ dataArray,
+ dataArray.length,
+ interfaceRequestor
+ );
+ } else {
+ // Otherwise, assume it's a PKCS12 file and import it that way.
+ let password = {};
+ let errorCode = Ci.nsIX509CertDB.ERROR_BAD_PASSWORD;
+ while (
+ errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+ certdialogs.getPKCS12FilePassword(window, password)
+ ) {
+ errorCode = certdb.importPKCS12File(fp.file, password.value);
+ if (
+ errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+ !password.value.length
+ ) {
+ // It didn't like empty string password, try no password.
+ errorCode = certdb.importPKCS12File(fp.file, null);
+ }
+ promptError(errorCode);
+ }
+ }
+
+ var certcache = certdb.getCerts();
+ userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
+ userTreeView.selection.clearSelection();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ enableBackupAllButton();
+ });
+}
+
+async function exportCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ await exportToFile(window, cert);
+ }
+}
+
+/**
+ * Deletes the selected certs in the active tab.
+ */
+function deleteCerts() {
+ getSelectedTreeItems();
+ let numcerts = selected_tree_items.length;
+ if (numcerts == 0) {
+ return;
+ }
+
+ const treeViewMap = {
+ mine_tab: userTreeView,
+ ca_tab: caTreeView,
+ others_tab: emailTreeView,
+ };
+ let selTab = document.getElementById("certMgrTabbox").selectedItem;
+ let selTabID = selTab.getAttribute("id");
+
+ if (!(selTabID in treeViewMap)) {
+ return;
+ }
+
+ let retVals = {
+ deleteConfirmed: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/deletecert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ selTabID,
+ selected_tree_items,
+ retVals
+ );
+
+ if (retVals.deleteConfirmed) {
+ let treeView = treeViewMap[selTabID];
+
+ for (let t = numcerts - 1; t >= 0; t--) {
+ treeView.deleteEntryObject(selected_index[t]);
+ }
+
+ selected_tree_items = [];
+ selected_index = [];
+ treeView.selection.clearSelection();
+ if (selTabID == "mine_tab") {
+ enableBackupAllButton();
+ }
+ }
+}
+
+function viewCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ viewCertHelper(window, cert);
+ }
+}
+
+async function addCACerts() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [importCa, fileCertSpec] = await document.l10n.formatValues([
+ { id: "import-ca-certs-prompt" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, importCa, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.CA_CERT);
+ let certcache = certdb.getCerts();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ }
+ });
+}
+
+async function addEmailCert() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [importEmail, fileCertSpec] = await document.l10n.formatValues([
+ { id: "import-email-cert-prompt" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, importEmail, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.EMAIL_CERT);
+ var certcache = certdb.getCerts();
+ emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
+ emailTreeView.selection.clearSelection();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ }
+ });
+}
diff --git a/security/manager/pki/resources/content/certManager.xhtml b/security/manager/pki/resources/content/certManager.xhtml
new file mode 100644
index 0000000000..9cf0543d78
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.xhtml
@@ -0,0 +1,330 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://pippki/content/certManager.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ windowtype="mozilla:certmanager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="certmgr-title"
+ onload="LoadCerts();"
+ persist="screenX screenY width height"
+>
+ <dialog id="certmanager" buttons="accept">
+ <linkset>
+ <html:link
+ rel="localization"
+ href="security/certificates/certManager.ftl"
+ />
+ </linkset>
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/certManager.js" />
+
+ <tabbox id="certmanagertabs" flex="1" persist="selectedIndex">
+ <tabs id="certMgrTabbox">
+ <tab id="mine_tab" data-l10n-id="certmgr-tab-mine" />
+ <tab id="remembered_tab" data-l10n-id="certmgr-tab-remembered" />
+ <tab id="others_tab" data-l10n-id="certmgr-tab-people" />
+ <tab id="websites_tab" data-l10n-id="certmgr-tab-servers" />
+ <tab id="ca_tab" data-l10n-id="certmgr-tab-ca" selected="true" />
+ </tabs>
+ <tabpanels flex="1">
+ <vbox id="myCerts" flex="1">
+ <description data-l10n-id="certmgr-mine"></description>
+ <separator class="thin" />
+ <tree
+ id="user-tree"
+ flex="1"
+ enableColumnDrag="true"
+ onselect="mine_enableButtons()"
+ >
+ <treecols>
+ <!--
+ The below code may suggest that 'ordinal' is still a supported XUL
+ XUL attribute. It is not. This is a crutch so that we can
+ continue persisting the CSS order property,
+ which is the appropriate replacement for the ordinal attribute
+ but cannot yet be easily persisted. The code that synchronizes
+ the attribute with the CSS lives in
+ toolkit/content/widget/tree.js and is specific to tree elements.
+ -->
+ <treecol
+ id="certcol"
+ data-l10n-id="certmgr-cert-name"
+ primary="true"
+ persist="hidden width ordinal"
+ />
+ <splitter class="tree-splitter" />
+ <treecol
+ id="tokencol"
+ data-l10n-id="certmgr-token-name"
+ persist="hidden width ordinal"
+ />
+ <splitter class="tree-splitter" />
+ <treecol
+ id="serialnumcol"
+ data-l10n-id="certmgr-serial"
+ persist="hidden width ordinal"
+ />
+ <splitter class="tree-splitter" />
+ <treecol
+ id="issuedcol"
+ data-l10n-id="certmgr-begins-label"
+ hidden="true"
+ persist="hidden width ordinal"
+ />
+ <splitter class="tree-splitter" />
+ <treecol
+ id="expiredcol"
+ data-l10n-id="certmgr-expires-label"
+ persist="hidden width ordinal"
+ />
+ </treecols>
+ <treechildren ondblclick="viewCerts();" />
+ </tree>
+
+ <separator class="thin" />
+
+ <hbox>
+ <button
+ id="mine_viewButton"
+ class="normal"
+ data-l10n-id="certmgr-view"
+ disabled="true"
+ oncommand="viewCerts();"
+ />
+ <button
+ id="mine_backupButton"
+ class="normal"
+ data-l10n-id="certmgr-backup"
+ disabled="true"
+ oncommand="backupCerts();"
+ />
+ <button
+ id="mine_backupAllButton"
+ class="normal"
+ data-l10n-id="certmgr-backup-all"
+ oncommand="backupAllCerts();"
+ />
+ <button
+ id="mine_restoreButton"
+ class="normal"
+ data-l10n-id="certmgr-restore"
+ oncommand="restoreCerts();"
+ />
+ <button
+ id="mine_deleteButton"
+ class="normal"
+ data-l10n-id="certmgr-delete"
+ disabled="true"
+ oncommand="deleteCerts();"
+ />
+ </hbox>
+ </vbox>
+ <vbox id="rememberedCerts" flex="1">
+ <description data-l10n-id="certmgr-remembered"></description>
+ <separator class="thin" />
+
+ <listheader equalsize="always">
+ <treecol
+ id="hostcol"
+ data-l10n-id="certmgr-cert-host"
+ primary="true"
+ persist="hidden width ordinal"
+ />
+ <treecol
+ id="certcol"
+ data-l10n-id="certmgr-cert-name"
+ primary="true"
+ persist="hidden width ordinal"
+ />
+ <treecol
+ id="serialnumcol"
+ data-l10n-id="certmgr-serial"
+ persist="hidden width ordinal"
+ />
+ </listheader>
+ <richlistbox id="rememberedList" flex="1" selected="false" />
+
+ <separator class="thin" />
+
+ <hbox>
+ <button
+ id="remembered_deleteButton"
+ class="normal"
+ data-l10n-id="certmgr-delete"
+ oncommand="rememberedDecisionsRichList.deleteSelectedRichListItem()"
+ />
+
+ <button
+ id="remembered_viewButton"
+ class="normal"
+ data-l10n-id="certmgr-view"
+ oncommand="rememberedDecisionsRichList.viewSelectedRichListItem()"
+ />
+ </hbox>
+ </vbox>
+ <vbox id="othersCerts" flex="1">
+ <description data-l10n-id="certmgr-people"></description>
+ <separator class="thin" />
+ <tree id="email-tree" flex="1" onselect="email_enableButtons()">
+ <treecols>
+ <treecol
+ id="certcol"
+ data-l10n-id="certmgr-cert-name"
+ primary="true"
+ />
+ <splitter class="tree-splitter" />
+ <treecol id="emailcol" data-l10n-id="certmgr-email" />
+ <splitter class="tree-splitter" />
+ <treecol id="expiredcol" data-l10n-id="certmgr-expires-label" />
+ </treecols>
+ <treechildren flex="1" ondblclick="viewCerts();" />
+ </tree>
+
+ <separator class="thin" />
+
+ <hbox>
+ <button
+ id="email_viewButton"
+ data-l10n-id="certmgr-view"
+ disabled="true"
+ oncommand="viewCerts();"
+ />
+ <button
+ id="email_addButton"
+ data-l10n-id="certmgr-restore"
+ oncommand="addEmailCert();"
+ />
+ <button
+ id="email_exportButton"
+ data-l10n-id="certmgr-export"
+ disabled="true"
+ oncommand="exportCerts();"
+ />
+ <button
+ id="email_deleteButton"
+ data-l10n-id="certmgr-delete"
+ disabled="true"
+ oncommand="deleteCerts();"
+ />
+ </hbox>
+ </vbox>
+
+ <vbox id="webCerts" flex="1">
+ <description data-l10n-id="certmgr-server"></description>
+ <separator class="thin" />
+
+ <listheader equalsize="always">
+ <treecol
+ id="sitecol"
+ data-l10n-id="certmgr-cert-server"
+ primary="true"
+ />
+ <treecol
+ id="sha256col"
+ data-l10n-id="certmgr-fingerprint-sha-256"
+ />
+ </listheader>
+ <richlistbox
+ ondblclick="serverRichList.viewSelectedRichListItem();"
+ id="serverList"
+ flex="1"
+ selected="false"
+ />
+
+ <separator class="thin" />
+
+ <hbox>
+ <button
+ id="websites_deleteButton"
+ data-l10n-id="certmgr-delete"
+ oncommand="serverRichList.deleteSelectedRichListItem();"
+ />
+ <button
+ id="websites_exceptionButton"
+ data-l10n-id="certmgr-add-exception"
+ oncommand="serverRichList.addException();"
+ />
+ </hbox>
+ </vbox>
+ <vbox id="CACerts" flex="1">
+ <description data-l10n-id="certmgr-ca"></description>
+ <separator class="thin" />
+ <tree
+ id="ca-tree"
+ flex="1"
+ enableColumnDrag="true"
+ onselect="ca_enableButtons()"
+ >
+ <treecols>
+ <!--
+ The below code may suggest that 'ordinal' is still a supported XUL
+ XUL attribute. It is not. This is a crutch so that we can
+ continue persisting the CSS order property,
+ which is the appropriate replacement for the ordinal attribute
+ but cannot yet be easily persisted. The code that synchronizes
+ the attribute with the CSS lives in
+ toolkit/content/widget/tree.js and is specific to tree elements.
+ -->
+ <treecol
+ id="certcol"
+ data-l10n-id="certmgr-cert-name"
+ primary="true"
+ persist="hidden width ordinal"
+ />
+ <splitter class="tree-splitter" />
+ <treecol
+ id="tokencol"
+ data-l10n-id="certmgr-token-name"
+ persist="hidden width ordinal"
+ />
+ </treecols>
+ <treechildren ondblclick="viewCerts();" />
+ </tree>
+
+ <separator class="thin" />
+
+ <hbox>
+ <button
+ id="ca_viewButton"
+ data-l10n-id="certmgr-view"
+ disabled="true"
+ oncommand="viewCerts();"
+ />
+ <button
+ id="ca_editButton"
+ data-l10n-id="certmgr-edit"
+ disabled="true"
+ oncommand="editCerts();"
+ />
+ <button
+ id="ca_addButton"
+ data-l10n-id="certmgr-restore"
+ oncommand="addCACerts();"
+ />
+ <button
+ id="ca_exportButton"
+ data-l10n-id="certmgr-export"
+ disabled="true"
+ oncommand="exportCerts();"
+ />
+ <button
+ id="ca_deleteButton"
+ data-l10n-id="certmgr-delete-builtin"
+ disabled="true"
+ oncommand="deleteCerts();"
+ />
+ </hbox>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/changepassword.js b/security/manager/pki/resources/content/changepassword.js
new file mode 100644
index 0000000000..e3d6b118bf
--- /dev/null
+++ b/security/manager/pki/resources/content/changepassword.js
@@ -0,0 +1,212 @@
+/* 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 { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyGetter(
+ this,
+ "l10n",
+ () => new Localization(["security/pippki/pippki.ftl"], true)
+);
+
+var params;
+var token;
+var pw1;
+
+function doPrompt(messageL10nId) {
+ let msg = l10n.formatValueSync(messageL10nId);
+ Services.prompt.alert(window, null, msg);
+}
+
+function onLoad() {
+ document.getElementById("set_password").getButton("accept").disabled = true;
+ document.addEventListener("dialogaccept", setPassword);
+
+ pw1 = document.getElementById("pw1");
+ params = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
+ token = params.objects.GetElementAt(0).QueryInterface(Ci.nsIPK11Token);
+
+ document.l10n.setAttributes(
+ document.getElementById("tokenName"),
+ "change-password-token",
+ { tokenName: token.tokenName }
+ );
+ process();
+}
+
+function process() {
+ let bundle = document.getElementById("pippki_bundle");
+ let oldpwbox = document.getElementById("oldpw");
+ let msgBox = document.getElementById("message");
+ // If the token is unitialized, don't use the old password box.
+ // Otherwise, do.
+ if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
+ oldpwbox.hidden = true;
+ msgBox.setAttribute("value", bundle.getString("password_not_set"));
+ msgBox.hidden = false;
+
+ if (!token.needsLogin()) {
+ oldpwbox.setAttribute("inited", "empty");
+ } else {
+ oldpwbox.setAttribute("inited", "true");
+ }
+
+ // Select first password field
+ document.getElementById("pw1").focus();
+ } else {
+ // Select old password field
+ oldpwbox.hidden = false;
+ msgBox.hidden = true;
+ oldpwbox.setAttribute("inited", "false");
+ oldpwbox.focus();
+ }
+
+ // Return value 0 means "canceled"
+ params.SetInt(1, 0);
+
+ checkPasswords();
+}
+
+function setPassword(event) {
+ var oldpwbox = document.getElementById("oldpw");
+ var initpw = oldpwbox.getAttribute("inited");
+
+ var success = false;
+
+ if (initpw == "false" || initpw == "empty") {
+ try {
+ var oldpw = "";
+ var passok = 0;
+
+ if (initpw == "empty") {
+ passok = 1;
+ } else {
+ oldpw = oldpwbox.value;
+ passok = token.checkPassword(oldpw);
+ }
+
+ if (passok) {
+ if (initpw == "empty" && pw1.value == "") {
+ // checkPasswords() should have prevented this path from being reached.
+ } else {
+ if (pw1.value == "") {
+ var secmoddb = Cc[
+ "@mozilla.org/security/pkcs11moduledb;1"
+ ].getService(Ci.nsIPKCS11ModuleDB);
+ if (secmoddb.isFIPSEnabled) {
+ // empty passwords are not allowed in FIPS mode
+ doPrompt("pippki-pw-change2empty-in-fips-mode");
+ passok = 0;
+ }
+ }
+ if (passok) {
+ token.changePassword(oldpw, pw1.value);
+ if (pw1.value == "") {
+ doPrompt("pippki-pw-erased-ok");
+ } else {
+ doPrompt("pippki-pw-change-ok");
+ }
+ success = true;
+ }
+ }
+ } else {
+ oldpwbox.focus();
+ oldpwbox.setAttribute("value", "");
+ doPrompt("pippki-incorrect-pw");
+ }
+ } catch (e) {
+ doPrompt("pippki-failed-pw-change");
+ }
+ } else {
+ token.initPassword(pw1.value);
+ if (pw1.value == "") {
+ doPrompt("pippki-pw-not-wanted");
+ }
+ success = true;
+ }
+
+ if (success && params) {
+ // Return value 1 means "successfully executed ok"
+ params.SetInt(1, 1);
+ }
+
+ // Terminate dialog
+ if (!success) {
+ event.preventDefault();
+ }
+}
+
+function setPasswordStrength() {
+ // We weigh the quality of the password by checking the number of:
+ // - Characters
+ // - Numbers
+ // - Non-alphanumeric chars
+ // - Upper and lower case characters
+
+ let pw = document.getElementById("pw1").value;
+
+ let pwlength = pw.length;
+ if (pwlength > 5) {
+ pwlength = 5;
+ }
+
+ let numnumeric = pw.replace(/[0-9]/g, "");
+ let numeric = pw.length - numnumeric.length;
+ if (numeric > 3) {
+ numeric = 3;
+ }
+
+ let symbols = pw.replace(/\W/g, "");
+ let numsymbols = pw.length - symbols.length;
+ if (numsymbols > 3) {
+ numsymbols = 3;
+ }
+
+ let numupper = pw.replace(/[A-Z]/g, "");
+ let upper = pw.length - numupper.length;
+ if (upper > 3) {
+ upper = 3;
+ }
+
+ let pwstrength =
+ pwlength * 10 - 20 + numeric * 10 + numsymbols * 15 + upper * 10;
+
+ // Clamp strength to [0, 100].
+ if (pwstrength < 0) {
+ pwstrength = 0;
+ }
+ if (pwstrength > 100) {
+ pwstrength = 100;
+ }
+
+ let meter = document.getElementById("pwmeter");
+ meter.setAttribute("value", pwstrength);
+}
+
+function checkPasswords() {
+ let pw1 = document.getElementById("pw1").value;
+ let pw2 = document.getElementById("pw2").value;
+
+ var oldpwbox = document.getElementById("oldpw");
+ if (oldpwbox) {
+ var initpw = oldpwbox.getAttribute("inited");
+
+ if (initpw == "empty" && pw1 == "") {
+ // The token has already been initialized, therefore this dialog
+ // was called with the intention to change the password.
+ // The token currently uses an empty password.
+ // We will not allow changing the password from empty to empty.
+ document
+ .getElementById("set_password")
+ .getButton("accept").disabled = true;
+ return;
+ }
+ }
+
+ document.getElementById("set_password").getButton("accept").disabled =
+ pw1 != pw2;
+}
diff --git a/security/manager/pki/resources/content/changepassword.xhtml b/security/manager/pki/resources/content/changepassword.xhtml
new file mode 100644
index 0000000000..8ed7e5e6a2
--- /dev/null
+++ b/security/manager/pki/resources/content/changepassword.xhtml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="change-device-password-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();"
+>
+ <dialog id="set_password" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl" />
+ </linkset>
+
+ <stringbundle
+ id="pippki_bundle"
+ src="chrome://pippki/locale/pippki.properties"
+ />
+
+ <script src="chrome://global/content/globalOverlay.js" />
+ <script src="chrome://global/content/editMenuOverlay.js" />
+
+ <script src="chrome://pippki/content/changepassword.js" />
+
+ <hbox align="center">
+ <label
+ id="tokenName"
+ data-l10n-id="change-password-token"
+ data-l10n-args='{"tokenName":""}'
+ />
+ </hbox>
+
+ <separator />
+
+ <vbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-old" />
+ <html:input id="oldpw" type="password" />
+ <!-- This textbox is inserted as a workaround to the fact that making the 'type'
+ & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
+ 'false'] and ['text' & 'true'] - as would be necessary if the menu has more
+ than one tokens, some initialized and some not - does not work properly. So,
+ either the textbox 'oldpw' or the textbox 'message' would be displayed,
+ depending on the state of the token selected
+ -->
+ <html:input id="message" disabled="true" />
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-new" />
+ <html:input
+ id="pw1"
+ type="password"
+ oninput="setPasswordStrength(); checkPasswords();"
+ />
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-reenter" />
+ <html:input id="pw2" type="password" oninput="checkPasswords();" />
+ </hbox>
+ </vbox>
+
+ <vbox style="margin: 6px">
+ <label
+ for="pwmeter"
+ style="display: flex"
+ data-l10n-id="password-quality-meter"
+ />
+ <html:progress id="pwmeter" value="0" max="100" />
+ </vbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/clientauthask.js b/security/manager/pki/resources/content/clientauthask.js
new file mode 100644
index 0000000000..782b0d7204
--- /dev/null
+++ b/security/manager/pki/resources/content/clientauthask.js
@@ -0,0 +1,178 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+const { parse, pemToDER } = ChromeUtils.importESModule(
+ "chrome://global/content/certviewer/certDecoder.mjs"
+);
+
+/**
+ * @file Implements the functionality of clientauthask.xhtml: a dialog that allows
+ * a user pick a client certificate for TLS client authentication.
+ * @param {string} window.arguments.0
+ * The hostname of the server requesting client authentication.
+ * @param {string} window.arguments.1
+ * The Organization of the server cert.
+ * @param {string} window.arguments.2
+ * The Organization of the issuer of the server cert.
+ * @param {number} window.arguments.3
+ * The port of the server.
+ * @param {nsISupports} window.arguments.4
+ * List of certificates the user can choose from, queryable to
+ * nsIArray<nsIX509Cert>.
+ * @param {nsISupports} window.arguments.5
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of ClientAuthAskReturnValues.
+ */
+
+/**
+ * @typedef ClientAuthAskReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} certChosen
+ * Set to true if the user chose a cert and accepted the dialog, false
+ * otherwise.
+ * @property {boolean} rememberSelection
+ * Set to true if the user wanted their cert selection to be
+ * remembered, false otherwise.
+ * @property {number} selectedIndex
+ * The index the chosen cert is at for the given cert list. Undefined
+ * value if |certChosen| is not true.
+ */
+
+/**
+ * The pippki <stringbundle> element.
+ *
+ * @type {stringbundle}
+ * @see {@link toolkit/content/widgets/stringbundle.js}
+ */
+var bundle;
+/**
+ * The array of certs the user can choose from.
+ *
+ * @type {nsIArray<nsIX509Cert>}
+ */
+var certArray;
+/**
+ * The checkbox storing whether the user wants to remember the selected cert.
+ *
+ * @type {HTMLInputElement} Element checkbox, has to have |checked| property.
+ */
+var rememberBox;
+
+async function onLoad() {
+ bundle = document.getElementById("pippki_bundle");
+ let rememberSetting = Services.prefs.getBoolPref(
+ "security.remember_cert_checkbox_default_setting"
+ );
+
+ rememberBox = document.getElementById("rememberBox");
+ rememberBox.label = bundle.getString("clientAuthRemember");
+ rememberBox.checked = rememberSetting;
+
+ let hostname = window.arguments[0];
+ let org = window.arguments[1];
+ let issuerOrg = window.arguments[2];
+ let port = window.arguments[3];
+ let formattedOrg = bundle.getFormattedString("clientAuthMessage1", [org]);
+ let formattedIssuerOrg = bundle.getFormattedString("clientAuthMessage2", [
+ issuerOrg,
+ ]);
+ let formattedHostnameAndPort = bundle.getFormattedString(
+ "clientAuthHostnameAndPort",
+ [hostname, port.toString()]
+ );
+ setText("hostname", formattedHostnameAndPort);
+ setText("organization", formattedOrg);
+ setText("issuer", formattedIssuerOrg);
+
+ let selectElement = document.getElementById("nicknames");
+ certArray = window.arguments[4].QueryInterface(Ci.nsIArray);
+ for (let i = 0; i < certArray.length; i++) {
+ let menuItemNode = document.createXULElement("menuitem");
+ let cert = certArray.queryElementAt(i, Ci.nsIX509Cert);
+ let nickAndSerial = bundle.getFormattedString("clientAuthNickAndSerial", [
+ cert.displayName,
+ cert.serialNumber,
+ ]);
+ menuItemNode.setAttribute("value", i);
+ menuItemNode.setAttribute("label", nickAndSerial); // This is displayed.
+ selectElement.menupopup.appendChild(menuItemNode);
+ if (i == 0) {
+ selectElement.selectedItem = menuItemNode;
+ }
+ }
+
+ await setDetails();
+ document.addEventListener("dialogaccept", doOK);
+ document.addEventListener("dialogcancel", doCancel);
+
+ Services.obs.notifyObservers(
+ document.getElementById("certAuthAsk"),
+ "cert-dialog-loaded"
+ );
+}
+
+/**
+ * Populates the details section with information concerning the selected cert.
+ */
+async function setDetails() {
+ let index = parseInt(document.getElementById("nicknames").value);
+ let cert = certArray.queryElementAt(index, Ci.nsIX509Cert);
+
+ const formatter = new Intl.DateTimeFormat(undefined, {
+ dateStyle: "medium",
+ timeStyle: "long",
+ });
+ let detailLines = [
+ bundle.getFormattedString("clientAuthIssuedTo", [cert.subjectName]),
+ bundle.getFormattedString("clientAuthSerial", [cert.serialNumber]),
+ bundle.getFormattedString("clientAuthValidityPeriod", [
+ formatter.format(new Date(cert.validity.notBefore / 1000)),
+ formatter.format(new Date(cert.validity.notAfter / 1000)),
+ ]),
+ ];
+ let parsedCert = await parse(pemToDER(cert.getBase64DERString()));
+ let keyUsages = parsedCert.ext.keyUsages;
+ if (keyUsages && keyUsages.purposes.length) {
+ detailLines.push(
+ bundle.getFormattedString("clientAuthKeyUsages", [keyUsages.purposes])
+ );
+ }
+ let emailAddresses = cert.getEmailAddresses();
+ if (emailAddresses.length) {
+ let joinedAddresses = emailAddresses.join(", ");
+ detailLines.push(
+ bundle.getFormattedString("clientAuthEmailAddresses", [joinedAddresses])
+ );
+ }
+ detailLines.push(
+ bundle.getFormattedString("clientAuthIssuedBy", [cert.issuerName])
+ );
+ detailLines.push(
+ bundle.getFormattedString("clientAuthStoredOn", [cert.tokenName])
+ );
+
+ document.getElementById("details").value = detailLines.join("\n");
+}
+
+async function onCertSelected() {
+ await setDetails();
+}
+
+function doOK() {
+ let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("certChosen", true);
+ let index = parseInt(document.getElementById("nicknames").value);
+ retVals.setPropertyAsUint32("selectedIndex", index);
+ retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
+}
+
+function doCancel() {
+ let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("certChosen", false);
+ retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
+}
diff --git a/security/manager/pki/resources/content/clientauthask.xhtml b/security/manager/pki/resources/content/clientauthask.xhtml
new file mode 100644
index 0000000000..f182372ed2
--- /dev/null
+++ b/security/manager/pki/resources/content/clientauthask.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="client-auth-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();"
+>
+ <dialog id="certAuthAsk" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl" />
+ </linkset>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle
+ id="pippki_bundle"
+ src="chrome://pippki/locale/pippki.properties"
+ />
+ </stringbundleset>
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/clientauthask.js" />
+ <script src="chrome://global/content/globalOverlay.js" />
+ <script src="chrome://global/content/editMenuOverlay.js" />
+
+ <description
+ style="font-weight: bold"
+ data-l10n-id="client-auth-site-description"
+ ></description>
+ <description id="hostname" />
+ <description id="organization" />
+ <description id="issuer" />
+
+ <description
+ style="font-weight: bold"
+ data-l10n-id="client-auth-choose-cert"
+ ></description>
+ <!-- The items in this menulist must never be sorted,
+ but remain in the order filled by the application
+-->
+ <menulist id="nicknames" oncommand="onCertSelected();" native="true">
+ <menupopup />
+ </menulist>
+ <description data-l10n-id="client-auth-cert-details"></description>
+ <html:textarea readonly="readonly" id="details" style="height: 11em" />
+ <checkbox id="rememberBox" checked="true" native="true" />
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/deletecert.css b/security/manager/pki/resources/content/deletecert.css
new file mode 100644
index 0000000000..4607f0a44a
--- /dev/null
+++ b/security/manager/pki/resources/content/deletecert.css
@@ -0,0 +1,19 @@
+/* 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/. */
+
+dialog::part(content-box) {
+ flex: 1;
+}
+
+#confirm,
+#impact {
+ /* We don't want these to impact the horizontal size of the dialog */
+ contain: inline-size;
+}
+
+#certlist {
+ flex: 1;
+ min-height: 8em;
+ min-width: 35em;
+}
diff --git a/security/manager/pki/resources/content/deletecert.js b/security/manager/pki/resources/content/deletecert.js
new file mode 100644
index 0000000000..7d926863ed
--- /dev/null
+++ b/security/manager/pki/resources/content/deletecert.js
@@ -0,0 +1,121 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+/**
+ * @file Implements the functionality of deletecert.xhtml: a dialog that allows a
+ * user to confirm whether to delete certain certificates.
+ * @param {string} window.arguments.0
+ * One of the tab IDs listed in certManager.xhtml.
+ * @param {object[]} window.arguments.1
+ * An array of objects representing the certs to delete.
+ * Each must have a 'cert' property or a 'hostPort' property.
+ * @param {DeleteCertReturnValues} window.arguments.2
+ * Object holding the return values of calling the dialog.
+ */
+
+/**
+ * @typedef DeleteCertReturnValues
+ * @type {object}
+ * @property {boolean} deleteConfirmed
+ * Set to true if the user confirmed deletion of the given certs,
+ * false otherwise.
+ */
+
+/**
+ * Returns the element to represent the given cert to delete.
+ *
+ * @param {object} certToDelete
+ * The item to represent.
+ * @returns {Element}
+ * A element of each cert tree item.
+ */
+function getLabelForCertToDelete(certToDelete) {
+ let element = document.createXULElement("label");
+ let cert = certToDelete.cert;
+ if (!cert) {
+ element.setAttribute("value", certToDelete.hostPort);
+ return element;
+ }
+
+ const attributes = [
+ cert.commonName,
+ cert.organizationalUnit,
+ cert.organization,
+ cert.subjectName,
+ ];
+ for (let attribute of attributes) {
+ if (attribute) {
+ element.setAttribute("value", attribute);
+ return element;
+ }
+ }
+
+ document.l10n.setAttributes(element, "cert-with-serial", {
+ serialNumber: cert.serialNumber,
+ });
+ return element;
+}
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ let typeFlag = window.arguments[0];
+ let confirm = document.getElementById("confirm");
+ let impact = document.getElementById("impact");
+ let prefixForType;
+ switch (typeFlag) {
+ case "mine_tab":
+ prefixForType = "delete-user-cert-";
+ break;
+ case "websites_tab":
+ prefixForType = "delete-ssl-override-";
+ break;
+ case "ca_tab":
+ prefixForType = "delete-ca-cert-";
+ break;
+ case "others_tab":
+ prefixForType = "delete-email-cert-";
+ break;
+ default:
+ return;
+ }
+
+ document.l10n.setAttributes(
+ document.documentElement,
+ prefixForType + "title"
+ );
+ document.l10n.setAttributes(confirm, prefixForType + "confirm");
+ document.l10n.setAttributes(impact, prefixForType + "impact");
+
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+
+ let box = document.getElementById("certlist");
+ let certsToDelete = window.arguments[1];
+ for (let certToDelete of certsToDelete) {
+ let listItem = document.createXULElement("richlistitem");
+ let label = getLabelForCertToDelete(certToDelete);
+ listItem.appendChild(label);
+ box.appendChild(listItem);
+ }
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let retVals = window.arguments[2];
+ retVals.deleteConfirmed = true;
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[2];
+ retVals.deleteConfirmed = false;
+}
diff --git a/security/manager/pki/resources/content/deletecert.xhtml b/security/manager/pki/resources/content/deletecert.xhtml
new file mode 100644
index 0000000000..44f4248399
--- /dev/null
+++ b/security/manager/pki/resources/content/deletecert.xhtml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://pippki/content/deletecert.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="certmgr-delete-cert2"
+ data-l10n-attrs="style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();"
+>
+ <dialog id="deleteCertificate" buttons="accept,cancel">
+ <linkset>
+ <html:link
+ rel="localization"
+ href="security/certificates/certManager.ftl"
+ />
+ </linkset>
+
+ <script src="pippki.js" />
+ <script src="chrome://pippki/content/deletecert.js" />
+
+ <description id="confirm" />
+ <richlistbox id="certlist" class="box-padded" />
+ <description id="impact" />
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/device_manager.js b/security/manager/pki/resources/content/device_manager.js
new file mode 100644
index 0000000000..deb58d2ff3
--- /dev/null
+++ b/security/manager/pki/resources/content/device_manager.js
@@ -0,0 +1,433 @@
+/* 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 { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+var secmoddb;
+var skip_enable_buttons = false;
+
+/* Do the initial load of all PKCS# modules and list them. */
+function LoadModules() {
+ secmoddb = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
+ Ci.nsIPKCS11ModuleDB
+ );
+ RefreshDeviceList();
+}
+
+async function doPrompt(l10n_id) {
+ let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
+ Services.prompt.alert(window, null, msg);
+}
+
+async function doConfirm(l10n_id) {
+ let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
+ return Services.prompt.confirm(window, null, msg);
+}
+
+function RefreshDeviceList() {
+ for (let module of secmoddb.listModules()) {
+ let slots = module.listSlots();
+ AddModule(module, slots);
+ }
+
+ // Set the text on the FIPS button.
+ SetFIPSButton();
+}
+
+function SetFIPSButton() {
+ var fipsButton = document.getElementById("fipsbutton");
+ if (secmoddb.isFIPSEnabled) {
+ document.l10n.setAttributes(fipsButton, "devmgr-button-disable-fips");
+ } else {
+ document.l10n.setAttributes(fipsButton, "devmgr-button-enable-fips");
+ }
+
+ var can_toggle = secmoddb.canToggleFIPS;
+ if (can_toggle) {
+ fipsButton.removeAttribute("disabled");
+ } else {
+ fipsButton.setAttribute("disabled", "true");
+ }
+}
+
+/* Add a module to the tree. slots is the array of slots in the module,
+ * to be represented as children.
+ */
+function AddModule(module, slots) {
+ var tree = document.getElementById("device_list");
+ var item = document.createXULElement("treeitem");
+ var row = document.createXULElement("treerow");
+ var cell = document.createXULElement("treecell");
+ cell.setAttribute("label", module.name);
+ row.appendChild(cell);
+ item.appendChild(row);
+ var parent = document.createXULElement("treechildren");
+ for (let slot of slots) {
+ var child_item = document.createXULElement("treeitem");
+ var child_row = document.createXULElement("treerow");
+ var child_cell = document.createXULElement("treecell");
+ child_cell.setAttribute("label", slot.name);
+ child_row.appendChild(child_cell);
+ child_item.appendChild(child_row);
+ child_item.setAttribute("pk11kind", "slot");
+ // 'slot' is an attribute on any HTML element, hence 'slotObject' instead.
+ child_item.slotObject = slot;
+ parent.appendChild(child_item);
+ }
+ item.appendChild(parent);
+ item.setAttribute("pk11kind", "module");
+ item.module = module;
+ item.setAttribute("open", "true");
+ item.setAttribute("container", "true");
+ tree.appendChild(item);
+}
+
+var selected_slot;
+var selected_module;
+
+/* get the slot selected by the user (can only be one-at-a-time) */
+function getSelectedItem() {
+ let tree = document.getElementById("device_tree");
+ if (tree.currentIndex < 0) {
+ return;
+ }
+ let item = tree.view.getItemAtIndex(tree.currentIndex);
+ selected_slot = null;
+ selected_module = null;
+ if (item) {
+ let kind = item.getAttribute("pk11kind");
+ if (kind == "slot") {
+ selected_slot = item.slotObject;
+ } else {
+ // (kind == "module")
+ selected_module = item.module;
+ }
+ }
+}
+
+function enableButtons() {
+ if (skip_enable_buttons) {
+ return;
+ }
+
+ var login_toggle = "true";
+ var logout_toggle = "true";
+ var pw_toggle = "true";
+ var unload_toggle = "true";
+ getSelectedItem();
+ if (selected_module) {
+ unload_toggle = "false";
+ showModuleInfo();
+ } else if (selected_slot) {
+ // here's the workaround - login functions are all with token,
+ // so grab the token type
+ var selected_token = selected_slot.getToken();
+ if (selected_token != null) {
+ if (selected_token.needsLogin() || !selected_token.needsUserInit) {
+ pw_toggle = "false";
+ if (selected_token.needsLogin()) {
+ if (selected_token.isLoggedIn()) {
+ logout_toggle = "false";
+ } else {
+ login_toggle = "false";
+ }
+ }
+ }
+
+ if (
+ !Services.policies.isAllowed("createMasterPassword") &&
+ selected_token.isInternalKeyToken &&
+ !selected_token.hasPassword
+ ) {
+ pw_toggle = "true";
+ }
+ }
+ showSlotInfo();
+ }
+ document
+ .getElementById("login_button")
+ .setAttribute("disabled", login_toggle);
+ document
+ .getElementById("logout_button")
+ .setAttribute("disabled", logout_toggle);
+ document
+ .getElementById("change_pw_button")
+ .setAttribute("disabled", pw_toggle);
+ document
+ .getElementById("unload_button")
+ .setAttribute("disabled", unload_toggle);
+}
+
+// clear the display of information for the slot
+function ClearInfoList() {
+ let infoList = document.getElementById("info_list");
+ while (infoList.hasChildNodes()) {
+ infoList.firstChild.remove();
+ }
+}
+
+function ClearDeviceList() {
+ ClearInfoList();
+
+ skip_enable_buttons = true;
+ var tree = document.getElementById("device_tree");
+ tree.view.selection.clearSelection();
+ skip_enable_buttons = false;
+
+ // Remove the existing listed modules so that a refresh doesn't display the
+ // module that just changed.
+ let deviceList = document.getElementById("device_list");
+ while (deviceList.hasChildNodes()) {
+ deviceList.firstChild.remove();
+ }
+}
+
+// show a list of info about a slot
+function showSlotInfo() {
+ var present = true;
+ ClearInfoList();
+ switch (selected_slot.status) {
+ case Ci.nsIPKCS11Slot.SLOT_DISABLED:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-disabled" },
+ "tok_status"
+ );
+ present = false;
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_NOT_PRESENT:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-not-present" },
+ "tok_status"
+ );
+ present = false;
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-uninitialized" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-not-logged-in" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_LOGGED_IN:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-logged-in" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_READY:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-ready" },
+ "tok_status"
+ );
+ break;
+ default:
+ return;
+ }
+ AddInfoRow("devinfo-desc", { label: selected_slot.desc }, "slot_desc");
+ AddInfoRow("devinfo-man-id", { label: selected_slot.manID }, "slot_manID");
+ AddInfoRow(
+ "devinfo-hwversion",
+ { label: selected_slot.HWVersion },
+ "slot_hwv"
+ );
+ AddInfoRow(
+ "devinfo-fwversion",
+ { label: selected_slot.FWVersion },
+ "slot_fwv"
+ );
+ if (present) {
+ showTokenInfo();
+ }
+}
+
+function showModuleInfo() {
+ ClearInfoList();
+ AddInfoRow("devinfo-modname", { label: selected_module.name }, "module_name");
+ AddInfoRow(
+ "devinfo-modpath",
+ { label: selected_module.libName },
+ "module_path"
+ );
+}
+
+// add a row to the info list, as [col1 col2] (ex.: ["status" "logged in"])
+function AddInfoRow(l10nID, col2, cell_id) {
+ var tree = document.getElementById("info_list");
+ var item = document.createXULElement("treeitem");
+ var row = document.createXULElement("treerow");
+ var cell1 = document.createXULElement("treecell");
+ document.l10n.setAttributes(cell1, l10nID);
+ cell1.setAttribute("crop", "never");
+ row.appendChild(cell1);
+ var cell2 = document.createXULElement("treecell");
+ if (col2.l10nID) {
+ document.l10n.setAttributes(cell2, col2.l10nID);
+ } else {
+ cell2.setAttribute("label", col2.label);
+ }
+ cell2.setAttribute("crop", "never");
+ cell2.setAttribute("id", cell_id);
+ row.appendChild(cell2);
+ item.appendChild(row);
+ tree.appendChild(item);
+}
+
+// log in to a slot
+function doLogin() {
+ getSelectedItem();
+ // here's the workaround - login functions are with token
+ var selected_token = selected_slot.getToken();
+ try {
+ selected_token.login(false);
+ var tok_status = document.getElementById("tok_status");
+ if (selected_token.isLoggedIn()) {
+ document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
+ } else {
+ document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
+ }
+ } catch (e) {
+ doPrompt("login-failed");
+ }
+ enableButtons();
+}
+
+// log out of a slot
+function doLogout() {
+ getSelectedItem();
+ // here's the workaround - login functions are with token
+ var selected_token = selected_slot.getToken();
+ try {
+ selected_token.logoutAndDropAuthenticatedResources();
+ var tok_status = document.getElementById("tok_status");
+ if (selected_token.isLoggedIn()) {
+ document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
+ } else {
+ document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
+ }
+ } catch (e) {}
+ enableButtons();
+}
+
+// load a new device
+function doLoad() {
+ window.browsingContext.topChromeWindow.open(
+ "load_device.xhtml",
+ "loaddevice",
+ "chrome,centerscreen,modal"
+ );
+ ClearDeviceList();
+ RefreshDeviceList();
+}
+
+async function deleteSelected() {
+ getSelectedItem();
+ if (selected_module && (await doConfirm("del-module-warning"))) {
+ try {
+ secmoddb.deleteModule(selected_module.name);
+ } catch (e) {
+ doPrompt("del-module-error");
+ return false;
+ }
+ selected_module = null;
+ return true;
+ }
+ return false;
+}
+
+async function doUnload() {
+ if (await deleteSelected()) {
+ ClearDeviceList();
+ RefreshDeviceList();
+ }
+}
+
+function changePassword() {
+ getSelectedItem();
+ let params = Cc["@mozilla.org/embedcomp/dialogparam;1"].createInstance(
+ Ci.nsIDialogParamBlock
+ );
+ let objects = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ objects.appendElement(selected_slot.getToken());
+ params.objects = objects;
+ window.browsingContext.topChromeWindow.openDialog(
+ "changepassword.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ params
+ );
+ showSlotInfo();
+ enableButtons();
+}
+
+// ------------------------------------- Old code
+
+function showTokenInfo() {
+ var selected_token = selected_slot.getToken();
+ AddInfoRow("devinfo-label", { label: selected_token.tokenName }, "tok_label");
+ AddInfoRow(
+ "devinfo-man-id",
+ { label: selected_token.tokenManID },
+ "tok_manID"
+ );
+ AddInfoRow(
+ "devinfo-serialnum",
+ { label: selected_token.tokenSerialNumber },
+ "tok_sNum"
+ );
+ AddInfoRow(
+ "devinfo-hwversion",
+ { label: selected_token.tokenHWVersion },
+ "tok_hwv"
+ );
+ AddInfoRow(
+ "devinfo-fwversion",
+ { label: selected_token.tokenFWVersion },
+ "tok_fwv"
+ );
+}
+
+function toggleFIPS() {
+ if (!secmoddb.isFIPSEnabled) {
+ // A restriction of FIPS mode is, the password must be set
+ // In FIPS mode the password must be non-empty.
+ // This is different from what we allow in NON-Fips mode.
+
+ var tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
+ Ci.nsIPK11TokenDB
+ );
+ var internal_token = tokendb.getInternalKeyToken(); // nsIPK11Token
+ if (!internal_token.hasPassword) {
+ // Token has either no or an empty password.
+ doPrompt("fips-nonempty-primary-password-required");
+ return;
+ }
+ }
+
+ try {
+ secmoddb.toggleFIPSMode();
+ } catch (e) {
+ doPrompt("unable-to-toggle-fips");
+ return;
+ }
+
+ // Remove the existing listed modules so that a refresh doesn't display the
+ // module that just changed.
+ ClearDeviceList();
+
+ RefreshDeviceList();
+}
diff --git a/security/manager/pki/resources/content/device_manager.xhtml b/security/manager/pki/resources/content/device_manager.xhtml
new file mode 100644
index 0000000000..145aa98c3d
--- /dev/null
+++ b/security/manager/pki/resources/content/device_manager.xhtml
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE dialog>
+
+<window
+ windowtype="mozilla:devicemanager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="devmgr-window"
+ data-l10n-attrs="style"
+ persist="screenX screenY width height"
+ onload="LoadModules();"
+>
+ <dialog id="devicemanager" buttons="accept">
+ <linkset>
+ <html:link
+ rel="localization"
+ href="security/certificates/deviceManager.ftl"
+ />
+ </linkset>
+
+ <script src="chrome://pippki/content/device_manager.js" />
+
+ <hbox flex="1" style="margin: 5px">
+ <!-- List of devices -->
+ <tree
+ id="device_tree"
+ seltype="single"
+ onselect="enableButtons();"
+ hidecolumnpicker="true"
+ flex="1"
+ style="min-width: 15em"
+ >
+ <treecols>
+ <treecol
+ id="deviceCol"
+ flex="1"
+ primary="true"
+ data-l10n-id="devmgr-devlist"
+ />
+ </treecols>
+ <treechildren id="device_list" />
+ </tree>
+ <!-- / List of devices -->
+ <!-- Device status -->
+ <tree
+ id="info_tree"
+ seltype="single"
+ hidecolumnpicker="true"
+ style="flex: 3 3; min-width: 10em"
+ >
+ <treecols>
+ <treecol
+ id="title1Col"
+ style="flex: 5 5 auto"
+ primary="true"
+ data-l10n-id="devmgr-header-details"
+ />
+ <treecol
+ id="title2Col"
+ style="flex: 7 7 auto"
+ data-l10n-id="devmgr-header-value"
+ />
+ </treecols>
+ <treechildren id="info_list" />
+ </tree>
+ <!-- / Device status -->
+ <vbox>
+ <!-- Buttons for manipulating devices -->
+ <button
+ id="login_button"
+ data-l10n-id="devmgr-button-login"
+ oncommand="doLogin();"
+ disabled="true"
+ />
+ <button
+ id="logout_button"
+ data-l10n-id="devmgr-button-logout"
+ oncommand="doLogout();"
+ disabled="true"
+ />
+ <button
+ id="change_pw_button"
+ data-l10n-id="devmgr-button-changepw"
+ oncommand="changePassword();"
+ disabled="true"
+ />
+ <button
+ id="load_button"
+ data-l10n-id="devmgr-button-load"
+ oncommand="doLoad();"
+ />
+ <button
+ id="unload_button"
+ data-l10n-id="devmgr-button-unload"
+ oncommand="doUnload();"
+ disabled="true"
+ />
+ <button
+ id="fipsbutton"
+ data-l10n-id="devmgr-button-enable-fips"
+ oncommand="toggleFIPS();"
+ />
+ </vbox>
+ <!-- / Buttons for manipulating devices -->
+ </hbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/downloadcert.js b/security/manager/pki/resources/content/downloadcert.js
new file mode 100644
index 0000000000..8451997441
--- /dev/null
+++ b/security/manager/pki/resources/content/downloadcert.js
@@ -0,0 +1,83 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+/**
+ * @file Implements the functionality of downloadcert.xhtml: a dialog that allows
+ * a user to confirm whether to import a certificate, and if so what trust
+ * to give it.
+ * @param {nsISupports} window.arguments.0
+ * Certificate to confirm import of, queryable to nsIX509Cert.
+ * @param {nsISupports} window.arguments.1
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of DownloadCertReturnValues.
+ */
+
+/**
+ * @typedef DownloadCertReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} importConfirmed
+ * Set to true if the user confirmed import of the cert and accepted
+ * the dialog, false otherwise.
+ * @property {boolean} trustForSSL
+ * Set to true if the cert should be trusted for SSL, false otherwise.
+ * Undefined value if |importConfirmed| is not true.
+ * @property {boolean} trustForEmail
+ * Set to true if the cert should be trusted for e-mail, false
+ * otherwise. Undefined value if |importConfirmed| is not true.
+ */
+
+/**
+ * The cert to potentially import.
+ *
+ * @type {nsIX509Cert}
+ */
+var gCert;
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);
+
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+
+ let bundle = document.getElementById("pippki_bundle");
+ let caName = gCert.commonName;
+ if (!caName.length) {
+ caName = bundle.getString("unnamedCA");
+ }
+
+ setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName]));
+}
+
+/**
+ * Handler for the "View Cert" button.
+ */
+function viewCert() {
+ viewCertHelper(window, gCert, "window");
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let checkSSL = document.getElementById("trustSSL");
+ let checkEmail = document.getElementById("trustEmail");
+
+ let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("importConfirmed", true);
+ retVals.setPropertyAsBool("trustForSSL", checkSSL.checked);
+ retVals.setPropertyAsBool("trustForEmail", checkEmail.checked);
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("importConfirmed", false);
+}
diff --git a/security/manager/pki/resources/content/downloadcert.xhtml b/security/manager/pki/resources/content/downloadcert.xhtml
new file mode 100644
index 0000000000..cf85c01faf
--- /dev/null
+++ b/security/manager/pki/resources/content/downloadcert.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="download-cert-window2"
+ data-l10n-attrs="title, style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();"
+>
+ <dialog id="download_cert" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl" />
+ </linkset>
+
+ <stringbundle
+ id="pippki_bundle"
+ src="chrome://pippki/locale/pippki.properties"
+ />
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/downloadcert.js" />
+
+ <!-- Let 'em know what they're doing -->
+ <vbox>
+ <description data-l10n-id="download-cert-message"></description>
+ </vbox>
+
+ <separator />
+
+ <!-- checkboxes for trust bits
+ - "do you want to?"
+ - * trust for SSL
+ - * trust for email
+ -->
+ <vbox>
+ <description id="trustHeader" />
+ <checkbox data-l10n-id="download-cert-trust-ssl" id="trustSSL" />
+ <checkbox data-l10n-id="download-cert-trust-email" id="trustEmail" />
+ </vbox>
+
+ <separator />
+
+ <vbox>
+ <description data-l10n-id="download-cert-message-desc"></description>
+ <separator />
+ <hbox>
+ <button
+ id="viewC-button"
+ data-l10n-id="download-cert-view-cert"
+ oncommand="viewCert();"
+ />
+ <description
+ style="margin: 4px"
+ data-l10n-id="download-cert-view-text"
+ ></description>
+ </hbox>
+ </vbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/editcacert.js b/security/manager/pki/resources/content/editcacert.js
new file mode 100644
index 0000000000..ecb6b3e3ce
--- /dev/null
+++ b/security/manager/pki/resources/content/editcacert.js
@@ -0,0 +1,52 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+/**
+ * Cert to edit the trust of.
+ *
+ * @type {nsIX509Cert}
+ */
+var gCert = window.arguments[0];
+
+document.addEventListener("DOMContentLoaded", init);
+
+function init() {
+ document.addEventListener("dialogaccept", onDialogAccept);
+
+ let sslCheckbox = document.getElementById("trustSSL");
+ sslCheckbox.checked = gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ );
+
+ let emailCheckbox = document.getElementById("trustEmail");
+ emailCheckbox.checked = gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_EMAIL
+ );
+
+ let certMsg = document.getElementById("certmsg");
+ document.l10n.setAttributes(certMsg, "edit-trust-ca", {
+ certName: gCert.commonName,
+ });
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let sslCheckbox = document.getElementById("trustSSL");
+ let emailCheckbox = document.getElementById("trustEmail");
+ let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0;
+ let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0;
+
+ gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT, trustSSL | trustEmail);
+}
diff --git a/security/manager/pki/resources/content/editcacert.xhtml b/security/manager/pki/resources/content/editcacert.xhtml
new file mode 100644
index 0000000000..2d4dabb367
--- /dev/null
+++ b/security/manager/pki/resources/content/editcacert.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="certmgr-edit-ca-cert2"
+ data-l10n-attrs="style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <dialog id="editCaCert" buttons="accept,cancel">
+ <linkset>
+ <html:link
+ rel="localization"
+ href="security/certificates/certManager.ftl"
+ />
+ </linkset>
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/editcacert.js" />
+
+ <description id="certmsg" />
+ <separator />
+ <description data-l10n-id="certmgr-edit-cert-edit-trust" />
+ <vbox align="start">
+ <checkbox data-l10n-id="certmgr-edit-cert-trust-ssl" id="trustSSL" />
+ <checkbox data-l10n-id="certmgr-edit-cert-trust-email" id="trustEmail" />
+ </vbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/exceptionDialog.css b/security/manager/pki/resources/content/exceptionDialog.css
new file mode 100644
index 0000000000..b2c8eb7a7b
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.css
@@ -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/. */
+
+:root {
+ max-width: 40em;
+ min-width: 35em;
+}
+
+#warningSupplemental,
+.description {
+ font-weight: bold;
+}
+
+.longDescription {
+ padding-bottom: 1em;
+}
+
+#warningText,
+#warningSupplemental,
+#headerDescription,
+.longDescription {
+ /* Don't let these affect the min horizontal size of the dialog */
+ contain: inline-size;
+ white-space: pre-wrap;
+}
+
+.description:empty,
+.longDescription:empty {
+ display: none;
+}
+
+#locationTextBox {
+ flex: 1;
+}
diff --git a/security/manager/pki/resources/content/exceptionDialog.js b/security/manager/pki/resources/content/exceptionDialog.js
new file mode 100644
index 0000000000..1953cce8fa
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.js
@@ -0,0 +1,334 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+var gDialog;
+var gSecInfo;
+var gCert;
+var gChecking;
+var gBroken;
+var gNeedReset;
+
+const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
+);
+
+function initExceptionDialog() {
+ gNeedReset = false;
+ gDialog = document.getElementById("exceptiondialog");
+ let warningText = document.getElementById("warningText");
+ document.l10n.setAttributes(warningText, "add-exception-branded-warning");
+ let confirmButton = gDialog.getButton("extra1");
+ let l10nUpdatedElements = [confirmButton, warningText];
+ confirmButton.disabled = true;
+
+ var args = window.arguments;
+ if (args && args[0]) {
+ if (args[0].location) {
+ // We were pre-seeded with a location.
+ document.getElementById("locationTextBox").value = args[0].location;
+ document.getElementById("checkCertButton").disabled = false;
+
+ if (args[0].securityInfo) {
+ gSecInfo = args[0].securityInfo;
+ gCert = gSecInfo.serverCert;
+ gBroken = true;
+ l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());
+ } else if (args[0].prefetchCert) {
+ // We can optionally pre-fetch the certificate too. Don't do this
+ // synchronously, since it would prevent the window from appearing
+ // until the fetch is completed, which could be multiple seconds.
+ // Instead, let's use a timer to spawn the actual fetch, but update
+ // the dialog to "checking..." state right away, so that the UI
+ // is appropriately responsive. Bug 453855
+ document.getElementById("checkCertButton").disabled = true;
+ gChecking = true;
+ l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());
+
+ window.setTimeout(checkCert, 0);
+ }
+ }
+
+ // Set out parameter to false by default
+ args[0].exceptionAdded = false;
+ }
+
+ for (let id of [
+ "warningSupplemental",
+ "certLocationLabel",
+ "checkCertButton",
+ "statusDescription",
+ "statusLongDescription",
+ "viewCertButton",
+ "permanent",
+ ]) {
+ let element = document.getElementById(id);
+ l10nUpdatedElements.push(element);
+ }
+
+ document.l10n
+ .translateElements(l10nUpdatedElements)
+ .then(() => window.sizeToContent());
+
+ document.addEventListener("dialogextra1", addException);
+ document.addEventListener("dialogextra2", checkCert);
+}
+
+/**
+ * Helper function for checkCert. Set as the onerror/onload callbacks for an
+ * XMLHttpRequest. Sets gSecInfo, gCert, gBroken, and gChecking according to
+ * the load information from the request. Probably should not be used directly.
+ *
+ * @param {XMLHttpRequest} req
+ * The XMLHttpRequest created and sent by checkCert.
+ * @param {Event} evt
+ * The load or error event.
+ */
+function grabCert(req, evt) {
+ if (req.channel && req.channel.securityInfo) {
+ gSecInfo = req.channel.securityInfo;
+ gCert = gSecInfo ? gSecInfo.serverCert : null;
+ }
+ gBroken = evt.type == "error";
+ gChecking = false;
+ document.l10n
+ .translateElements(updateCertStatus())
+ .then(() => window.sizeToContent());
+}
+
+/**
+ * Attempt to download the certificate for the location specified, and populate
+ * the Certificate Status section with the result.
+ */
+async function checkCert() {
+ gCert = null;
+ gSecInfo = null;
+ gChecking = true;
+ gBroken = false;
+ await document.l10n.translateElements(updateCertStatus());
+ window.sizeToContent();
+
+ let uri = getURI();
+
+ if (uri) {
+ let req = new XMLHttpRequest();
+ req.open("GET", uri.prePath);
+ req.onerror = grabCert.bind(this, req);
+ req.onload = grabCert.bind(this, req);
+ req.send(null);
+ } else {
+ gChecking = false;
+ await document.l10n.translateElements(updateCertStatus());
+ window.sizeToContent();
+ }
+}
+
+/**
+ * Build and return a URI, based on the information supplied in the
+ * Certificate Location fields
+ *
+ * @returns {nsIURI}
+ * URI constructed from the information supplied on success, null
+ * otherwise.
+ */
+function getURI() {
+ // Use fixup service instead of just ioservice's newURI since it's quite
+ // likely that the host will be supplied without a protocol prefix, resulting
+ // in malformed uri exceptions being thrown.
+ let locationTextBox = document.getElementById("locationTextBox");
+ let { preferredURI: uri } = Services.uriFixup.getFixupURIInfo(
+ locationTextBox.value
+ );
+
+ if (!uri) {
+ return null;
+ }
+
+ let mutator = uri.mutate();
+ if (uri.scheme == "http") {
+ mutator.setScheme("https");
+ }
+
+ if (uri.port == -1) {
+ mutator.setPort(443);
+ }
+
+ return mutator.finalize();
+}
+
+function resetDialog() {
+ document.getElementById("viewCertButton").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ setText("headerDescription", "");
+ setText("statusDescription", "");
+ setText("statusLongDescription", "");
+ setText("status2Description", "");
+ setText("status2LongDescription", "");
+ setText("status3Description", "");
+ setText("status3LongDescription", "");
+ window.sizeToContent();
+}
+
+/**
+ * Called by input textboxes to manage UI state
+ */
+function handleTextChange() {
+ var checkCertButton = document.getElementById("checkCertButton");
+ checkCertButton.disabled = !document.getElementById("locationTextBox").value;
+ if (gNeedReset) {
+ gNeedReset = false;
+ resetDialog();
+ }
+}
+
+function updateCertStatus() {
+ var shortDesc, longDesc;
+ let l10nUpdatedElements = [];
+ if (gCert) {
+ if (gBroken) {
+ var mms = "add-exception-domain-mismatch-short";
+ var mml = "add-exception-domain-mismatch-long";
+ var exs = "add-exception-expired-short";
+ var exl = "add-exception-expired-long";
+ var uts = "add-exception-unverified-or-bad-signature-short";
+ var utl = "add-exception-unverified-or-bad-signature-long";
+ if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_TRUST
+ ) {
+ shortDesc = uts;
+ longDesc = utl;
+ } else if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_DOMAIN
+ ) {
+ shortDesc = mms;
+ longDesc = mml;
+ } else if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_TIME
+ ) {
+ shortDesc = exs;
+ longDesc = exl;
+ }
+ // In these cases, we do want to enable the "Add Exception" button
+ gDialog.getButton("extra1").disabled = false;
+
+ // If the Private Browsing service is available and the mode is active,
+ // don't store permanent exceptions, since they would persist after
+ // private browsing mode was disabled.
+ var inPrivateBrowsing = inPrivateBrowsingMode();
+ var pe = document.getElementById("permanent");
+ pe.disabled = inPrivateBrowsing;
+ pe.checked = !inPrivateBrowsing;
+
+ let headerDescription = document.getElementById("headerDescription");
+ document.l10n.setAttributes(
+ headerDescription,
+ "add-exception-invalid-header"
+ );
+ l10nUpdatedElements.push(headerDescription);
+ } else {
+ shortDesc = "add-exception-valid-short";
+ longDesc = "add-exception-valid-long";
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ }
+
+ // We're done checking the certificate, so allow the user to check it again.
+ document.getElementById("checkCertButton").disabled = false;
+ document.getElementById("viewCertButton").disabled = false;
+
+ // Notify observers about the availability of the certificate
+ Services.obs.notifyObservers(null, "cert-exception-ui-ready");
+ } else if (gChecking) {
+ shortDesc = "add-exception-checking-short";
+ longDesc = "add-exception-checking-long";
+ // We're checking the certificate, so we disable the Get Certificate
+ // button to make sure that the user can't interrupt the process and
+ // trigger another certificate fetch.
+ document.getElementById("checkCertButton").disabled = true;
+ document.getElementById("viewCertButton").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ } else {
+ shortDesc = "add-exception-no-cert-short";
+ longDesc = "add-exception-no-cert-long";
+ // We're done checking the certificate, so allow the user to check it again.
+ document.getElementById("checkCertButton").disabled = false;
+ document.getElementById("viewCertButton").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ }
+ let statusDescription = document.getElementById("statusDescription");
+ let statusLongDescription = document.getElementById("statusLongDescription");
+ document.l10n.setAttributes(statusDescription, shortDesc);
+ document.l10n.setAttributes(statusLongDescription, longDesc);
+ l10nUpdatedElements.push(statusDescription);
+ l10nUpdatedElements.push(statusLongDescription);
+
+ gNeedReset = true;
+ return l10nUpdatedElements;
+}
+
+/**
+ * Handle user request to display certificate details
+ */
+function viewCertButtonClick() {
+ if (gCert) {
+ viewCertHelper(this, gCert);
+ }
+}
+
+/**
+ * Handle user request to add an exception for the specified cert
+ */
+function addException() {
+ if (!gCert || !gSecInfo) {
+ return;
+ }
+
+ var overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
+ Ci.nsICertOverrideService
+ );
+ var flags = 0;
+ if (gSecInfo.isUntrusted) {
+ flags |= overrideService.ERROR_UNTRUSTED;
+ }
+ if (gSecInfo.isDomainMismatch) {
+ flags |= overrideService.ERROR_MISMATCH;
+ }
+ if (gSecInfo.isNotValidAtThisTime) {
+ flags |= overrideService.ERROR_TIME;
+ }
+
+ var permanentCheckbox = document.getElementById("permanent");
+ var shouldStorePermanently =
+ permanentCheckbox.checked && !inPrivateBrowsingMode();
+ var uri = getURI();
+ overrideService.rememberValidityOverride(
+ uri.asciiHost,
+ uri.port,
+ {},
+ gCert,
+ flags,
+ !shouldStorePermanently
+ );
+
+ let args = window.arguments;
+ if (args && args[0]) {
+ args[0].exceptionAdded = true;
+ }
+
+ gDialog.acceptDialog();
+}
+
+/**
+ * @returns {boolean} Whether this dialog is in private browsing mode.
+ */
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isWindowPrivate(window);
+}
diff --git a/security/manager/pki/resources/content/exceptionDialog.xhtml b/security/manager/pki/resources/content/exceptionDialog.xhtml
new file mode 100644
index 0000000000..56be4bc5cb
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.xhtml
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://pippki/content/exceptionDialog.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window windowtype="mozilla:exceptiondialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="exception-mgr"
+ onload="initExceptionDialog();">
+<dialog id="exceptiondialog"
+ buttonidextra1="exception-mgr-extra-button"
+ buttons="cancel,extra1,extra2"
+ defaultButton="extra2">
+
+ <linkset>
+ <html:link rel="localization" href="branding/brand.ftl"/>
+ <html:link rel="localization" href="security/certificates/certManager.ftl"/>
+ </linkset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+
+ <script src="chrome://pippki/content/pippki.js"/>
+ <script src="chrome://pippki/content/exceptionDialog.js"/>
+
+ <hbox>
+ <vbox>
+#ifdef MOZ_WIDGET_GTK
+ <image src="moz-icon://stock/gtk-dialog-warning?size=dialog"/>
+#else
+ <image src="chrome://global/skin/icons/warning-large.png"/>
+#endif
+ <spacer flex="1"/>
+ </vbox>
+ <vbox flex="1">
+ <!-- Note that because of the styling, there must be no whitespace within
+ the description tags -->
+ <description id="warningText"/>
+ <description id="warningSupplemental"
+ data-l10n-id="exception-mgr-supplemental-warning"/>
+ </vbox>
+ </hbox>
+
+ <hbox align="center">
+ <label control="locationTextBox"
+ id="certLocationLabel"
+ data-l10n-id="exception-mgr-cert-location-url"/>
+ <html:input id="locationTextBox"
+ oninput="handleTextChange();"
+ value="https://"
+ class="uri-element"/>
+ <button id="checkCertButton"
+ disabled="true"
+ dlgtype="extra2"
+ data-l10n-id="exception-mgr-cert-location-download"/>
+ </hbox>
+
+ <hbox align="center">
+ <description id="headerDescription"
+ flex="1"/>
+ <button id="viewCertButton"
+ data-l10n-id="exception-mgr-cert-status-view-cert"
+ disabled="true"
+ oncommand="viewCertButtonClick();"/>
+ </hbox>
+ <description id="statusDescription"
+ class="description"/>
+ <description id="statusLongDescription"
+ class="longDescription"/>
+ <description id="status2Description"
+ class="description"/>
+ <description id="status2LongDescription"
+ class="longDescription"/>
+ <description id="status3Description"
+ class="description"/>
+ <description id="status3LongDescription"
+ class="longDescription"/>
+ <checkbox id="permanent"
+ disabled="true"
+ data-l10n-id="exception-mgr-permanent"/>
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/load_device.js b/security/manager/pki/resources/content/load_device.js
new file mode 100644
index 0000000000..0f77e1f1bd
--- /dev/null
+++ b/security/manager/pki/resources/content/load_device.js
@@ -0,0 +1,75 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+document.addEventListener("dialogaccept", onDialogAccept);
+
+/**
+ * @file Implements the functionality of load_device.xhtml: a dialog that allows
+ * a PKCS #11 module to be loaded into Firefox.
+ */
+
+async function onBrowseBtnPress() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [loadPK11ModuleFilePickerTitle] = await document.l10n.formatValues([
+ { id: "load-pk11-module-file-picker-title" },
+ ]);
+ fp.init(window, loadPK11ModuleFilePickerTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ document.getElementById("device_path").value = fp.file.path;
+ }
+
+ // This notification gets sent solely for test purposes. It should not be
+ // used by production code.
+ Services.obs.notifyObservers(window, "LoadPKCS11Module:FilePickHandled");
+ });
+}
+
+/**
+ * ondialogaccept() handler.
+ *
+ * @param {object} event
+ * The event causing this handler function to be called.
+ */
+function onDialogAccept(event) {
+ let nameBox = document.getElementById("device_name");
+ let pathBox = document.getElementById("device_path");
+ let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
+ Ci.nsIPKCS11ModuleDB
+ );
+
+ try {
+ pkcs11ModuleDB.addModule(nameBox.value, pathBox.value, 0, 0);
+ } catch (e) {
+ addModuleFailure("add-module-failure");
+ event.preventDefault();
+ }
+}
+
+async function addModuleFailure(l10nID) {
+ let [AddModuleFailure] = await document.l10n.formatValues([{ id: l10nID }]);
+ alertPromptService(null, AddModuleFailure);
+}
+
+function validateModuleName() {
+ let name = document.getElementById("device_name").value;
+ let helpText = document.getElementById("helpText");
+ helpText.value = "";
+ let dialogNode = document.querySelector("dialog");
+ dialogNode.removeAttribute("buttondisabledaccept");
+ if (name == "") {
+ document.l10n.setAttributes(helpText, "load-module-help-empty-module-name");
+ dialogNode.setAttribute("buttondisabledaccept", true);
+ }
+ if (name == "Root Certs") {
+ document.l10n.setAttributes(
+ helpText,
+ "load-module-help-root-certs-module-name"
+ );
+ dialogNode.setAttribute("buttondisabledaccept", true);
+ }
+}
diff --git a/security/manager/pki/resources/content/load_device.xhtml b/security/manager/pki/resources/content/load_device.xhtml
new file mode 100644
index 0000000000..440b8b1f6b
--- /dev/null
+++ b/security/manager/pki/resources/content/load_device.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="load-device"
+>
+ <dialog id="loaddevice" buttons="accept,cancel">
+ <linkset>
+ <html:link
+ rel="localization"
+ href="security/certificates/deviceManager.ftl"
+ />
+ </linkset>
+
+ <script src="chrome://global/content/globalOverlay.js" />
+ <script src="chrome://global/content/editMenuOverlay.js" />
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/load_device.js" />
+
+ <html:style>
+ #device_name, #device_path { flex: 1; }
+ </html:style>
+
+ <description data-l10n-id="load-device-info"></description>
+ <hbox align="center">
+ <label data-l10n-id="load-device-modname" control="device_name" />
+ <html:input
+ id="device_name"
+ data-l10n-id="load-device-modname-default"
+ data-l10n-attrs="value"
+ onchange="validateModuleName();"
+ />
+ </hbox>
+ <hbox align="center">
+ <label data-l10n-id="load-device-filename" control="device_path" />
+ <html:input id="device_path" />
+ <button
+ id="browse"
+ flex="1"
+ data-l10n-id="load-device-browse"
+ oncommand="onBrowseBtnPress();"
+ />
+ </hbox>
+ <label id="helpText" value="" />
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/pippki.js b/security/manager/pki/resources/content/pippki.js
new file mode 100644
index 0000000000..8094074281
--- /dev/null
+++ b/security/manager/pki/resources/content/pippki.js
@@ -0,0 +1,300 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+
+/*
+ * These are helper functions to be included
+ * pippki UI js files.
+ */
+
+function setText(id, value) {
+ let element = document.getElementById(id);
+ if (!element) {
+ return;
+ }
+ if (element.hasChildNodes()) {
+ element.firstChild.remove();
+ }
+ element.appendChild(document.createTextNode(value));
+}
+
+async function viewCertHelper(parent, cert, openingOption = "tab") {
+ if (!cert) {
+ return;
+ }
+
+ let win = Services.wm.getMostRecentBrowserWindow();
+ let results = await asyncDetermineUsages(cert);
+ let chain = getBestChain(results);
+ if (!chain) {
+ chain = [cert];
+ }
+ let certs = chain.map(elem => encodeURIComponent(elem.getBase64DERString()));
+ let certsStringURL = certs.map(elem => `cert=${elem}`);
+ certsStringURL = certsStringURL.join("&");
+ let url = `about:certificate?${certsStringURL}`;
+ let opened = win.switchToTabHavingURI(url, false, {});
+ if (!opened) {
+ win.openTrustedLinkIn(url, openingOption);
+ }
+}
+
+function getPKCS7Array(certArray) {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let pkcs7String = certdb.asPKCS7Blob(certArray);
+ let pkcs7Array = new Uint8Array(pkcs7String.length);
+ for (let i = 0; i < pkcs7Array.length; i++) {
+ pkcs7Array[i] = pkcs7String.charCodeAt(i);
+ }
+ return pkcs7Array;
+}
+
+function getPEMString(cert) {
+ var derb64 = cert.getBase64DERString();
+ // Wrap the Base64 string into lines of 64 characters with CRLF line breaks
+ // (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return (
+ "-----BEGIN CERTIFICATE-----\r\n" +
+ wrapped +
+ "\r\n-----END CERTIFICATE-----\r\n"
+ );
+}
+
+function alertPromptService(title, message) {
+ // XXX Bug 1425832 - Using Services.prompt here causes tests to report memory
+ // leaks.
+ // eslint-disable-next-line mozilla/use-services
+ var ps = Cc["@mozilla.org/prompter;1"].getService(Ci.nsIPromptService);
+ ps.alert(window, title, message);
+}
+
+const DEFAULT_CERT_EXTENSION = "crt";
+
+/**
+ * Generates a filename for a cert suitable to set as the |defaultString|
+ * attribute on an Ci.nsIFilePicker.
+ *
+ * @param {nsIX509Cert} cert
+ * The cert to generate a filename for.
+ * @returns {string}
+ * Generated filename.
+ */
+function certToFilename(cert) {
+ let filename = cert.displayName;
+
+ // Remove unneeded and/or unsafe characters.
+ filename = filename
+ .replace(/\s/g, "")
+ .replace(/\./g, "_")
+ .replace(/\\/g, "")
+ .replace(/\//g, "");
+
+ // Ci.nsIFilePicker.defaultExtension is more of a suggestion to some
+ // implementations, so we include the extension in the file name as well. This
+ // is what the documentation for Ci.nsIFilePicker.defaultString says we should do
+ // anyways.
+ return `${filename}.${DEFAULT_CERT_EXTENSION}`;
+}
+
+async function exportToFile(parent, cert) {
+ if (!cert) {
+ return;
+ }
+
+ let results = await asyncDetermineUsages(cert);
+ let chain = getBestChain(results);
+ if (!chain) {
+ chain = [cert];
+ }
+
+ let formats = {
+ base64: "*.crt; *.pem",
+ "base64-chain": "*.crt; *.pem",
+ der: "*.der",
+ pkcs7: "*.p7c",
+ "pkcs7-chain": "*.p7c",
+ };
+ let [saveCertAs, ...formatLabels] = await document.l10n.formatValues(
+ ["save-cert-as", ...Object.keys(formats).map(f => "cert-format-" + f)].map(
+ id => ({ id })
+ )
+ );
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(parent, saveCertAs, Ci.nsIFilePicker.modeSave);
+ fp.defaultString = certToFilename(cert);
+ fp.defaultExtension = DEFAULT_CERT_EXTENSION;
+ for (let format of Object.values(formats)) {
+ fp.appendFilter(formatLabels.shift(), format);
+ }
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ let filePickerResult = await new Promise(resolve => {
+ fp.open(resolve);
+ });
+
+ if (
+ filePickerResult != Ci.nsIFilePicker.returnOK &&
+ filePickerResult != Ci.nsIFilePicker.returnReplace
+ ) {
+ return;
+ }
+
+ var content = "";
+ switch (fp.filterIndex) {
+ case 1:
+ content = getPEMString(cert);
+ for (let i = 1; i < chain.length; i++) {
+ content += getPEMString(chain[i]);
+ }
+ break;
+ case 2:
+ // IOUtils.write requires a typed array.
+ // nsIX509Cert.getRawDER() returns an array (not a typed array), so we
+ // convert it here.
+ content = Uint8Array.from(cert.getRawDER());
+ break;
+ case 3:
+ // getPKCS7Array returns a typed array already, so no conversion is
+ // necessary.
+ content = getPKCS7Array([cert]);
+ break;
+ case 4:
+ content = getPKCS7Array(chain);
+ break;
+ case 0:
+ default:
+ content = getPEMString(cert);
+ break;
+ }
+
+ if (typeof content === "string") {
+ content = new TextEncoder().encode(content);
+ }
+
+ try {
+ await IOUtils.write(fp.file.path, content);
+ } catch (ex) {
+ let title = await document.l10n.formatValue("write-file-failure");
+ alertPromptService(title, ex.toString());
+ }
+ if (Cu.isInAutomation) {
+ Services.obs.notifyObservers(null, "cert-export-finished");
+ }
+}
+
+const PRErrorCodeSuccess = 0;
+
+// Certificate usages we care about in the certificate viewer.
+const certificateUsageSSLClient = 0x0001;
+const certificateUsageSSLServer = 0x0002;
+const certificateUsageSSLCA = 0x0008;
+const certificateUsageEmailSigner = 0x0010;
+const certificateUsageEmailRecipient = 0x0020;
+
+// A map from the name of a certificate usage to the value of the usage.
+// Useful for printing debugging information and for enumerating all supported
+// usages.
+const certificateUsages = {
+ certificateUsageSSLClient,
+ certificateUsageSSLServer,
+ certificateUsageSSLCA,
+ certificateUsageEmailSigner,
+ certificateUsageEmailRecipient,
+};
+
+/**
+ * Returns a promise that will resolve with a results array consisting of what
+ * usages the given certificate successfully verified for.
+ *
+ * @param {nsIX509Cert} cert
+ * The certificate to determine valid usages for.
+ * @returns {Promise}
+ * A promise that will resolve with the results of the verifications.
+ */
+function asyncDetermineUsages(cert) {
+ let promises = [];
+ let now = Date.now() / 1000;
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ Object.keys(certificateUsages).forEach(usageString => {
+ promises.push(
+ new Promise((resolve, reject) => {
+ let usage = certificateUsages[usageString];
+ certdb.asyncVerifyCertAtTime(
+ cert,
+ usage,
+ 0,
+ null,
+ now,
+ (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
+ resolve({
+ usageString,
+ errorCode: aPRErrorCode,
+ chain: aVerifiedChain,
+ });
+ }
+ );
+ })
+ );
+ });
+ return Promise.all(promises);
+}
+
+/**
+ * Given a results array, returns the "best" verified certificate chain. Since
+ * the primary use case is for TLS server certificates in Firefox, such a
+ * verified chain will be returned if present. Otherwise, the priority is: TLS
+ * client certificate, email signer, email recipient, CA. Returns null if no
+ * usage verified successfully.
+ *
+ * @param {Array} results
+ * An array of results from `asyncDetermineUsages`. See `displayUsages`.
+ * @returns {Array} An array of `nsIX509Cert` representing the verified
+ * certificate chain for the given usage, or null if there is none.
+ */
+function getBestChain(results) {
+ let usages = [
+ certificateUsageSSLServer,
+ certificateUsageSSLClient,
+ certificateUsageEmailSigner,
+ certificateUsageEmailRecipient,
+ certificateUsageSSLCA,
+ ];
+ for (let usage of usages) {
+ let chain = getChainForUsage(results, usage);
+ if (chain) {
+ return chain;
+ }
+ }
+ return null;
+}
+
+/**
+ * Given a results array, returns the chain corresponding to the desired usage,
+ * if verifying for that usage succeeded. Returns null otherwise.
+ *
+ * @param {Array} results
+ * An array of results from `asyncDetermineUsages`. See `displayUsages`.
+ * @param {number} usage
+ * A numerical value corresponding to a usage. See `certificateUsages`.
+ * @returns {Array} An array of `nsIX509Cert` representing the verified
+ * certificate chain for the given usage, or null if there is none.
+ */
+function getChainForUsage(results, usage) {
+ for (let result of results) {
+ if (
+ certificateUsages[result.usageString] == usage &&
+ result.errorCode == PRErrorCodeSuccess
+ ) {
+ return result.chain;
+ }
+ }
+ return null;
+}
diff --git a/security/manager/pki/resources/content/resetpassword.js b/security/manager/pki/resources/content/resetpassword.js
new file mode 100644
index 0000000000..30db349794
--- /dev/null
+++ b/security/manager/pki/resources/content/resetpassword.js
@@ -0,0 +1,28 @@
+/* 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-globals-from pippki.js */
+"use strict";
+
+document.addEventListener("dialogaccept", resetPassword);
+
+function resetPassword() {
+ var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
+ Ci.nsIPK11TokenDB
+ );
+ var token = pk11db.getInternalKeyToken();
+ token.reset();
+
+ try {
+ Services.logins.removeAllUserFacingLogins();
+ } catch (e) {}
+
+ let l10n = new Localization(["security/pippki/pippki.ftl"], true);
+ if (l10n) {
+ Services.prompt.alert(
+ window,
+ l10n.formatValueSync("pippki-reset-password-confirmation-title"),
+ l10n.formatValueSync("pippki-reset-password-confirmation-message")
+ );
+ }
+}
diff --git a/security/manager/pki/resources/content/resetpassword.xhtml b/security/manager/pki/resources/content/resetpassword.xhtml
new file mode 100644
index 0000000000..d83fa05bd6
--- /dev/null
+++ b/security/manager/pki/resources/content/resetpassword.xhtml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="reset-primary-password-window2"
+ data-l10n-attrs="title, style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <dialog
+ id="reset_password"
+ buttons="accept,cancel"
+ buttonidaccept="reset-password-button-label"
+ defaultButton="cancel"
+ >
+ <linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl" />
+ </linkset>
+
+ <stringbundle
+ id="pippki_bundle"
+ src="chrome://pippki/locale/pippki.properties"
+ />
+
+ <script src="chrome://pippki/content/pippki.js" />
+ <script src="chrome://pippki/content/resetpassword.js" />
+
+ <hbox flex="1">
+ <vbox>
+ <image class="alert-icon" style="margin: 5px" />
+ </vbox>
+ <vbox style="margin: 5px" flex="1">
+ <hbox flex="1">
+ <vbox flex="1">
+ <description
+ data-l10n-id="reset-primary-password-text"
+ ></description>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/content/setp12password.js b/security/manager/pki/resources/content/setp12password.js
new file mode 100644
index 0000000000..14200c36ce
--- /dev/null
+++ b/security/manager/pki/resources/content/setp12password.js
@@ -0,0 +1,127 @@
+/* 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";
+
+/**
+ * @file Implements the functionality of setp12password.xhtml: a dialog that lets
+ * the user confirm the password to set on a PKCS #12 file.
+ * @param {nsISupports} window.arguments.0
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of SetP12PasswordReturnValues.
+ */
+
+/**
+ * @typedef SetP12PasswordReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} confirmedPassword
+ * Set to true if the user entered two matching passwords and
+ * confirmed the dialog.
+ * @property {string} password
+ * The password the user entered. Undefined value if
+ * |confirmedPassword| is not true.
+ */
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ // Ensure the first password textbox has focus.
+ document.getElementById("pw1").focus();
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let password = document.getElementById("pw1").value;
+
+ let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("confirmedPassword", true);
+ retVals.setPropertyAsAString("password", password);
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("confirmedPassword", false);
+}
+
+/**
+ * Calculates the strength of the given password, suitable for use in updating
+ * a progress bar that represents said strength.
+ *
+ * The strength of the password is calculated by checking the number of:
+ * - Characters
+ * - Numbers
+ * - Non-alphanumeric chars
+ * - Upper case characters
+ *
+ * @param {string} password
+ * The password to calculate the strength of.
+ * @returns {number}
+ * The strength of the password in the range [0, 100].
+ */
+function getPasswordStrength(password) {
+ let lengthStrength = password.length;
+ if (lengthStrength > 5) {
+ lengthStrength = 5;
+ }
+
+ let nonNumericChars = password.replace(/[0-9]/g, "");
+ let numericStrength = password.length - nonNumericChars.length;
+ if (numericStrength > 3) {
+ numericStrength = 3;
+ }
+
+ let nonSymbolChars = password.replace(/\W/g, "");
+ let symbolStrength = password.length - nonSymbolChars.length;
+ if (symbolStrength > 3) {
+ symbolStrength = 3;
+ }
+
+ let nonUpperAlphaChars = password.replace(/[A-Z]/g, "");
+ let upperAlphaStrength = password.length - nonUpperAlphaChars.length;
+ if (upperAlphaStrength > 3) {
+ upperAlphaStrength = 3;
+ }
+
+ let strength =
+ lengthStrength * 10 -
+ 20 +
+ numericStrength * 10 +
+ symbolStrength * 15 +
+ upperAlphaStrength * 10;
+ if (strength < 0) {
+ strength = 0;
+ }
+ if (strength > 100) {
+ strength = 100;
+ }
+
+ return strength;
+}
+
+/**
+ * oninput() handler for both password textboxes.
+ *
+ * @param {boolean} recalculatePasswordStrength
+ * Whether to recalculate the strength of the first password.
+ */
+function onPasswordInput(recalculatePasswordStrength) {
+ let pw1 = document.getElementById("pw1").value;
+
+ if (recalculatePasswordStrength) {
+ document.getElementById("pwmeter").value = getPasswordStrength(pw1);
+ }
+
+ // Disable the accept button if the two passwords don't match, and enable it
+ // if the passwords do match.
+ let pw2 = document.getElementById("pw2").value;
+ document.getElementById("setp12password").getButton("accept").disabled =
+ pw1 != pw2;
+}
diff --git a/security/manager/pki/resources/content/setp12password.xhtml b/security/manager/pki/resources/content/setp12password.xhtml
new file mode 100644
index 0000000000..f4ba567c0e
--- /dev/null
+++ b/security/manager/pki/resources/content/setp12password.xhtml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="set-password-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="width: 48em"
+ onload="onLoad();"
+>
+ <dialog id="setp12password" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl" />
+ </linkset>
+
+ <script src="chrome://global/content/globalOverlay.js" />
+ <script src="chrome://global/content/editMenuOverlay.js" />
+
+ <script src="chrome://pippki/content/setp12password.js" />
+
+ <description data-l10n-id="set-password-message"></description>
+ <separator />
+ <vbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="set-password-backup-pw" />
+ <html:input id="pw1" type="password" oninput="onPasswordInput(true);" />
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="set-password-repeat-backup-pw" />
+ <html:input
+ id="pw2"
+ type="password"
+ oninput="onPasswordInput(false);"
+ />
+ </hbox>
+ </vbox>
+ <separator />
+ <description data-l10n-id="set-password-reminder"></description>
+ <separator />
+
+ <vbox style="margin: 6px">
+ <html:label
+ for="pwmeter"
+ style="display: flex"
+ data-l10n-id="password-quality-meter"
+ ></html:label>
+ <html:progress id="pwmeter" value="0" max="100" />
+ </vbox>
+ </dialog>
+</window>
diff --git a/security/manager/pki/resources/jar.mn b/security/manager/pki/resources/jar.mn
new file mode 100644
index 0000000000..a855664601
--- /dev/null
+++ b/security/manager/pki/resources/jar.mn
@@ -0,0 +1,32 @@
+# 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/.
+
+pippki.jar:
+% content pippki %content/pippki/
+ content/pippki/certManager.js (content/certManager.js)
+ content/pippki/certManager.css (content/certManager.css)
+ content/pippki/certManager.xhtml (content/certManager.xhtml)
+ content/pippki/changepassword.js (content/changepassword.js)
+ content/pippki/changepassword.xhtml (content/changepassword.xhtml)
+ content/pippki/clientauthask.js (content/clientauthask.js)
+ content/pippki/clientauthask.xhtml (content/clientauthask.xhtml)
+ content/pippki/deletecert.js (content/deletecert.js)
+ content/pippki/deletecert.css (content/deletecert.css)
+ content/pippki/deletecert.xhtml (content/deletecert.xhtml)
+ content/pippki/device_manager.js (content/device_manager.js)
+ content/pippki/device_manager.xhtml (content/device_manager.xhtml)
+ content/pippki/downloadcert.js (content/downloadcert.js)
+ content/pippki/downloadcert.xhtml (content/downloadcert.xhtml)
+ content/pippki/editcacert.js (content/editcacert.js)
+ content/pippki/editcacert.xhtml (content/editcacert.xhtml)
+ content/pippki/exceptionDialog.css (content/exceptionDialog.css)
+ content/pippki/exceptionDialog.js (content/exceptionDialog.js)
+* content/pippki/exceptionDialog.xhtml (content/exceptionDialog.xhtml)
+ content/pippki/load_device.js (content/load_device.js)
+ content/pippki/load_device.xhtml (content/load_device.xhtml)
+ content/pippki/pippki.js (content/pippki.js)
+ content/pippki/resetpassword.js (content/resetpassword.js)
+ content/pippki/resetpassword.xhtml (content/resetpassword.xhtml)
+ content/pippki/setp12password.js (content/setp12password.js)
+ content/pippki/setp12password.xhtml (content/setp12password.xhtml)
diff --git a/security/manager/pki/resources/moz.build b/security/manager/pki/resources/moz.build
new file mode 100644
index 0000000000..d988c0ff9b
--- /dev/null
+++ b/security/manager/pki/resources/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ["jar.mn"]