From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- security/manager/pki/components.conf | 21 + security/manager/pki/moz.build | 34 + security/manager/pki/nsIASN1Tree.idl | 24 + security/manager/pki/nsNSSDialogHelper.cpp | 45 ++ security/manager/pki/nsNSSDialogHelper.h | 39 ++ security/manager/pki/nsNSSDialogs.cpp | 314 +++++++++ security/manager/pki/nsNSSDialogs.h | 40 ++ .../manager/pki/resources/content/certManager.css | 34 + .../manager/pki/resources/content/certManager.js | 769 +++++++++++++++++++++ .../pki/resources/content/certManager.xhtml | 330 +++++++++ .../pki/resources/content/changepassword.js | 212 ++++++ .../pki/resources/content/changepassword.xhtml | 77 +++ .../manager/pki/resources/content/clientauthask.js | 178 +++++ .../pki/resources/content/clientauthask.xhtml | 55 ++ .../manager/pki/resources/content/deletecert.css | 19 + .../manager/pki/resources/content/deletecert.js | 121 ++++ .../manager/pki/resources/content/deletecert.xhtml | 33 + .../pki/resources/content/device_manager.js | 433 ++++++++++++ .../pki/resources/content/device_manager.xhtml | 113 +++ .../manager/pki/resources/content/downloadcert.js | 83 +++ .../pki/resources/content/downloadcert.xhtml | 66 ++ .../manager/pki/resources/content/editcacert.js | 52 ++ .../manager/pki/resources/content/editcacert.xhtml | 35 + .../pki/resources/content/exceptionDialog.css | 35 + .../pki/resources/content/exceptionDialog.js | 334 +++++++++ .../pki/resources/content/exceptionDialog.xhtml | 88 +++ .../manager/pki/resources/content/load_device.js | 75 ++ .../pki/resources/content/load_device.xhtml | 55 ++ security/manager/pki/resources/content/pippki.js | 300 ++++++++ .../manager/pki/resources/content/resetpassword.js | 28 + .../pki/resources/content/resetpassword.xhtml | 49 ++ .../pki/resources/content/setp12password.js | 127 ++++ .../pki/resources/content/setp12password.xhtml | 56 ++ security/manager/pki/resources/jar.mn | 32 + security/manager/pki/resources/moz.build | 7 + 35 files changed, 4313 insertions(+) create mode 100644 security/manager/pki/components.conf create mode 100644 security/manager/pki/moz.build create mode 100644 security/manager/pki/nsIASN1Tree.idl create mode 100644 security/manager/pki/nsNSSDialogHelper.cpp create mode 100644 security/manager/pki/nsNSSDialogHelper.h create mode 100644 security/manager/pki/nsNSSDialogs.cpp create mode 100644 security/manager/pki/nsNSSDialogs.h create mode 100644 security/manager/pki/resources/content/certManager.css create mode 100644 security/manager/pki/resources/content/certManager.js create mode 100644 security/manager/pki/resources/content/certManager.xhtml create mode 100644 security/manager/pki/resources/content/changepassword.js create mode 100644 security/manager/pki/resources/content/changepassword.xhtml create mode 100644 security/manager/pki/resources/content/clientauthask.js create mode 100644 security/manager/pki/resources/content/clientauthask.xhtml create mode 100644 security/manager/pki/resources/content/deletecert.css create mode 100644 security/manager/pki/resources/content/deletecert.js create mode 100644 security/manager/pki/resources/content/deletecert.xhtml create mode 100644 security/manager/pki/resources/content/device_manager.js create mode 100644 security/manager/pki/resources/content/device_manager.xhtml create mode 100644 security/manager/pki/resources/content/downloadcert.js create mode 100644 security/manager/pki/resources/content/downloadcert.xhtml create mode 100644 security/manager/pki/resources/content/editcacert.js create mode 100644 security/manager/pki/resources/content/editcacert.xhtml create mode 100644 security/manager/pki/resources/content/exceptionDialog.css create mode 100644 security/manager/pki/resources/content/exceptionDialog.js create mode 100644 security/manager/pki/resources/content/exceptionDialog.xhtml create mode 100644 security/manager/pki/resources/content/load_device.js create mode 100644 security/manager/pki/resources/content/load_device.xhtml create mode 100644 security/manager/pki/resources/content/pippki.js create mode 100644 security/manager/pki/resources/content/resetpassword.js create mode 100644 security/manager/pki/resources/content/resetpassword.xhtml create mode 100644 security/manager/pki/resources/content/setp12password.js create mode 100644 security/manager/pki/resources/content/setp12password.xhtml create mode 100644 security/manager/pki/resources/jar.mn create mode 100644 security/manager/pki/resources/moz.build (limited to 'security/manager/pki') 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 windowWatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr 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 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 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 parent = do_GetInterface(ctx); + + nsCOMPtr block = + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID); + if (!block) return NS_ERROR_FAILURE; + + nsCOMPtr 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 argArray = nsArrayBase::Create(); + if (!argArray) { + return NS_ERROR_FAILURE; + } + + nsresult rv = argArray->AppendElement(cert); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr retVals = new nsHashPropertyBag(); + rv = argArray->AppendElement(retVals); + if (NS_FAILED(rv)) { + return rv; + } + + // Get the parent window for the dialog + nsCOMPtr 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 argArray = nsArrayBase::Create(); + if (!argArray) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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 organizationVariant = new nsVariant(); + rv = organizationVariant->SetAsAUTF8String(organization); + if (NS_FAILED(rv)) { + return rv; + } + rv = argArray->AppendElement(organizationVariant); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr issuerOrgVariant = new nsVariant(); + rv = issuerOrgVariant->SetAsAUTF8String(issuerOrg); + if (NS_FAILED(rv)) { + return rv; + } + rv = argArray->AppendElement(issuerOrgVariant); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr 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 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 parent = do_GetInterface(ctx); + nsCOMPtr 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 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 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 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 and non-XUL layout + * interactions */ + padding: 0; +} + +/* This matches the 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 @@ + + + + + + + + + + + + + + +