diff options
Diffstat (limited to 'security/manager/pki')
48 files changed, 5521 insertions, 0 deletions
diff --git a/security/manager/pki/moz.build b/security/manager/pki/moz.build new file mode 100644 index 000000000..9a8038922 --- /dev/null +++ b/security/manager/pki/moz.build @@ -0,0 +1,26 @@ +# -*- 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'] + +XPIDL_SOURCES += [ + 'nsIASN1Tree.idl', +] + +XPIDL_MODULE = 'pippki' + +UNIFIED_SOURCES += [ + 'nsASN1Tree.cpp', + 'nsNSSDialogHelper.cpp', + 'nsNSSDialogs.cpp', + 'nsPKIModule.cpp', +] + +LOCAL_INCLUDES += [ + '!/dist/public/nss', +] + +FINAL_LIBRARY = 'xul' diff --git a/security/manager/pki/nsASN1Tree.cpp b/security/manager/pki/nsASN1Tree.cpp new file mode 100644 index 000000000..ce9672d8d --- /dev/null +++ b/security/manager/pki/nsASN1Tree.cpp @@ -0,0 +1,573 @@ +/* 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 "nsASN1Tree.h" + +#include "mozilla/Assertions.h" +#include "nsArrayUtils.h" +#include "nsDebug.h" +#include "nsIMutableArray.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsNSSASN1Tree, nsIASN1Tree, nsITreeView) + +nsNSSASN1Tree::nsNSSASN1Tree() + : mTopNode(nullptr) +{ +} + +nsNSSASN1Tree::~nsNSSASN1Tree() +{ + ClearNodes(); +} + +void +nsNSSASN1Tree::ClearNodesRecursively(myNode* n) +{ + // Note: |n| is allowed to be null. + + myNode *walk = n; + while (walk) { + myNode *kill = walk; + + if (walk->child) { + ClearNodesRecursively(walk->child); + } + + walk = walk->next; + delete kill; + } +} + +void +nsNSSASN1Tree::ClearNodes() +{ + ClearNodesRecursively(mTopNode); + mTopNode = nullptr; +} + +void +nsNSSASN1Tree::InitChildsRecursively(myNode* n) +{ + MOZ_ASSERT(n); + if (!n) { + return; + } + + if (!n->obj) + return; + + n->seq = do_QueryInterface(n->obj); + if (!n->seq) + return; + + // If the object is a sequence, there might still be a reason + // why it should not be displayed as a container. + // If we decide that it has all the properties to justify + // displaying as a container, we will create a new child chain. + // If we decide, it does not make sense to display as a container, + // we forget that it is a sequence by erasing n->seq. + // That way, n->seq and n->child will be either both set or both null. + + bool isContainer; + n->seq->GetIsValidContainer(&isContainer); + if (!isContainer) { + n->seq = nullptr; + return; + } + + nsCOMPtr<nsIMutableArray> asn1Objects; + n->seq->GetASN1Objects(getter_AddRefs(asn1Objects)); + uint32_t numObjects; + asn1Objects->GetLength(&numObjects); + if (!numObjects) { + n->seq = nullptr; + return; + } + + myNode *walk = nullptr; + myNode *prev = nullptr; + for (uint32_t i = 0; i < numObjects; i++) { + if (0 == i) { + n->child = walk = new myNode; + } + else { + walk = new myNode; + } + + walk->parent = n; + if (prev) { + prev->next = walk; + } + + walk->obj = do_QueryElementAt(asn1Objects, i); + + InitChildsRecursively(walk); + + prev = walk; + } +} + +void +nsNSSASN1Tree::InitNodes() +{ + ClearNodes(); + + mTopNode = new myNode; + mTopNode->obj = mASN1Object; + + InitChildsRecursively(mTopNode); +} + +NS_IMETHODIMP +nsNSSASN1Tree::LoadASN1Structure(nsIASN1Object* asn1Object) +{ + // Note: |asn1Object| is allowed to be null. + + // The tree won't automatically re-draw if the contents + // have been changed. So I do a quick test here to let + // me know if I should forced the tree to redraw itself + // by calling RowCountChanged on it. + // + bool redraw = (mASN1Object && mTree); + int32_t rowsToDelete = 0; + + if (redraw) { + // This is the number of rows we will be deleting after + // the contents have changed. + rowsToDelete = 0-CountVisibleNodes(mTopNode); + } + + mASN1Object = asn1Object; + InitNodes(); + + if (redraw) { + // The number of rows in the new content. + int32_t newRows = CountVisibleNodes(mTopNode); + mTree->BeginUpdateBatch(); + // Erase all of the old rows. + mTree->RowCountChanged(0, rowsToDelete); + // Replace them with the new contents + mTree->RowCountChanged(0, newRows); + mTree->EndUpdateBatch(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetRowCount(int32_t* aRowCount) +{ + NS_ENSURE_ARG_POINTER(aRowCount); + + if (mASN1Object) { + *aRowCount = CountVisibleNodes(mTopNode); + } else { + *aRowCount = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetSelection(nsITreeSelection** aSelection) +{ + NS_ENSURE_ARG_POINTER(aSelection); + *aSelection = mSelection; + NS_IF_ADDREF(*aSelection); + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::SetSelection(nsITreeSelection* aSelection) +{ + // Note: |aSelection| is allowed to be null. + mSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetRowProperties(int32_t, nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetCellProperties(int32_t, nsITreeColumn*, nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetColumnProperties(nsITreeColumn*, nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsContainer(int32_t index, bool* _retval) +{ + NS_ENSURE_ARG_MIN(index, 0); + NS_ENSURE_ARG_POINTER(_retval); + + myNode *n = FindNodeFromIndex(index); + if (!n) + return NS_ERROR_FAILURE; + + *_retval = (n->seq != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsContainerOpen(int32_t index, bool* _retval) +{ + NS_ENSURE_ARG_MIN(index, 0); + NS_ENSURE_ARG_POINTER(_retval); + + myNode *n = FindNodeFromIndex(index); + if (!n || !n->seq) + return NS_ERROR_FAILURE; + + return n->seq->GetIsExpanded(_retval); +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsContainerEmpty(int32_t, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsSeparator(int32_t, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetLevel(int32_t index, int32_t* _retval) +{ + NS_ENSURE_ARG_MIN(index, 0); + NS_ENSURE_ARG_POINTER(_retval); + + int32_t nodeLevel; + myNode* n = FindNodeFromIndex(index, nullptr, &nodeLevel); + if (!n) + return NS_ERROR_FAILURE; + + *_retval = nodeLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetImageSrc(int32_t, nsITreeColumn*, nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetProgressMode(int32_t, nsITreeColumn*, int32_t*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetCellValue(int32_t, nsITreeColumn*, nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetCellText(int32_t row, nsITreeColumn*, nsAString& _retval) +{ + NS_ENSURE_ARG_MIN(row, 0); + + _retval.Truncate(); + + myNode* n = FindNodeFromIndex(row); + if (!n) + return NS_ERROR_FAILURE; + + // There's only one column for ASN1 dump. + return n->obj->GetDisplayName(_retval); +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetDisplayData(uint32_t index, nsAString& _retval) +{ + myNode *n = FindNodeFromIndex(index); + if (!n) + return NS_ERROR_FAILURE; + + return n->obj->GetDisplayValue(_retval); +} + +NS_IMETHODIMP +nsNSSASN1Tree::SetTree(nsITreeBoxObject* tree) +{ + // Note: |tree| is allowed to be null. + mTree = tree; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::ToggleOpenState(int32_t index) +{ + NS_ENSURE_ARG_MIN(index, 0); + + myNode *n = FindNodeFromIndex(index); + if (!n) + return NS_ERROR_FAILURE; + + if (!n->seq) + return NS_ERROR_FAILURE; + + bool IsExpanded; + n->seq->GetIsExpanded(&IsExpanded); + int32_t rowCountChange; + if (IsExpanded) { + rowCountChange = -CountVisibleNodes(n->child); + n->seq->SetIsExpanded(false); + } else { + n->seq->SetIsExpanded(true); + rowCountChange = CountVisibleNodes(n->child); + } + if (mTree) + mTree->RowCountChanged(index, rowCountChange); + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::CycleHeader(nsITreeColumn*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::SelectionChanged() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsNSSASN1Tree::CycleCell(int32_t, nsITreeColumn*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsEditable(int32_t, nsITreeColumn*, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsSelectable(int32_t, nsITreeColumn*, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::SetCellValue(int32_t, nsITreeColumn*, const nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::SetCellText(int32_t, nsITreeColumn*, const nsAString&) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::PerformAction(const char16_t*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::PerformActionOnRow(const char16_t*, int32_t) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::PerformActionOnCell(const char16_t*, int32_t, nsITreeColumn*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::CanDrop(int32_t, int32_t, nsIDOMDataTransfer*, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::Drop(int32_t, int32_t, nsIDOMDataTransfer*) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::IsSorted(bool* _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::GetParentIndex(int32_t rowIndex, int32_t* _retval) +{ + NS_ENSURE_ARG_MIN(rowIndex, 0); + NS_ENSURE_ARG_POINTER(_retval); + + int32_t parentIndex = -1; + + myNode *n = FindNodeFromIndex(rowIndex, &parentIndex); + if (!n) + return NS_ERROR_FAILURE; + + *_retval = parentIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSASN1Tree::HasNextSibling(int32_t rowIndex, int32_t afterIndex, + bool* _retval) +{ + NS_ENSURE_ARG_MIN(rowIndex, 0); + NS_ENSURE_ARG_MIN(afterIndex, 0); + NS_ENSURE_ARG_POINTER(_retval); + + myNode *n = FindNodeFromIndex(rowIndex); + if (!n) + return NS_ERROR_FAILURE; + + if (!n->next) { + *_retval = false; + } + else { + int32_t nTotalSize = CountVisibleNodes(n); + int32_t nLastChildPos = rowIndex + nTotalSize -1; + int32_t nextSiblingPos = nLastChildPos +1; + *_retval = (nextSiblingPos > afterIndex); + } + + return NS_OK; +} + +int32_t +nsNSSASN1Tree::CountVisibleNodes(myNode* n) +{ + if (!n) + return 0; + + myNode *walk = n; + int32_t count = 0; + while (walk) { + ++count; + + if (walk->seq) { + bool IsExpanded; + walk->seq->GetIsExpanded(&IsExpanded); + if (IsExpanded) { + count += CountVisibleNodes(walk->child); + } + } + + walk = walk->next; + } + + return count; +} + +// Entry point for find +nsNSSASN1Tree::myNode* +nsNSSASN1Tree::FindNodeFromIndex(int32_t wantedIndex, + int32_t* optionalOutParentIndex, + int32_t* optionalOutLevel) +{ + MOZ_ASSERT(wantedIndex >= 0); + if (wantedIndex < 0) { + return nullptr; + } + + if (0 == wantedIndex) { + if (optionalOutLevel) { + *optionalOutLevel = 0; + } + if (optionalOutParentIndex) { + *optionalOutParentIndex = -1; + } + return mTopNode; + } + + int32_t index = 0; + int32_t level = 0; + return FindNodeFromIndex(mTopNode, wantedIndex, index, level, + optionalOutParentIndex, optionalOutLevel); +} + +// Internal recursive helper function +nsNSSASN1Tree::myNode* +nsNSSASN1Tree::FindNodeFromIndex(myNode* n, int32_t wantedIndex, + int32_t& indexCounter, int32_t& levelCounter, + int32_t* optionalOutParentIndex, + int32_t* optionalOutLevel) +{ + MOZ_ASSERT(wantedIndex >= 0); + MOZ_ASSERT(indexCounter >= 0); + MOZ_ASSERT(levelCounter >= 0); + if (!n || wantedIndex < 0 || indexCounter < 0 || levelCounter < 0) { + return nullptr; + } + + myNode *walk = n; + int32_t parentIndex = indexCounter - 1; + + while (walk) { + if (indexCounter == wantedIndex) { + if (optionalOutLevel) { + *optionalOutLevel = levelCounter; + } + if (optionalOutParentIndex) { + *optionalOutParentIndex = parentIndex; + } + return walk; + } + + if (walk->seq) { + bool IsExpanded; + walk->seq->GetIsExpanded(&IsExpanded); + if (IsExpanded) { + ++indexCounter; // set to walk->child + + ++levelCounter; + myNode* found = FindNodeFromIndex(walk->child, wantedIndex, indexCounter, + levelCounter, optionalOutParentIndex, + optionalOutLevel); + --levelCounter; + + if (found) + return found; + } + } + + walk = walk->next; + if (walk) { + ++indexCounter; + } + } + + return nullptr; +} diff --git a/security/manager/pki/nsASN1Tree.h b/security/manager/pki/nsASN1Tree.h new file mode 100644 index 000000000..bb2325b7a --- /dev/null +++ b/security/manager/pki/nsASN1Tree.h @@ -0,0 +1,72 @@ +/* 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 _NSSASNTREE_H_ +#define _NSSASNTREE_H_ + +#include "nscore.h" +#include "nsIX509Cert.h" +#include "nsIASN1Tree.h" +#include "nsIASN1Object.h" +#include "nsIASN1Sequence.h" +#include "nsITreeView.h" +#include "nsITreeBoxObject.h" +#include "nsITreeSelection.h" +#include "nsCOMPtr.h" + +//4bfaa9f0-1dd2-11b2-afae-a82cbaa0b606 +#define NS_NSSASN1OUTINER_CID { \ + 0x4bfaa9f0, \ + 0x1dd2, \ + 0x11b2, \ + {0xaf,0xae,0xa8,0x2c,0xba,0xa0,0xb6,0x06} \ + } + + +class nsNSSASN1Tree : public nsIASN1Tree +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIASN1TREE + NS_DECL_NSITREEVIEW + + nsNSSASN1Tree(); +protected: + virtual ~nsNSSASN1Tree(); + + class myNode + { + public: + nsCOMPtr<nsIASN1Object> obj; + nsCOMPtr<nsIASN1Sequence> seq; + myNode *child; + myNode *next; + myNode *parent; + + myNode() { + child = next = parent = nullptr; + } + }; + + myNode *mTopNode; + + nsCOMPtr<nsIASN1Object> mASN1Object; + nsCOMPtr<nsITreeSelection> mSelection; + nsCOMPtr<nsITreeBoxObject> mTree; + + void InitNodes(); + void InitChildsRecursively(myNode *n); + + void ClearNodes(); + void ClearNodesRecursively(myNode *n); + + int32_t CountVisibleNodes(myNode *n); + myNode *FindNodeFromIndex(myNode *n, int32_t wantedIndex, + int32_t &index_counter, int32_t &level_counter, + int32_t *optionalOutParentIndex, int32_t *optionalOutLevel); + myNode *FindNodeFromIndex(int32_t wantedIndex, + int32_t *optionalOutParentIndex = nullptr, + int32_t *optionalOutLevel = nullptr); + +}; +#endif //_NSSASNTREE_H_ diff --git a/security/manager/pki/nsIASN1Tree.idl b/security/manager/pki/nsIASN1Tree.idl new file mode 100644 index 000000000..a0e2a265a --- /dev/null +++ b/security/manager/pki/nsIASN1Tree.idl @@ -0,0 +1,25 @@ +/* -*- 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 { + + void loadASN1Structure(in nsIASN1Object asn1Object); + + 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 000000000..1ec161b81 --- /dev/null +++ b/security/manager/pki/nsNSSDialogHelper.cpp @@ -0,0 +1,56 @@ +/* -*- 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 "mozilla/dom/ScriptSettings.h" +#include "nsCOMPtr.h" +#include "nsIDOMWindow.h" +#include "nsIServiceManager.h" +#include "nsIWindowWatcher.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) +{ +#ifdef MOZ_WIDGET_GONK + // On b2g devices, we need to proxy the dialog creation & management + // to Gaia. + return NS_ERROR_NOT_IMPLEMENTED; +#endif + + 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, + url, + "_blank", + 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 000000000..87605da5f --- /dev/null +++ b/security/manager/pki/nsNSSDialogHelper.h @@ -0,0 +1,38 @@ +/* -*- 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 + +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 000000000..831bce57e --- /dev/null +++ b/security/manager/pki/nsNSSDialogs.cpp @@ -0,0 +1,441 @@ +/* -*- 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 "nsEmbedCID.h" +#include "nsHashPropertyBag.h" +#include "nsIDialogParamBlock.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIKeygenThread.h" +#include "nsIPromptService.h" +#include "nsIProtectedAuthThread.h" +#include "nsIWindowWatcher.h" +#include "nsIX509CertDB.h" +#include "nsIX509Cert.h" +#include "nsNSSDialogHelper.h" +#include "nsString.h" +#include "nsVariant.h" + +#define PIPSTRING_BUNDLE_URL "chrome://pippki/locale/pippki.properties" + +nsNSSDialogs::nsNSSDialogs() +{ +} + +nsNSSDialogs::~nsNSSDialogs() +{ +} + +NS_IMPL_ISUPPORTS(nsNSSDialogs, nsITokenPasswordDialogs, + nsICertificateDialogs, + nsIClientAuthDialogs, + nsITokenDialogs, + nsIGeneratingKeypairInfoDialogs) + +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; +} + +nsresult +nsNSSDialogs::SetPassword(nsIInterfaceRequestor *ctx, + const char16_t *tokenName, bool* _canceled) +{ + nsresult rv; + + *_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; + + rv = block->SetString(1, tokenName); + if (NS_FAILED(rv)) return rv; + + rv = nsNSSDialogHelper::openDialog(parent, + "chrome://pippki/content/changepassword.xul", + block); + + if (NS_FAILED(rv)) return rv; + + int32_t status; + + rv = block->GetInt(1, &status); + if (NS_FAILED(rv)) return rv; + + *_canceled = (status == 0)?true:false; + + 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, false); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag(); + rv = argArray->AppendElement(retVals, false); + 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.xul", + argArray); + if (NS_FAILED(rv)) { + return rv; + } + + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("importConfirmed"), + importConfirmed); + if (NS_FAILED(rv)) { + return rv; + } + + *trust = nsIX509CertDB::UNTRUSTED; + if (!*importConfirmed) { + return NS_OK; + } + + bool trustForSSL = false; + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForSSL"), + &trustForSSL); + if (NS_FAILED(rv)) { + return rv; + } + bool trustForEmail = false; + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForEmail"), + &trustForEmail); + if (NS_FAILED(rv)) { + return rv; + } + bool trustForObjSign = false; + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForObjSign"), + &trustForObjSign); + if (NS_FAILED(rv)) { + return rv; + } + + *trust |= trustForSSL ? nsIX509CertDB::TRUSTED_SSL : 0; + *trust |= trustForEmail ? nsIX509CertDB::TRUSTED_EMAIL : 0; + *trust |= trustForObjSign ? nsIX509CertDB::TRUSTED_OBJSIGN : 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsNSSDialogs::ChooseCertificate(nsIInterfaceRequestor* ctx, + const nsACString& hostname, + int32_t port, + const nsACString& organization, + const nsACString& issuerOrg, + nsIArray* certList, + /*out*/ uint32_t* selectedIndex, + /*out*/ bool* certificateChosen) +{ + NS_ENSURE_ARG_POINTER(ctx); + NS_ENSURE_ARG_POINTER(certList); + NS_ENSURE_ARG_POINTER(selectedIndex); + NS_ENSURE_ARG_POINTER(certificateChosen); + + *certificateChosen = 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, false); + 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, false); + 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, false); + 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, false); + if (NS_FAILED(rv)) { + return rv; + } + + rv = argArray->AppendElement(certList, false); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag(); + rv = argArray->AppendElement(retVals, false); + if (NS_FAILED(rv)) { + return rv; + } + + rv = nsNSSDialogHelper::openDialog(nullptr, + "chrome://pippki/content/clientauthask.xul", + argArray); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIClientAuthUserDecision> extraResult = do_QueryInterface(ctx); + if (extraResult) { + bool rememberSelection = false; + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("rememberSelection"), + &rememberSelection); + if (NS_SUCCEEDED(rv)) { + extraResult->SetRememberClientAuthCertificate(rememberSelection); + } + } + + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("certChosen"), + certificateChosen); + if (NS_FAILED(rv)) { + return rv; + } + if (*certificateChosen) { + rv = retVals->GetPropertyAsUint32(NS_LITERAL_STRING("selectedIndex"), + 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.xul", + retVals); + if (NS_FAILED(rv)) { + return rv; + } + + rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("confirmedPassword"), + confirmedPassword); + if (NS_FAILED(rv)) { + return rv; + } + + if (!*confirmedPassword) { + return NS_OK; + } + + return retVals->GetPropertyAsAString(NS_LITERAL_STRING("password"), 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( + u"getPKCS12FilePasswordMessage", getter_Copies(msg)); + if (NS_FAILED(rv)) { + return rv; + } + + // Get the parent window for the dialog + nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx); + bool ignored = false; + char16_t* pwTemp = nullptr; + rv = promptSvc->PromptPassword(parent, nullptr, msg.get(), &pwTemp, nullptr, + &ignored, _retval); + if (NS_FAILED(rv)) { + return rv; + } + + if (*_retval) { + _password.Assign(pwTemp); + free(pwTemp); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNSSDialogs::ViewCert(nsIInterfaceRequestor* ctx, nsIX509Cert* cert) +{ + // |ctx| is allowed to be null. + NS_ENSURE_ARG(cert); + + // Get the parent window for the dialog + nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx); + return nsNSSDialogHelper::openDialog(parent, + "chrome://pippki/content/certViewer.xul", + cert, + false /*modal*/); +} + +NS_IMETHODIMP +nsNSSDialogs::DisplayGeneratingKeypairInfo(nsIInterfaceRequestor *aCtx, nsIKeygenThread *runnable) +{ + nsresult rv; + + // Get the parent window for the dialog + nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(aCtx); + + rv = nsNSSDialogHelper::openDialog(parent, + "chrome://pippki/content/createCertInfo.xul", + runnable); + return rv; +} + +NS_IMETHODIMP +nsNSSDialogs::ChooseToken(nsIInterfaceRequestor *aCtx, const char16_t **aTokenList, uint32_t aCount, char16_t **aTokenChosen, bool *aCanceled) { + nsresult rv; + uint32_t i; + + *aCanceled = false; + + nsCOMPtr<nsIDialogParamBlock> block = + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID); + if (!block) return NS_ERROR_FAILURE; + + block->SetNumberStrings(aCount); + + for (i = 0; i < aCount; i++) { + rv = block->SetString(i, aTokenList[i]); + if (NS_FAILED(rv)) return rv; + } + + rv = block->SetInt(0, aCount); + if (NS_FAILED(rv)) return rv; + + rv = nsNSSDialogHelper::openDialog(nullptr, + "chrome://pippki/content/choosetoken.xul", + block); + if (NS_FAILED(rv)) return rv; + + int32_t status; + + rv = block->GetInt(0, &status); + if (NS_FAILED(rv)) return rv; + + *aCanceled = (status == 0)?true:false; + if (!*aCanceled) { + // retrieve the nickname + rv = block->GetString(0, aTokenChosen); + } + return rv; +} + +NS_IMETHODIMP +nsNSSDialogs::DisplayProtectedAuth(nsIInterfaceRequestor *aCtx, nsIProtectedAuthThread *runnable) +{ + // We cannot use nsNSSDialogHelper here. We cannot allow close widget + // in the window because protected authentication is interruptible + // from user interface and changing nsNSSDialogHelper's static variable + // would not be thread-safe + + nsresult rv = NS_ERROR_FAILURE; + + // Get the parent window for the dialog + nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(aCtx); + + nsCOMPtr<nsIWindowWatcher> windowWatcher = + do_GetService("@mozilla.org/embedcomp/window-watcher;1", &rv); + if (NS_FAILED(rv)) + return rv; + + if (!parent) { + windowWatcher->GetActiveWindow(getter_AddRefs(parent)); + } + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = windowWatcher->OpenWindow(parent, + "chrome://pippki/content/protectedAuth.xul", + "_blank", + "centerscreen,chrome,modal,titlebar,close=no", + runnable, + getter_AddRefs(newWindow)); + + return rv; +} diff --git a/security/manager/pki/nsNSSDialogs.h b/security/manager/pki/nsNSSDialogs.h new file mode 100644 index 000000000..164d661ee --- /dev/null +++ b/security/manager/pki/nsNSSDialogs.h @@ -0,0 +1,44 @@ +/* -*- 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 "nsIGenKeypairInfoDlg.h" +#include "nsIStringBundle.h" +#include "nsITokenDialogs.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 nsIGeneratingKeypairInfoDialogs + , public nsITokenDialogs + , public nsITokenPasswordDialogs +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITOKENPASSWORDDIALOGS + NS_DECL_NSICERTIFICATEDIALOGS + NS_DECL_NSICLIENTAUTHDIALOGS + NS_DECL_NSITOKENDIALOGS + NS_DECL_NSIGENERATINGKEYPAIRINFODIALOGS + nsNSSDialogs(); + + nsresult Init(); + +protected: + virtual ~nsNSSDialogs(); + nsCOMPtr<nsIStringBundle> mPIPStringBundle; +}; + +#endif // nsNSSDialogs_h diff --git a/security/manager/pki/nsPKIModule.cpp b/security/manager/pki/nsPKIModule.cpp new file mode 100644 index 000000000..fc4c5689c --- /dev/null +++ b/security/manager/pki/nsPKIModule.cpp @@ -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/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsASN1Tree.h" +#include "nsNSSDialogs.h" + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNSSDialogs, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsNSSASN1Tree) + +NS_DEFINE_NAMED_CID(NS_NSSDIALOGS_CID); +NS_DEFINE_NAMED_CID(NS_NSSASN1OUTINER_CID); + + +static const mozilla::Module::CIDEntry kPKICIDs[] = { + { &kNS_NSSDIALOGS_CID, false, nullptr, nsNSSDialogsConstructor }, + { &kNS_NSSASN1OUTINER_CID, false, nullptr, nsNSSASN1TreeConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kPKIContracts[] = { + { NS_TOKENPASSWORDSDIALOG_CONTRACTID, &kNS_NSSDIALOGS_CID }, + { NS_CERTIFICATEDIALOGS_CONTRACTID, &kNS_NSSDIALOGS_CID }, + { NS_CLIENTAUTHDIALOGS_CONTRACTID, &kNS_NSSDIALOGS_CID }, + { NS_TOKENDIALOGS_CONTRACTID, &kNS_NSSDIALOGS_CID }, + { NS_GENERATINGKEYPAIRINFODIALOGS_CONTRACTID, &kNS_NSSDIALOGS_CID }, + { NS_ASN1TREE_CONTRACTID, &kNS_NSSASN1OUTINER_CID }, + { nullptr } +}; + +static const mozilla::Module kPKIModule = { + mozilla::Module::kVersion, + kPKICIDs, + kPKIContracts +}; + +NSMODULE_DEFN(PKI) = &kPKIModule; diff --git a/security/manager/pki/resources/content/CAOverlay.xul b/security/manager/pki/resources/content/CAOverlay.xul new file mode 100644 index 000000000..ba4f4abf8 --- /dev/null +++ b/security/manager/pki/resources/content/CAOverlay.xul @@ -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/" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="CAOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="CACerts"> + <description>&certmgr.cas;</description> + <separator class="thin"/> + <tree id="ca-tree" flex="1" enableColumnDrag="true" + onselect="ca_enableButtons()"> + <treecols> + <treecol id="certcol" label="&certmgr.certname;" primary="true" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="tokencol" label="&certmgr.tokenname;" + persist="hidden width ordinal" flex="1"/> + </treecols> + <treechildren ondblclick="viewCerts();"/> + </tree> + + <separator class="thin"/> + + <hbox> + <button id="ca_viewButton" + label="&certmgr.view2.label;" + accesskey="&certmgr.view2.accesskey;" + disabled="true" oncommand="viewCerts();"/> + <button id="ca_editButton" + label="&certmgr.edit3.label;" + accesskey="&certmgr.edit3.accesskey;" + disabled="true" oncommand="editCerts();"/> + <button id="ca_addButton" + label="&certmgr.restore2.label;" + accesskey="&certmgr.restore2.accesskey;" + oncommand="addCACerts();"/> + <button id="ca_exportButton" + label="&certmgr.export.label;" + accesskey="&certmgr.export.accesskey;" + disabled="true" oncommand="exportCerts();"/> + <button id="ca_deleteButton" + label="&certmgr.delete_builtin.label;" + accesskey="&certmgr.delete_builtin.accesskey;" + disabled="true" oncommand="deleteCerts();"/> + </hbox> + </vbox> +</overlay> diff --git a/security/manager/pki/resources/content/MineOverlay.xul b/security/manager/pki/resources/content/MineOverlay.xul new file mode 100644 index 000000000..57526ac0f --- /dev/null +++ b/security/manager/pki/resources/content/MineOverlay.xul @@ -0,0 +1,64 @@ +<?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/" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="MineOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="myCerts"> + <description>&certmgr.mine;</description> + <separator class="thin"/> + <tree id="user-tree" flex="1" enableColumnDrag="true" + onselect="mine_enableButtons()"> + <treecols> + <treecol id="certcol" label="&certmgr.certname;" primary="true" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="tokencol" label="&certmgr.tokenname;" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="serialnumcol" label="&certmgr.serial;" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="issuedcol" label="&certmgr.begins;" + hidden="true" persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="expiredcol" label="&certmgr.expires;" + persist="hidden width ordinal" flex="1"/> + </treecols> + <treechildren ondblclick="viewCerts();"/> + </tree> + + <separator class="thin"/> + + <hbox> + <button id="mine_viewButton" class="normal" + label="&certmgr.view2.label;" + accesskey="&certmgr.view2.accesskey;" + disabled="true" oncommand="viewCerts();"/> + <button id="mine_backupButton" class="normal" + label="&certmgr.backup2.label;" + accesskey="&certmgr.backup2.accesskey;" + disabled="true" oncommand="backupCerts();"/> + <button id="mine_backupAllButton" class="normal" + label="&certmgr.backupall2.label;" + accesskey="&certmgr.backupall2.accesskey;" + oncommand="backupAllCerts();"/> + <button id="mine_restoreButton" class="normal" + label="&certmgr.restore2.label;" + accesskey="&certmgr.restore2.accesskey;" + oncommand="restoreCerts();"/> + <button id="mine_deleteButton" class="normal" + label="&certmgr.delete2.label;" + accesskey="&certmgr.delete2.accesskey;" + disabled="true" oncommand="deleteCerts();"/> + </hbox> + </vbox> +</overlay> diff --git a/security/manager/pki/resources/content/OrphanOverlay.xul b/security/manager/pki/resources/content/OrphanOverlay.xul new file mode 100644 index 000000000..a81ca9424 --- /dev/null +++ b/security/manager/pki/resources/content/OrphanOverlay.xul @@ -0,0 +1,47 @@ +<?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/" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="OrphanOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="OrphanCerts"> + <description>&certmgr.orphans;</description> + <separator class="thin"/> + <tree id="orphan-tree" flex="1" enableColumnDrag="true" + onselect="orphan_enableButtons()"> + <treecols> + <treecol id="certcol" label="&certmgr.certname;" primary="true" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="tokencol" label="&certmgr.tokenname;" + persist="hidden width ordinal" flex="1"/> + </treecols> + <treechildren ondblclick="viewCerts();"/> + </tree> + + <separator class="thin"/> + + <hbox> + <button id="orphan_viewButton" class="normal" + label="&certmgr.view2.label;" + accesskey="&certmgr.view2.accesskey;" + disabled="true" oncommand="viewCerts();"/> + <button id="orphan_exportButton" class="normal" + label="&certmgr.export.label;" + accesskey="&certmgr.export.accesskey;" + disabled="true" oncommand="exportCerts();"/> + <button id="orphan_deleteButton" class="normal" + label="&certmgr.delete2.label;" + accesskey="&certmgr.delete2.accesskey;" + disabled="true" oncommand="deleteCerts();"/> + </hbox> + </vbox> +</overlay> diff --git a/security/manager/pki/resources/content/OthersOverlay.xul b/security/manager/pki/resources/content/OthersOverlay.xul new file mode 100644 index 000000000..0f5154277 --- /dev/null +++ b/security/manager/pki/resources/content/OthersOverlay.xul @@ -0,0 +1,54 @@ +<?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/" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="OthersOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="othersCerts"> + <description>&certmgr.others;</description> + <separator class="thin"/> + <tree id="email-tree" flex="1" + onselect="email_enableButtons()"> + <treecols> + <treecol id="certcol" label="&certmgr.certname;" primary="true" + flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="emailcol" label="&certmgr.email;" + flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="expiredcol" label="&certmgr.expires;" + flex="1"/> + </treecols> + <treechildren flex="1" ondblclick="viewCerts();"/> + </tree> + + <separator class="thin"/> + + <hbox> + <button id="email_viewButton" + label="&certmgr.view2.label;" + accesskey="&certmgr.view2.accesskey;" + disabled="true" oncommand="viewCerts();"/> + <button id="email_addButton" + label="&certmgr.restore2.label;" + accesskey="&certmgr.restore2.accesskey;" + oncommand="addEmailCert();"/> + <button id="email_exportButton" + label="&certmgr.export.label;" + accesskey="&certmgr.export.accesskey;" + disabled="true" oncommand="exportCerts();"/> + <button id="email_deleteButton" + label="&certmgr.delete2.label;" + accesskey="&certmgr.delete2.accesskey;" + disabled="true" oncommand="deleteCerts();"/> + </hbox> + </vbox> +</overlay> diff --git a/security/manager/pki/resources/content/WebSitesOverlay.xul b/security/manager/pki/resources/content/WebSitesOverlay.xul new file mode 100644 index 000000000..e0f3ae788 --- /dev/null +++ b/security/manager/pki/resources/content/WebSitesOverlay.xul @@ -0,0 +1,57 @@ +<?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/" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="WebSitesOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="webCerts"> + <description>&certmgr.websites2;</description> + <separator class="thin"/> + <tree id="server-tree" flex="1" enableColumnDrag="true" + onselect="websites_enableButtons()"> + <treecols> + <treecol id="certcol" label="&certmgr.certname;" primary="true" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="sitecol" label="&certmgr.certserver;" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="lifetimecol" label="&certmgr.override_lifetime;" + persist="hidden width ordinal" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="expiredcol" label="&certmgr.expires;" + persist="hidden width ordinal" flex="1"/> + </treecols> + <treechildren ondblclick="viewCerts();"/> + </tree> + + <separator class="thin"/> + + <hbox> + <button id="websites_viewButton" + label="&certmgr.view2.label;" + accesskey="&certmgr.view2.accesskey;" + disabled="true" oncommand="viewCerts();"/> + <button id="websites_exportButton" + label="&certmgr.export.label;" + accesskey="&certmgr.export.accesskey;" + disabled="true" oncommand="exportCerts();"/> + <button id="websites_deleteButton" + label="&certmgr.delete2.label;" + accesskey="&certmgr.delete2.accesskey;" + disabled="true" oncommand="deleteCerts();"/> + <button id="websites_exceptionButton" + label="&certmgr.addException.label;" + accesskey="&certmgr.addException.accesskey;" + oncommand="addException();"/> + </hbox> + </vbox> +</overlay> diff --git a/security/manager/pki/resources/content/certDump.xul b/security/manager/pki/resources/content/certDump.xul new file mode 100644 index 000000000..74d9b3f8b --- /dev/null +++ b/security/manager/pki/resources/content/certDump.xul @@ -0,0 +1,44 @@ +<?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/. --> + +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="certDumpOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<vbox class="box-padded" id="certPrettyPrint" flex="1"> + <label class="header" value="&certmgr.hierarchy.label;" + control="treesetDump" accesskey="&certmgr.hierarchy.accesskey2;"/> + <tree id="treesetDump" onselect="updateCertDump();" flex="1" + hidecolumnpicker="true" style="height: 8em;"> + <treecols> + <treecol id="dumpCol" flex="1" primary="true" hideheader="true"/> + </treecols> + </tree> + + <label class="header" value="&certmgr.details.label;" + control="prettyDumpTree" accesskey="&certmgr.details.accesskey;"/> + <tree id="prettyDumpTree" style="height: 15em" treelines="true" flex="1" + onselect="displaySelected();" hidecolumnpicker="true"> + <treecols> + <treecol flex="1" id="certDataCol" primary="true" hideheader="true"/> + </treecols> + <treechildren/> + </tree> + + <label class="header" value="&certmgr.fields.label;" + control="certDumpVal" accesskey="&certmgr.fields.accesskey;"/> + <textbox id="certDumpVal" multiline="true" flex="1" + readonly="true" style="height: 11em; font-family: -moz-fixed;"/> + + <separator class="thin"/> + <hbox> + <button id="export_cert" class="normal" label="&certmgr.export.label;" + accesskey="&certmgr.export.accesskey;" + oncommand="exportToFile(window, getCurrentCert());"/> + </hbox> +</vbox> +</overlay> diff --git a/security/manager/pki/resources/content/certManager.js b/security/manager/pki/resources/content/certManager.js new file mode 100644 index 000000000..c52477442 --- /dev/null +++ b/security/manager/pki/resources/content/certManager.js @@ -0,0 +1,542 @@ +/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +const nsIFilePicker = Components.interfaces.nsIFilePicker; +const nsFilePicker = "@mozilla.org/filepicker;1"; +const nsIX509CertDB = Components.interfaces.nsIX509CertDB; +const nsX509CertDB = "@mozilla.org/security/x509certdb;1"; +const nsIX509Cert = Components.interfaces.nsIX509Cert; +const nsICertTree = Components.interfaces.nsICertTree; +const nsCertTree = "@mozilla.org/security/nsCertTree;1"; + +const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der"; + +var { NetUtil } = Components.utils.import("resource://gre/modules/NetUtil.jsm", {}); +var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {}); + +var key; + +/** + * 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; +/** + * Cert tree for the "People" tab. + * @type nsICertTree + */ +var emailTreeView; +/** + * Cert tree for the "Your Certificates" tab. + * @type nsICertTree + */ +var userTreeView; +/** + * Cert tree for the "Other" tab. + * @type nsICertTree + */ +var orphanTreeView; + +var smartCardObserver = { + observe: function() { + onSmartCardChange(); + } +}; + +function DeregisterSmartCardObservers() +{ + Services.obs.removeObserver(smartCardObserver, "smartcard-insert"); + Services.obs.removeObserver(smartCardObserver, "smartcard-remove"); +} + +function LoadCerts() +{ + Services.obs.addObserver(smartCardObserver, "smartcard-insert", false); + Services.obs.addObserver(smartCardObserver, "smartcard-remove", false); + + certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB); + var certcache = certdb.getCerts(); + + caTreeView = Components.classes[nsCertTree] + .createInstance(nsICertTree); + caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT); + document.getElementById('ca-tree').view = caTreeView; + + serverTreeView = Components.classes[nsCertTree] + .createInstance(nsICertTree); + serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT); + document.getElementById('server-tree').view = serverTreeView; + + emailTreeView = Components.classes[nsCertTree] + .createInstance(nsICertTree); + emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT); + document.getElementById('email-tree').view = emailTreeView; + + userTreeView = Components.classes[nsCertTree] + .createInstance(nsICertTree); + userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT); + document.getElementById('user-tree').view = userTreeView; + + orphanTreeView = Components.classes[nsCertTree] + .createInstance(nsICertTree); + orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT); + document.getElementById('orphan-tree').view = orphanTreeView; + + 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 websites_tab = document.getElementById("websites_tab"); + var orphan_tab = document.getElementById("orphan_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; + } else if (websites_tab.selected) { + items = serverTreeView.selection; + } else if (orphan_tab.selected) { + items = orphanTreeView.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); + } else if (websites_tab.selected) { + cert = serverTreeView.getCert(j); + } else if (orphan_tab.selected) { + cert = orphanTreeView.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 websites_tab = document.getElementById("websites_tab"); + var orphan_tab = document.getElementById("orphan_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; + } else if (websites_tab.selected) { + items = serverTreeView.selection; + } else if (orphan_tab.selected) { + items = orphanTreeView.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); + } else if (websites_tab.selected) { + tree_item = serverTreeView.getTreeItem(j); + } else if (orphan_tab.selected) { + tree_item = orphanTreeView.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; +} + +/** + * 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 websites_enableButtons() +{ + let idList = [ + "websites_viewButton", + "websites_exportButton", + "websites_deleteButton", + ]; + enableButtonsForCertTree(serverTreeView, idList); +} + +function email_enableButtons() +{ + let idList = [ + "email_viewButton", + "email_exportButton", + "email_deleteButton", + ]; + enableButtonsForCertTree(emailTreeView, idList); +} + +function orphan_enableButtons() +{ + let idList = [ + "orphan_viewButton", + "orphan_exportButton", + "orphan_deleteButton", + ]; + enableButtonsForCertTree(orphanTreeView, idList); +} + +function backupCerts() +{ + getSelectedCerts(); + var numcerts = selected_certs.length; + if (numcerts == 0) { + return; + } + + var bundle = document.getElementById("pippki_bundle"); + var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker); + fp.init(window, + bundle.getString("chooseP12BackupFileDialog"), + nsIFilePicker.modeSave); + fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"), + "*.p12"); + fp.appendFilters(nsIFilePicker.filterAll); + var rv = fp.show(); + if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) { + certdb.exportPKCS12File(null, fp.file, selected_certs.length, + selected_certs); + } +} + +function backupAllCerts() +{ + // Select all rows, then call doBackup() + var items = userTreeView.selection.selectAll(); + backupCerts(); +} + +function editCerts() +{ + getSelectedCerts(); + + for (let cert of selected_certs) { + window.openDialog("chrome://pippki/content/editcacert.xul", "", + "chrome,centerscreen,modal", cert); + } +} + +function restoreCerts() +{ + var bundle = document.getElementById("pippki_bundle"); + var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker); + fp.init(window, + bundle.getString("chooseP12RestoreFileDialog2"), + nsIFilePicker.modeOpen); + fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"), + "*.p12; *.pfx"); + fp.appendFilter(bundle.getString("file_browse_Certificate_spec"), + gCertFileTypes); + fp.appendFilters(nsIFilePicker.filterAll); + if (fp.show() == nsIFilePicker.returnOK) { + // 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 = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.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: function() { + return prompter; + } + }; + certdb.importUserCertificate(dataArray, dataArray.length, interfaceRequestor); + } else { + // Otherwise, assume it's a PKCS12 file and import it that way. + certdb.importPKCS12File(null, fp.file); + } + + var certcache = certdb.getCerts(); + userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT); + userTreeView.selection.clearSelection(); + caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT); + caTreeView.selection.clearSelection(); + enableBackupAllButton(); + } +} + +function exportCerts() +{ + getSelectedCerts(); + + for (let cert of selected_certs) { + 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, + "websites_tab": serverTreeView, + "ca_tab": caTreeView, + "others_tab": emailTreeView, + "orphan_tab": orphanTreeView, + }; + let selTab = document.getElementById("certMgrTabbox").selectedItem; + let selTabID = selTab.getAttribute("id"); + + if (!(selTabID in treeViewMap)) { + return; + } + + let retVals = { + deleteConfirmed: false, + }; + window.openDialog("chrome://pippki/content/deletecert.xul", "", + "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); + } +} + +function addCACerts() +{ + var bundle = document.getElementById("pippki_bundle"); + var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker); + fp.init(window, + bundle.getString("importCACertsPrompt"), + nsIFilePicker.modeOpen); + fp.appendFilter(bundle.getString("file_browse_Certificate_spec"), + gCertFileTypes); + fp.appendFilters(nsIFilePicker.filterAll); + if (fp.show() == nsIFilePicker.returnOK) { + certdb.importCertsFromFile(fp.file, nsIX509Cert.CA_CERT); + caTreeView.loadCerts(nsIX509Cert.CA_CERT); + caTreeView.selection.clearSelection(); + } +} + +function onSmartCardChange() +{ + var certcache = certdb.getCerts(); + // We've change the state of the smart cards inserted or removed + // that means the available certs may have changed. Update the display + userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT); + userTreeView.selection.clearSelection(); + caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT); + caTreeView.selection.clearSelection(); + serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT); + serverTreeView.selection.clearSelection(); + emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT); + emailTreeView.selection.clearSelection(); + orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT); + orphanTreeView.selection.clearSelection(); +} + +function addEmailCert() +{ + var bundle = document.getElementById("pippki_bundle"); + var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker); + fp.init(window, + bundle.getString("importEmailCertPrompt"), + nsIFilePicker.modeOpen); + fp.appendFilter(bundle.getString("file_browse_Certificate_spec"), + gCertFileTypes); + fp.appendFilters(nsIFilePicker.filterAll); + if (fp.show() == nsIFilePicker.returnOK) { + certdb.importCertsFromFile(fp.file, nsIX509Cert.EMAIL_CERT); + var certcache = certdb.getCerts(); + emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT); + emailTreeView.selection.clearSelection(); + caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT); + caTreeView.selection.clearSelection(); + } +} + +function addException() +{ + window.openDialog('chrome://pippki/content/exceptionDialog.xul', "", + 'chrome,centerscreen,modal'); + var certcache = certdb.getCerts(); + serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT); + serverTreeView.selection.clearSelection(); + orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT); + orphanTreeView.selection.clearSelection(); +} diff --git a/security/manager/pki/resources/content/certManager.xul b/security/manager/pki/resources/content/certManager.xul new file mode 100644 index 000000000..3ea5862e4 --- /dev/null +++ b/security/manager/pki/resources/content/certManager.xul @@ -0,0 +1,51 @@ +<?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/" type="text/css"?> + +<?xul-overlay href="chrome://pippki/content/MineOverlay.xul"?> +<?xul-overlay href="chrome://pippki/content/OthersOverlay.xul"?> +<?xul-overlay href="chrome://pippki/content/WebSitesOverlay.xul"?> +<?xul-overlay href="chrome://pippki/content/CAOverlay.xul"?> +<?xul-overlay href="chrome://pippki/content/OrphanOverlay.xul"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<dialog id="certmanager" + windowtype="mozilla:certmanager" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&certmgr.title;" + onload="LoadCerts();" + onunload="DeregisterSmartCardObservers();" + buttons="accept" + style="width: 63em; height: 32em;" + persist="screenX screenY width height"> + + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + + <script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + <script type="application/javascript" src="chrome://pippki/content/certManager.js"/> + + <vbox flex="1"> + <tabbox id="certmanagertabs" flex="1" style="margin:5px" persist="selectedIndex"> + <tabs id="certMgrTabbox"> + <tab id="mine_tab" label="&certmgr.tab.mine;"/> + <tab id="others_tab" label="&certmgr.tab.others2;"/> + <tab id="websites_tab" label="&certmgr.tab.websites3;"/> + <tab id="ca_tab" label="&certmgr.tab.ca;" selected="true"/> + <tab id="orphan_tab" label="&certmgr.tab.orphan2;"/> + </tabs> + <tabpanels flex="1"> + <vbox id="myCerts" flex="1"/> + <vbox id="othersCerts" flex="1"/> + <vbox id="webCerts" flex="1"/> + <vbox id="CACerts" flex="1"/> + <vbox id="OrphanCerts" flex="1"/> + </tabpanels> + </tabbox> + + </vbox> + +</dialog> diff --git a/security/manager/pki/resources/content/certViewer.js b/security/manager/pki/resources/content/certViewer.js new file mode 100644 index 000000000..c62507694 --- /dev/null +++ b/security/manager/pki/resources/content/certViewer.js @@ -0,0 +1,354 @@ +/* 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 functionality for certViewer.xul and its tabs certDump.xul + * and viewCertDetails.xul: a dialog that allows various attributes of a + * certificate to be viewed. + * @argument {nsISupports} window.arguments[0] + * The cert to view, queryable to nsIX509Cert. + */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; +const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + +const nsIX509Cert = Ci.nsIX509Cert; +const nsX509CertDB = "@mozilla.org/security/x509certdb;1"; +const nsIX509CertDB = Ci.nsIX509CertDB; +const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1"; +const nsIPK11TokenDB = Ci.nsIPK11TokenDB; +const nsIASN1Object = Ci.nsIASN1Object; +const nsIASN1Sequence = Ci.nsIASN1Sequence; +const nsIASN1PrintableItem = Ci.nsIASN1PrintableItem; +const nsIASN1Tree = Ci.nsIASN1Tree; +const nsASN1Tree = "@mozilla.org/security/nsASN1Tree;1"; + +var bundle; + +function doPrompt(msg) +{ + let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.nsIPromptService); + prompts.alert(window, null, msg); +} + +/** + * Fills out the "Certificate Hierarchy" tree of the cert viewer "Details" tab. + * + * @param {tree} node + * Parent tree node to append to. + * @param {nsIArray<nsIX509Cert>} chain + * Chain where cert element n is issued by cert element n + 1. + */ +function AddCertChain(node, chain) +{ + var child = document.getElementById(node); + var currCert; + var displayVal; + for (let i = chain.length - 1; i >= 0; i--) { + currCert = chain.queryElementAt(i, nsIX509Cert); + if (currCert.commonName) { + displayVal = currCert.commonName; + } else { + displayVal = currCert.windowTitle; + } + let addTwistie = i != 0; + child = addChildrenToTree(child, displayVal, currCert.dbKey, addTwistie); + } +} + +/** + * Adds a "verified usage" of a cert to the "General" tab of the cert viewer. + * + * @param {String} usage + * Verified usage to add. + */ +function AddUsage(usage) +{ + let verifyInfoBox = document.getElementById("verify_info_box"); + let text = document.createElement("textbox"); + text.setAttribute("value", usage); + text.setAttribute("style", "margin: 2px 5px"); + text.setAttribute("readonly", "true"); + text.setAttribute("class", "scrollfield"); + verifyInfoBox.appendChild(text); +} + +function setWindowName() +{ + bundle = document.getElementById("pippki_bundle"); + + let cert = window.arguments[0].QueryInterface(Ci.nsIX509Cert); + document.title = bundle.getFormattedString("certViewerTitle", + [cert.windowTitle]); + + // + // Set the cert attributes for viewing + // + + // The chain of trust + AddCertChain("treesetDump", cert.getChain()); + DisplayGeneralDataFromCert(cert); + BuildPrettyPrint(cert); + + asyncDetermineUsages(cert); +} + +// Certificate usages we care about in the certificate viewer. +const certificateUsageSSLClient = 0x0001; +const certificateUsageSSLServer = 0x0002; +const certificateUsageSSLCA = 0x0008; +const certificateUsageEmailSigner = 0x0010; +const certificateUsageEmailRecipient = 0x0020; +const certificateUsageObjectSigner = 0x0040; + +// 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, + certificateUsageObjectSigner, +}; + +// Map of certificate usage name to localization identifier. +const certificateUsageToStringBundleName = { + certificateUsageSSLClient: "VerifySSLClient", + certificateUsageSSLServer: "VerifySSLServer", + certificateUsageSSLCA: "VerifySSLCA", + certificateUsageEmailSigner: "VerifyEmailSigner", + certificateUsageEmailRecipient: "VerifyEmailRecip", + certificateUsageObjectSigner: "VerifyObjSign", +}; + +const PRErrorCodeSuccess = 0; + +const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; +const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11; +const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12; +const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; +const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20; +const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21; +const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; +const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176; + +/** + * Kicks off asynchronous verifications of the given certificate to determine + * what usages it is currently valid for. Updates the usage display area when + * complete. + * + * @param {nsIX509Cert} cert + * The certificate to determine valid usages for. + */ +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: usageString, errorCode: aPRErrorCode }); + }); + })); + }); + Promise.all(promises).then(displayUsages); +} + +/** + * Updates the usage display area given the results from asyncDetermineUsages. + * + * @param {Array} results + * An array of objects with the properties "usageString" and "errorCode". + * usageString is a string that is a key in the certificateUsages map. + * errorCode is either an NSPR error code or PRErrorCodeSuccess (which is + * a pseudo-NSPR error code with the value 0 that indicates success). + */ +function displayUsages(results) { + document.getElementById("verify_pending").setAttribute("hidden", "true"); + let verified = document.getElementById("verified"); + let someSuccess = results.some(result => + result.errorCode == PRErrorCodeSuccess + ); + if (someSuccess) { + let verifystr = bundle.getString("certVerified"); + verified.textContent = verifystr; + let pipnssBundle = Services.strings.createBundle( + "chrome://pipnss/locale/pipnss.properties"); + results.forEach(result => { + if (result.errorCode != PRErrorCodeSuccess) { + return; + } + let bundleName = certificateUsageToStringBundleName[result.usageString]; + let usage = pipnssBundle.GetStringFromName(bundleName); + AddUsage(usage); + }); + } else { + const errorRankings = [ + { error: SEC_ERROR_REVOKED_CERTIFICATE, + bundleString: "certNotVerified_CertRevoked" }, + { error: SEC_ERROR_UNTRUSTED_CERT, + bundleString: "certNotVerified_CertNotTrusted" }, + { error: SEC_ERROR_UNTRUSTED_ISSUER, + bundleString: "certNotVerified_IssuerNotTrusted" }, + { error: SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, + bundleString: "certNotVerified_AlgorithmDisabled" }, + { error: SEC_ERROR_EXPIRED_CERTIFICATE, + bundleString: "certNotVerified_CertExpired" }, + { error: SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE, + bundleString: "certNotVerified_CAInvalid" }, + { error: SEC_ERROR_UNKNOWN_ISSUER, + bundleString: "certNotVerified_IssuerUnknown" }, + ]; + let verifystr; + for (let errorRanking of errorRankings) { + let errorPresent = results.some(result => + result.errorCode == errorRanking.error + ); + if (errorPresent) { + verifystr = bundle.getString(errorRanking.bundleString); + break; + } + } + if (!verifystr) { + verifystr = bundle.getString("certNotVerified_Unknown"); + } + verified.textContent = verifystr; + } + // Notify that we are done determining the certificate's valid usages (this + // should be treated as an implementation detail that enables tests to run + // efficiently - other code in the browser probably shouldn't rely on this). + Services.obs.notifyObservers(window, "ViewCertDetails:CertUsagesDone", null); +} + +function addChildrenToTree(parentTree, label, value, addTwistie) +{ + let treeChild1 = document.createElement("treechildren"); + let treeElement = addTreeItemToTreeChild(treeChild1, label, value, + addTwistie); + parentTree.appendChild(treeChild1); + return treeElement; +} + +function addTreeItemToTreeChild(treeChild, label, value, addTwistie) +{ + let treeElem1 = document.createElement("treeitem"); + if (addTwistie) { + treeElem1.setAttribute("container", "true"); + treeElem1.setAttribute("open", "true"); + } + let treeRow = document.createElement("treerow"); + let treeCell = document.createElement("treecell"); + treeCell.setAttribute("label", label); + if (value) { + treeCell.setAttribute("display", value); + } + treeRow.appendChild(treeCell); + treeElem1.appendChild(treeRow); + treeChild.appendChild(treeElem1); + return treeElem1; +} + +function displaySelected() { + var asn1Tree = document.getElementById('prettyDumpTree') + .view.QueryInterface(nsIASN1Tree); + var items = asn1Tree.selection; + var certDumpVal = document.getElementById('certDumpVal'); + if (items.currentIndex != -1) { + var value = asn1Tree.getDisplayData(items.currentIndex); + certDumpVal.value = value; + } else { + certDumpVal.value = ""; + } +} + +function BuildPrettyPrint(cert) +{ + var certDumpTree = Components.classes[nsASN1Tree]. + createInstance(nsIASN1Tree); + certDumpTree.loadASN1Structure(cert.ASN1Structure); + document.getElementById('prettyDumpTree').view = certDumpTree; +} + +function addAttributeFromCert(nodeName, value) +{ + var node = document.getElementById(nodeName); + if (!value) { + value = bundle.getString('notPresent'); + } + node.setAttribute('value', value); +} + +/** + * Displays information about a cert in the "General" tab of the cert viewer. + * + * @param {nsIX509Cert} cert + * Cert to display information about. + */ +function DisplayGeneralDataFromCert(cert) +{ + addAttributeFromCert("commonname", cert.commonName); + addAttributeFromCert("organization", cert.organization); + addAttributeFromCert("orgunit", cert.organizationalUnit); + addAttributeFromCert("serialnumber", cert.serialNumber); + addAttributeFromCert("sha256fingerprint", cert.sha256Fingerprint); + addAttributeFromCert("sha1fingerprint", cert.sha1Fingerprint); + addAttributeFromCert("validitystart", cert.validity.notBeforeLocalDay); + addAttributeFromCert("validityend", cert.validity.notAfterLocalDay); + + addAttributeFromCert("issuercommonname", cert.issuerCommonName); + addAttributeFromCert("issuerorganization", cert.issuerOrganization); + addAttributeFromCert("issuerorgunit", cert.issuerOrganizationUnit); +} + +function updateCertDump() +{ + var asn1Tree = document.getElementById('prettyDumpTree') + .view.QueryInterface(nsIASN1Tree); + + var tree = document.getElementById('treesetDump'); + if (tree.currentIndex < 0) { + doPrompt("No items are selected."); //This should never happen. + } else { + var item = tree.contentView.getItemAtIndex(tree.currentIndex); + var dbKey = item.firstChild.firstChild.getAttribute('display'); + // Get the cert from the cert database + var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB); + var cert = certdb.findCertByDBKey(dbKey); + asn1Tree.loadASN1Structure(cert.ASN1Structure); + } + displaySelected(); +} + +function getCurrentCert() +{ + var realIndex; + var tree = document.getElementById('treesetDump'); + if (tree.view.selection.isSelected(tree.currentIndex) + && document.getElementById('prettyprint_tab').selected) { + /* if the user manually selected a cert on the Details tab, + then take that one */ + realIndex = tree.currentIndex; + } else { + /* otherwise, take the one at the bottom of the chain + (i.e. the one of the end-entity, unless we're displaying + CA certs) */ + realIndex = tree.view.rowCount - 1; + } + if (realIndex >= 0) { + var item = tree.contentView.getItemAtIndex(realIndex); + var dbKey = item.firstChild.firstChild.getAttribute('display'); + var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB); + var cert = certdb.findCertByDBKey(dbKey); + return cert; + } + /* shouldn't really happen */ + return null; +} diff --git a/security/manager/pki/resources/content/certViewer.xul b/security/manager/pki/resources/content/certViewer.xul new file mode 100644 index 000000000..3f2cd3bde --- /dev/null +++ b/security/manager/pki/resources/content/certViewer.xul @@ -0,0 +1,40 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<?xul-overlay href="chrome://pippki/content/viewCertDetails.xul"?> +<?xul-overlay href="chrome://pippki/content/certDump.xul"?> + +<dialog id="certDetails" + title="&certmgr.certdetail.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept" + buttonlabelaccept="&certmgr.close.label;" + buttonaccesskeyaccept="&certmgr.close.accesskey;" + onload="setWindowName();"> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" + src="chrome://pippki/content/certViewer.js"/> +<script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + + <tabbox flex="1"> + <tabs> + <tab id="general_tab" label="&certmgr.detail.general_tab.title;" + accesskey="&certmgr.detail.general_tab.accesskey;"/> + <tab id="prettyprint_tab" label="&certmgr.detail.prettyprint_tab.title;" + accesskey="&certmgr.detail.prettyprint_tab.accesskey;"/> + </tabs> + <tabpanels flex="1"> + <vbox id="general_info"/> + <vbox id="certPrettyPrint"/> + </tabpanels> + </tabbox> + +</dialog> diff --git a/security/manager/pki/resources/content/changepassword.js b/security/manager/pki/resources/content/changepassword.js new file mode 100644 index 000000000..823145697 --- /dev/null +++ b/security/manager/pki/resources/content/changepassword.js @@ -0,0 +1,270 @@ +/* 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 nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1"; +const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB; +const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; +const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1"; +const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB; +const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot; +const nsIPK11Token = Components.interfaces.nsIPK11Token; + +var params; +var tokenName = ""; +var pw1; + +function doPrompt(msg) +{ + let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.nsIPromptService); + prompts.alert(window, null, msg); +} + +function onLoad() +{ + document.documentElement.getButton("accept").disabled = true; + + pw1 = document.getElementById("pw1"); + try { + params = window.arguments[0].QueryInterface(nsIDialogParamBlock); + tokenName = params.GetString(1); + } catch (e) { + // this should not happen. + // previously we had self.name, but self.name was a bad idea + // as window name must be a subset of ascii, and the code was + // previously trying to assign unicode to the window's name. + // I checked all the places where we get a password prompt and + // all of them pass an argument as part of this patch. + tokenName = ""; + } + + if (tokenName == "") { + let tokenDB = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB); + let tokenList = tokenDB.listTokens(); + let i = 0; + let menu = document.getElementById("tokenMenu"); + while (tokenList.hasMoreElements()) { + let token = tokenList.getNext().QueryInterface(nsIPK11Token); + if (token.needsLogin() || !(token.needsUserInit)) { + let menuItemNode = document.createElement("menuitem"); + menuItemNode.setAttribute("value", token.tokenName); + menuItemNode.setAttribute("label", token.tokenName); + menu.firstChild.appendChild(menuItemNode); + if (i == 0) { + menu.selectedItem = menuItemNode; + tokenName = token.tokenName; + } + i++; + } + } + } else { + var sel = document.getElementById("tokenMenu"); + sel.setAttribute("hidden", "true"); + var tag = document.getElementById("tokenName"); + tag.setAttribute("value", tokenName); + } + + process(); +} + +function onMenuChange() +{ + //get the selected token + var list = document.getElementById("tokenMenu"); + tokenName = list.value; + + process(); +} + + +function process() +{ + var secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB); + var bundle = document.getElementById("pippki_bundle"); + + // If the token is unitialized, don't use the old password box. + // Otherwise, do. + + var slot = secmoddb.findSlotByName(tokenName); + if (slot) { + var oldpwbox = document.getElementById("oldpw"); + var msgBox = document.getElementById("message"); + var status = slot.status; + if (status == nsIPKCS11Slot.SLOT_UNINITIALIZED + || status == nsIPKCS11Slot.SLOT_READY) { + + oldpwbox.setAttribute("hidden", "true"); + msgBox.setAttribute("value", bundle.getString("password_not_set")); + msgBox.setAttribute("hidden", "false"); + + if (status == nsIPKCS11Slot.SLOT_READY) { + oldpwbox.setAttribute("inited", "empty"); + } else { + oldpwbox.setAttribute("inited", "true"); + } + + // Select first password field + document.getElementById('pw1').focus(); + } else { + // Select old password field + oldpwbox.setAttribute("hidden", "false"); + msgBox.setAttribute("hidden", "true"); + oldpwbox.setAttribute("inited", "false"); + oldpwbox.focus(); + } + } + + if (params) { + // Return value 0 means "canceled" + params.SetInt(1, 0); + } + + checkPasswords(); +} + +function setPassword() +{ + var pk11db = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB); + var token = pk11db.findTokenByName(tokenName); + + var oldpwbox = document.getElementById("oldpw"); + var initpw = oldpwbox.getAttribute("inited"); + var bundle = document.getElementById("pippki_bundle"); + + 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 = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB); + if (secmoddb.isFIPSEnabled) { + // empty passwords are not allowed in FIPS mode + doPrompt(bundle.getString("pw_change2empty_in_fips_mode")); + passok = 0; + } + } + if (passok) { + token.changePassword(oldpw, pw1.value); + if (pw1.value == "") { + doPrompt(bundle.getString("pw_erased_ok") + + " " + + bundle.getString("pw_empty_warning")); + } else { + doPrompt(bundle.getString("pw_change_ok")); + } + success = true; + } + } + } else { + oldpwbox.focus(); + oldpwbox.setAttribute("value", ""); + doPrompt(bundle.getString("incorrect_pw")); + } + } catch (e) { + doPrompt(bundle.getString("failed_pw_change")); + } + } else { + token.initPassword(pw1.value); + if (pw1.value == "") { + doPrompt(bundle.getString("pw_not_wanted") + " " + + bundle.getString("pw_empty_warning")); + } + success = true; + } + + if (success && params) { + // Return value 1 means "successfully executed ok" + params.SetInt(1, 1); + } + + // Terminate dialog + return success; +} + +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); + + return; +} + +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.documentElement.getButton("accept").disabled = true; + return; + } + } + + document.documentElement.getButton("accept").disabled = (pw1 != pw2); +} diff --git a/security/manager/pki/resources/content/changepassword.xul b/security/manager/pki/resources/content/changepassword.xul new file mode 100644 index 000000000..7f0878ec1 --- /dev/null +++ b/security/manager/pki/resources/content/changepassword.xul @@ -0,0 +1,78 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<dialog id="set_password" title="&setPassword.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + ondialogaccept="return setPassword();" + onload="onLoad();"> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" + src="chrome://pippki/content/changepassword.js"/> + +<hbox align="center"> + <label value="&setPassword.tokenName.label;: "/> + <label id="tokenName" /> + <menulist id="tokenMenu" oncommand="onMenuChange()"> + <menupopup/> + </menulist> +</hbox> + + +<!-- + <menulist id="signerList" disabled="true"> + <menupopup> + <menuitem id="token-menu" label="Built-in private key database"/> + <menuitem label="Bob Lord's iButton"/> + </menupopup> + </menulist> +--> +<separator/> + +<groupbox> +<grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row> + <label value="&setPassword.oldPassword.label;"/> + <textbox 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 + --> + <textbox id="message" disabled="true" /> + </row> + <row> + <label value="&setPassword.newPassword.label;"/> + <textbox id="pw1" type="password" + oninput="setPasswordStrength(); checkPasswords();"/> + </row> + <row> + <label value="&setPassword.reenterPassword.label;"/> + <textbox id="pw2" type="password" oninput="checkPasswords();"/> + </row> + </rows> +</grid> +</groupbox> + +<groupbox> + <caption label="&setPassword.meter.label;"/> + <progressmeter id="pwmeter" mode="determined" + value="0"/> +</groupbox> + +</dialog> diff --git a/security/manager/pki/resources/content/choosetoken.js b/security/manager/pki/resources/content/choosetoken.js new file mode 100644 index 000000000..42e8565da --- /dev/null +++ b/security/manager/pki/resources/content/choosetoken.js @@ -0,0 +1,44 @@ +/* -*- 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 nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; + +var dialogParams; + +function onLoad() +{ + dialogParams = window.arguments[0].QueryInterface(nsIDialogParamBlock); + let selectElement = document.getElementById("tokens"); + let count = dialogParams.GetInt(0); + for (let i = 0; i < count; i++) { + let menuItemNode = document.createElement("menuitem"); + let token = dialogParams.GetString(i); + menuItemNode.setAttribute("value", token); + menuItemNode.setAttribute("label", token); + selectElement.firstChild.appendChild(menuItemNode); + if (i == 0) { + selectElement.selectedItem = menuItemNode; + } + } +} + +function doOK() +{ + let tokenList = document.getElementById("tokens"); + // Signal that the user accepted. + dialogParams.SetInt(0, 1); + // Signal the name of the token the user chose. + dialogParams.SetString(0, tokenList.value); + return true; +} + +function doCancel() +{ + dialogParams.SetInt(0, 0); // Signal that the user cancelled. + return true; +} diff --git a/security/manager/pki/resources/content/choosetoken.xul b/security/manager/pki/resources/content/choosetoken.xul new file mode 100644 index 000000000..55d9c108b --- /dev/null +++ b/security/manager/pki/resources/content/choosetoken.xul @@ -0,0 +1,34 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" > +%pippkiDTD; +]> + + +<dialog id="ssl_warning" title="&chooseToken.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 40em;" + buttons="accept,cancel" + ondialogaccept="return doOK();" + ondialogcancel="return doCancel();" + onload="onLoad();"> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" src="chrome://pippki/content/pippki.js"/> +<script type="application/javascript" src="chrome://pippki/content/choosetoken.js"/> + + <groupbox> + <description>&chooseToken.message1;</description> + <menulist id="tokens"> + <menupopup/> + </menulist> + </groupbox> + +</dialog> diff --git a/security/manager/pki/resources/content/clientauthask.js b/security/manager/pki/resources/content/clientauthask.js new file mode 100644 index 000000000..55a3c5b77 --- /dev/null +++ b/security/manager/pki/resources/content/clientauthask.js @@ -0,0 +1,158 @@ +/* -*- 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"; + +/** + * @file Implements the functionality of clientauthask.xul: a dialog that allows + * a user pick a client certificate for TLS client authentication. + * @argument {String} window.arguments[0] + * The hostname of the server requesting client authentication. + * @argument {String} window.arguments[1] + * The Organization of the server cert. + * @argument {String} window.arguments[2] + * The Organization of the issuer of the server cert. + * @argument {Number} window.arguments[3] + * The port of the server. + * @argument {nsISupports} window.arguments[4] + * List of certificates the user can choose from, queryable to + * nsIArray<nsIX509Cert>. + * @argument {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. + */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + +/** + * The pippki <stringbundle> element. + * @type <stringbundle> + */ +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 nsIDOMXULCheckboxElement + */ +var rememberBox; + +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.createElement("menuitem"); + let cert = certArray.queryElementAt(i, Ci.nsIX509Cert); + let nickAndSerial = + bundle.getFormattedString("clientAuthNickAndSerial", + [cert.nickname, cert.serialNumber]); + menuItemNode.setAttribute("value", i); + menuItemNode.setAttribute("label", nickAndSerial); // This is displayed. + selectElement.firstChild.appendChild(menuItemNode); + if (i == 0) { + selectElement.selectedItem = menuItemNode; + } + } + + setDetails(); + + Services.obs.notifyObservers(document.getElementById("certAuthAsk"), + "cert-dialog-loaded", null); +} + +/** + * Populates the details section with information concerning the selected cert. + */ +function setDetails() { + let index = parseInt(document.getElementById("nicknames").value); + let cert = certArray.queryElementAt(index, Ci.nsIX509Cert); + + let detailLines = [ + bundle.getFormattedString("clientAuthIssuedTo", [cert.subjectName]), + bundle.getFormattedString("clientAuthSerial", [cert.serialNumber]), + bundle.getFormattedString("clientAuthValidityPeriod", + [cert.validity.notBeforeLocalTime, + cert.validity.notAfterLocalTime]), + ]; + let keyUsages = cert.keyUsages; + if (keyUsages) { + detailLines.push(bundle.getFormattedString("clientAuthKeyUsages", + [keyUsages])); + } + let emailAddresses = cert.getEmailAddresses({}); + if (emailAddresses.length > 0) { + 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"); +} + +function onCertSelected() { + 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); + return true; +} + +function doCancel() { + let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2); + retVals.setPropertyAsBool("certChosen", false); + retVals.setPropertyAsBool("rememberSelection", rememberBox.checked); + return true; +} diff --git a/security/manager/pki/resources/content/clientauthask.xul b/security/manager/pki/resources/content/clientauthask.xul new file mode 100644 index 000000000..2d6ba54c2 --- /dev/null +++ b/security/manager/pki/resources/content/clientauthask.xul @@ -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/" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" > +%pippkiDTD; +]> + + +<dialog id="certAuthAsk" title="&clientAuthAsk.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + ondialogaccept="return doOK();" + ondialogcancel="return doCancel();" + onload="onLoad();"> + +<stringbundleset id="stringbundleset"> + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> +</stringbundleset> + +<script type="application/javascript" src="chrome://pippki/content/pippki.js"/> +<script type="application/javascript" src="chrome://pippki/content/clientauthask.js"/> + + <groupbox> + <description style="font-weight: bold;">&clientAuthAsk.message1;</description> + <description id="hostname"/> + <description id="organization"/> + <description id="issuer"/> + </groupbox> + <groupbox> + <description style="font-weight: bold;">&clientAuthAsk.message2;</description> + <broadcaster id="certSelected" oncommand="onCertSelected();"/> + <!-- The items in this menulist must never be sorted, + but remain in the order filled by the application + --> + <menulist id="nicknames" observes="certSelected"> + <menupopup/> + </menulist> + <description>&clientAuthAsk.message3;</description> + <textbox readonly="true" id="details" multiline="true" + style="height: 11em;"/> + <checkbox id="rememberBox" checked="true"/> + </groupbox> + +</dialog> diff --git a/security/manager/pki/resources/content/createCertInfo.js b/security/manager/pki/resources/content/createCertInfo.js new file mode 100644 index 000000000..5e7e9052c --- /dev/null +++ b/security/manager/pki/resources/content/createCertInfo.js @@ -0,0 +1,39 @@ +/* -*- 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"; + +var keygenThread; + +function onLoad() +{ + keygenThread = window.arguments[0].QueryInterface(Components.interfaces.nsIKeygenThread); + + if (!keygenThread) { + window.close(); + return; + } + + window.setCursor("wait"); + + var obs = { + observe: function keygenListenerObserve(subject, topic, data) { + if (topic == "keygen-finished") { + window.close(); + } + } + }; + + keygenThread.startKeyGeneration(obs); +} + +function onClose() +{ + window.setCursor("auto"); + + var alreadyClosed = {}; + keygenThread.userCanceled(alreadyClosed); +} diff --git a/security/manager/pki/resources/content/createCertInfo.xul b/security/manager/pki/resources/content/createCertInfo.xul new file mode 100644 index 000000000..66f4ba9c6 --- /dev/null +++ b/security/manager/pki/resources/content/createCertInfo.xul @@ -0,0 +1,30 @@ +<?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/" type="text/css"?> + +<!DOCTYPE window SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<window + id="domainMismatch" title="&createCertInfo.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad();" + onclose="onClose();" +> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" src="pippki.js" /> +<script type="application/javascript" src="createCertInfo.js" /> + +<vbox style="margin: 5px; max-width: 50em;"> + + <description>&createCertInfo.msg1;</description> + <separator/> + <description style="font-weight: bold; text-align: center;">&createCertInfo.msg2;</description> + <separator/> + +</vbox> +</window> diff --git a/security/manager/pki/resources/content/deletecert.js b/security/manager/pki/resources/content/deletecert.js new file mode 100644 index 000000000..784001ea2 --- /dev/null +++ b/security/manager/pki/resources/content/deletecert.js @@ -0,0 +1,134 @@ +/* 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.xul: a dialog that allows a + * user to confirm whether to delete certain certificates. + * @argument {String} window.arguments[0] + * One of the tab IDs listed in certManager.xul. + * @argument {nsICertTreeItem[]} window.arguments[1] + * An array of cert tree items representing the certs to delete. + * @argument {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. + */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +/** + * Returns the most appropriate string to represent the given nsICertTreeItem. + * @param {nsICertTreeItem} certTreeItem + * The item to represent. + * @returns {String} + * A representative string. + */ +function certTreeItemToString(certTreeItem) { + let cert = certTreeItem.cert; + if (!cert) { + return certTreeItem.hostPort; + } + + const attributes = [ + cert.commonName, + cert.organizationalUnit, + cert.organization, + cert.subjectName, + ]; + for (let attribute of attributes) { + if (attribute) { + return attribute; + } + } + + let bundle = document.getElementById("pippki_bundle"); + return bundle.getFormattedString("certWithSerial", [cert.serialNumber]); +} + +/** + * onload() handler. + */ +function onLoad() { + let typeFlag = window.arguments[0]; + let bundle = document.getElementById("pippki_bundle"); + let title; + let confirm; + let impact; + + switch (typeFlag) { + case "mine_tab": + title = bundle.getString("deleteUserCertTitle"); + confirm = bundle.getString("deleteUserCertConfirm"); + impact = bundle.getString("deleteUserCertImpact"); + break; + case "websites_tab": + title = bundle.getString("deleteSslCertTitle3"); + confirm = bundle.getString("deleteSslCertConfirm3"); + impact = bundle.getString("deleteSslCertImpact3"); + break; + case "ca_tab": + title = bundle.getString("deleteCaCertTitle2"); + confirm = bundle.getString("deleteCaCertConfirm2"); + impact = bundle.getString("deleteCaCertImpactX2"); + break; + case "others_tab": + title = bundle.getString("deleteEmailCertTitle"); + confirm = bundle.getString("deleteEmailCertConfirm"); + impact = bundle.getString("deleteEmailCertImpactDesc"); + break; + case "orphan_tab": + title = bundle.getString("deleteOrphanCertTitle"); + confirm = bundle.getString("deleteOrphanCertConfirm"); + impact = ""; + break; + default: + return; + } + + document.title = title; + + setText("confirm", confirm); + + let box = document.getElementById("certlist"); + let certTreeItems = window.arguments[1]; + for (let certTreeItem of certTreeItems) { + let listItem = document.createElement("richlistitem"); + let label = document.createElement("label"); + label.setAttribute("value", certTreeItemToString(certTreeItem)); + listItem.appendChild(label); + box.appendChild(listItem); + } + + setText("impact", impact); +} + +/** + * ondialogaccept() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogAccept() { + let retVals = window.arguments[2]; + retVals.deleteConfirmed = true; + return true; +} + +/** + * ondialogcancel() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogCancel() { + let retVals = window.arguments[2]; + retVals.deleteConfirmed = false; + return true; +} diff --git a/security/manager/pki/resources/content/deletecert.xul b/security/manager/pki/resources/content/deletecert.xul new file mode 100644 index 000000000..40b0c081b --- /dev/null +++ b/security/manager/pki/resources/content/deletecert.xul @@ -0,0 +1,28 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<dialog id="deleteCertificate" + title="&certmgr.deletecert.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad();" + buttons="accept,cancel" + ondialogaccept="return onDialogAccept();" + ondialogcancel="return onDialogCancel();"> + + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + + <script type="application/javascript" src="chrome://pippki/content/deletecert.js"/> + <script type="application/javascript" src="pippki.js" /> + + <description id="confirm" style="width: 400px;"/> + <richlistbox id="certlist" class="box-padded" flex="1" + style="min-height: 8em; height: 8em; min-width: 35em;"/> + <description id="impact" style="width: 400px;"/> + +</dialog> 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 000000000..7d16c5888 --- /dev/null +++ b/security/manager/pki/resources/content/device_manager.js @@ -0,0 +1,509 @@ +/* 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 nsIFilePicker = Components.interfaces.nsIFilePicker; +const nsFilePicker = "@mozilla.org/filepicker;1"; +const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot; +const nsIPKCS11Module = Components.interfaces.nsIPKCS11Module; +const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1"; +const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB; +const nsIPK11Token = Components.interfaces.nsIPK11Token; +const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1"; +const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB; +const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; +const nsDialogParamBlock = "@mozilla.org/embedcomp/dialogparam;1"; +const nsIPKCS11 = Components.interfaces.nsIPKCS11; +const nsPKCS11ContractID = "@mozilla.org/security/pkcs11;1"; + +var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {}); + +var bundle; +var secmoddb; +var skip_enable_buttons = false; + +var smartCardObserver = { + observe: function() { + onSmartCardChange(); + } +}; + +function DeregisterSmartCardObservers() +{ + Services.obs.removeObserver(smartCardObserver, "smartcard-insert"); + Services.obs.removeObserver(smartCardObserver, "smartcard-remove"); +} + +/* Do the initial load of all PKCS# modules and list them. */ +function LoadModules() +{ + bundle = document.getElementById("pippki_bundle"); + secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB); + Services.obs.addObserver(smartCardObserver, "smartcard-insert", false); + Services.obs.addObserver(smartCardObserver, "smartcard-remove", false); + + RefreshDeviceList(); +} + +function getPKCS11() +{ + return Components.classes[nsPKCS11ContractID].getService(nsIPKCS11); +} + +function getNSSString(name) +{ + return document.getElementById("pipnss_bundle").getString(name); +} + +function doPrompt(msg) +{ + let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.nsIPromptService); + prompts.alert(window, null, msg); +} + +function doConfirm(msg) +{ + let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.nsIPromptService); + return prompts.confirm(window, null, msg); +} + +function RefreshDeviceList() +{ + let modules = secmoddb.listModules(); + while (modules.hasMoreElements()) { + let module = modules.getNext().QueryInterface(nsIPKCS11Module); + let slotnames = []; + let slots = module.listSlots(); + while (slots.hasMoreElements()) { + let slot = slots.getNext().QueryInterface(nsIPKCS11Slot); + // Token names are preferred because NSS prefers lookup by token name. + slotnames.push(slot.tokenName ? slot.tokenName : slot.name); + } + AddModule(module.name, slotnames); + } + + // Set the text on the FIPS button. + SetFIPSButton(); +} + +function SetFIPSButton() +{ + var fipsButton = document.getElementById("fipsbutton"); + var label; + if (secmoddb.isFIPSEnabled) { + label = bundle.getString("disable_fips"); + } else { + label = bundle.getString("enable_fips"); + } + fipsButton.setAttribute("label", label); + + 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.createElement("treeitem"); + var row = document.createElement("treerow"); + var cell = document.createElement("treecell"); + cell.setAttribute("label", module); + row.appendChild(cell); + item.appendChild(row); + var parent = document.createElement("treechildren"); + for (let slot of slots) { + var child_item = document.createElement("treeitem"); + var child_row = document.createElement("treerow"); + var child_cell = document.createElement("treecell"); + child_cell.setAttribute("label", slot); + child_row.appendChild(child_cell); + child_item.appendChild(child_row); + child_item.setAttribute("pk11kind", "slot"); + parent.appendChild(child_item); + } + item.appendChild(parent); + item.setAttribute("pk11kind", "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() +{ + var tree = document.getElementById('device_tree'); + if (tree.currentIndex < 0) return; + var item = tree.contentView.getItemAtIndex(tree.currentIndex); + selected_slot = null; + selected_module = null; + if (item) { + var kind = item.getAttribute("pk11kind"); + var module_name; + if (kind == "slot") { + // get the module cell for this slot cell + var cell = item.parentNode.parentNode.firstChild.firstChild; + module_name = cell.getAttribute("label"); + var module = secmoddb.findModuleByName(module_name); + // get the cell for the selected row (the slot to display) + cell = item.firstChild.firstChild; + var slot_name = cell.getAttribute("label"); + selected_slot = module.findSlotByName(slot_name); + } else { // (kind == "module") + // get the cell for the selected row (the module to display) + cell = item.firstChild.firstChild; + module_name = cell.getAttribute("label"); + selected_module = secmoddb.findModuleByName(module_name); + } + } +} + +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"; + } + } + } + } + showSlotInfo(); + } + var thebutton = document.getElementById('login_button'); + thebutton.setAttribute("disabled", login_toggle); + thebutton = document.getElementById('logout_button'); + thebutton.setAttribute("disabled", logout_toggle); + thebutton = document.getElementById('change_pw_button'); + thebutton.setAttribute("disabled", pw_toggle); + thebutton = document.getElementById('unload_button'); + thebutton.setAttribute("disabled", unload_toggle); + // not implemented + //thebutton = document.getElementById('change_slotname_button'); + //thebutton.setAttribute("disabled", toggle); +} + +// clear the display of information for the slot +function ClearInfoList() +{ + let infoList = document.getElementById("info_list"); + while (infoList.hasChildNodes()) { + infoList.removeChild(infoList.firstChild); + } +} + +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.removeChild(deviceList.firstChild); + } +} + + +// show a list of info about a slot +function showSlotInfo() +{ + var present = true; + ClearInfoList(); + switch (selected_slot.status) { + case nsIPKCS11Slot.SLOT_DISABLED: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_disabled"), + "tok_status"); + present = false; + break; + case nsIPKCS11Slot.SLOT_NOT_PRESENT: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_notpresent"), + "tok_status"); + present = false; + break; + case nsIPKCS11Slot.SLOT_UNINITIALIZED: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_uninitialized"), + "tok_status"); + break; + case nsIPKCS11Slot.SLOT_NOT_LOGGED_IN: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_notloggedin"), + "tok_status"); + break; + case nsIPKCS11Slot.SLOT_LOGGED_IN: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_loggedin"), + "tok_status"); + break; + case nsIPKCS11Slot.SLOT_READY: + AddInfoRow(bundle.getString("devinfo_status"), + bundle.getString("devinfo_stat_ready"), + "tok_status"); + break; + } + AddInfoRow(bundle.getString("devinfo_desc"), + selected_slot.desc, "slot_desc"); + AddInfoRow(bundle.getString("devinfo_manID"), + selected_slot.manID, "slot_manID"); + AddInfoRow(bundle.getString("devinfo_hwversion"), + selected_slot.HWVersion, "slot_hwv"); + AddInfoRow(bundle.getString("devinfo_fwversion"), + selected_slot.FWVersion, "slot_fwv"); + if (present) { + showTokenInfo(); + } +} + +function showModuleInfo() +{ + ClearInfoList(); + AddInfoRow(bundle.getString("devinfo_modname"), + selected_module.name, "module_name"); + AddInfoRow(bundle.getString("devinfo_modpath"), + selected_module.libName, "module_path"); +} + +// add a row to the info list, as [col1 col2] (ex.: ["status" "logged in"]) +function AddInfoRow(col1, col2, cell_id) +{ + var tree = document.getElementById("info_list"); + var item = document.createElement("treeitem"); + var row = document.createElement("treerow"); + var cell1 = document.createElement("treecell"); + cell1.setAttribute("label", col1); + cell1.setAttribute("crop", "never"); + row.appendChild(cell1); + var cell2 = document.createElement("treecell"); + cell2.setAttribute("label", col2); + 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()) { + tok_status.setAttribute("label", + bundle.getString("devinfo_stat_loggedin")); + } else { + tok_status.setAttribute("label", + bundle.getString("devinfo_stat_notloggedin")); + } + } catch (e) { + doPrompt(bundle.getString("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()) { + tok_status.setAttribute("label", + bundle.getString("devinfo_stat_loggedin")); + } else { + tok_status.setAttribute("label", + bundle.getString("devinfo_stat_notloggedin")); + } + } catch (e) { + } + enableButtons(); +} + +// load a new device +function doLoad() +{ + window.open("load_device.xul", "loaddevice", "chrome,centerscreen,modal"); + ClearDeviceList(); + RefreshDeviceList(); +} + +function deleteSelected() +{ + getSelectedItem(); + if (selected_module && + doConfirm(getNSSString("DelModuleWarning"))) { + try { + getPKCS11().deleteModule(selected_module.name); + } + catch (e) { + doPrompt(getNSSString("DelModuleError")); + return false; + } + selected_module = null; + return true; + } + return false; +} + +function doUnload() +{ + if (deleteSelected()) { + ClearDeviceList(); + RefreshDeviceList(); + } +} + +// handle card insertion and removal +function onSmartCardChange() +{ + var tree = document.getElementById('device_tree'); + var index = tree.currentIndex; + tree.currentIndex = 0; + ClearDeviceList(); + RefreshDeviceList(); + tree.currentIndex = index; + enableButtons(); +} + +function changePassword() +{ + getSelectedItem(); + let params = Components.classes[nsDialogParamBlock] + .createInstance(nsIDialogParamBlock); + params.SetString(1, selected_slot.tokenName); + window.openDialog("changepassword.xul", "", "chrome,centerscreen,modal", + params); + showSlotInfo(); + enableButtons(); +} + +// browse fs for PKCS#11 device +function doBrowseFiles() +{ + var srbundle = document.getElementById("pippki_bundle"); + var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker); + fp.init(window, + srbundle.getString("loadPK11TokenDialog"), + nsIFilePicker.modeOpen); + fp.appendFilters(nsIFilePicker.filterAll); + if (fp.show() == nsIFilePicker.returnOK) { + var pathbox = document.getElementById("device_path"); + pathbox.setAttribute("value", fp.file.path); + } +} + +function doLoadDevice() +{ + var name_box = document.getElementById("device_name"); + var path_box = document.getElementById("device_path"); + try { + getPKCS11().addModule(name_box.value, path_box.value, 0, 0); + } catch (e) { + if (e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) { + doPrompt(getNSSString("AddModuleDup")); + } else { + doPrompt(getNSSString("AddModuleFailure")); + } + + return false; + } + return true; +} + +// ------------------------------------- Old code + +function showTokenInfo() +{ + //ClearInfoList(); + var selected_token = selected_slot.getToken(); + AddInfoRow(bundle.getString("devinfo_label"), + selected_token.tokenLabel, "tok_label"); + AddInfoRow(bundle.getString("devinfo_manID"), + selected_token.tokenManID, "tok_manID"); + AddInfoRow(bundle.getString("devinfo_serialnum"), + selected_token.tokenSerialNumber, "tok_sNum"); + AddInfoRow(bundle.getString("devinfo_hwversion"), + selected_token.tokenHWVersion, "tok_hwv"); + AddInfoRow(bundle.getString("devinfo_fwversion"), + 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 = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB); + var internal_token = tokendb.getInternalKeyToken(); // nsIPK11Token + var slot = secmoddb.findSlotByName(internal_token.tokenName); + switch (slot.status) { + case nsIPKCS11Slot.SLOT_UNINITIALIZED: + case nsIPKCS11Slot.SLOT_READY: + // Token has either no or an empty password. + doPrompt(bundle.getString("fips_nonempty_password_required")); + return; + } + } + + try { + secmoddb.toggleFIPSMode(); + } + catch (e) { + doPrompt(bundle.getString("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.xul b/security/manager/pki/resources/content/device_manager.xul new file mode 100644 index 000000000..0a5b4ca91 --- /dev/null +++ b/security/manager/pki/resources/content/device_manager.xul @@ -0,0 +1,90 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % deviceManangerDTD SYSTEM "chrome://pippki/locale/deviceManager.dtd"> +%deviceManangerDTD; +<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" > +%pippkiDTD; +]> + +<dialog id="devicemanager" + windowtype="mozilla:devicemanager" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&devmgr.title;" + style="&devmgr.style2;" + persist="screenX screenY width height" + onload="LoadModules();" + onunload="DeregisterSmartCardObservers();" + buttons="accept"> + +<stringbundleset id="stringbundleset"> + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + <stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/> +</stringbundleset> + +<script type="application/javascript" src="chrome://pippki/content/device_manager.js"/> + +<grid flex="1" style="margin:5px"> + <columns> + <column flex="1"/> + <column flex="3"/> + <column/> + </columns> + <rows> + <row flex="1"> + <vbox> <!-- 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" label="&devmgr.devlist.label;"/> + </treecols> + <treechildren id="device_list"/> + </tree> + </vbox> <!-- / List of devices --> + <vbox> <!-- Device status --> + <tree id="info_tree" seltype="single" hidecolumnpicker="true" + flex="1" style="min-width: 10em"> + <treecols> + <treecol id="title1Col" flex="5" primary="true" label="&devmgr.details.title;"/> + <treecol id="title2Col" flex="7" label="&devmgr.details.title2;"/> + </treecols> + <treechildren id="info_list"/> + </tree> + </vbox> <!-- / Device status --> + <vbox> <!-- Buttons for manipulating devices --> + <button id="login_button" + label="&devmgr.button.login.label;" + accesskey="&devmgr.button.login.accesskey;" + oncommand="doLogin();" disabled="true"/> + <button id="logout_button" + label="&devmgr.button.logout.label;" + accesskey="&devmgr.button.logout.accesskey;" + oncommand="doLogout();" disabled="true"/> + <button id="change_pw_button" + label="&devmgr.button.changepw.label;" + accesskey="&devmgr.button.changepw.accesskey;" + oncommand="changePassword();" disabled="true"/> + <button id="load_button" + label="&devmgr.button.load.label;" + accesskey="&devmgr.button.load.accesskey;" + oncommand="doLoad();"/> + <button id="unload_button" + label="&devmgr.button.unload.label;" + accesskey="&devmgr.button.unload.accesskey;" + oncommand="doUnload();" disabled="true"/> + <button id="fipsbutton" + label="" + accesskey="&devmgr.button.fips.accesskey;" + oncommand="toggleFIPS();"/> + </vbox> <!-- / Buttons for manipulating devices --> + </row> + </rows> +</grid> + +</dialog> diff --git a/security/manager/pki/resources/content/downloadcert.js b/security/manager/pki/resources/content/downloadcert.js new file mode 100644 index 000000000..892e34be0 --- /dev/null +++ b/security/manager/pki/resources/content/downloadcert.js @@ -0,0 +1,92 @@ +/* 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.xul: a dialog that allows + * a user to confirm whether to import a certificate, and if so what trust + * to give it. + * @argument {nsISupports} window.arguments[0] + * Certificate to confirm import of, queryable to nsIX509Cert. + * @argument {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. + * @property {Boolean} trustForObjSign + * Set to true if the cert should be trusted for object signing, false + * otherwise. Undefined value if |importConfirmed| is not true. + */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +/** + * The cert to potentially import. + * @type nsIX509Cert + */ +var gCert; + +/** + * onload() handler. + */ +function onLoad() { + gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert); + + let bundle = document.getElementById("pippki_bundle"); + let caName = gCert.commonName; + if (caName.length == 0) { + caName = bundle.getString("unnamedCA"); + } + + setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName])); +} + +/** + * Handler for the "View Cert" button. + */ +function viewCert() { + viewCertHelper(window, gCert); +} + +/** + * ondialogaccept() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogAccept() { + let checkSSL = document.getElementById("trustSSL"); + let checkEmail = document.getElementById("trustEmail"); + let checkObjSign = document.getElementById("trustObjSign"); + + let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2); + retVals.setPropertyAsBool("importConfirmed", true); + retVals.setPropertyAsBool("trustForSSL", checkSSL.checked); + retVals.setPropertyAsBool("trustForEmail", checkEmail.checked); + retVals.setPropertyAsBool("trustForObjSign", checkObjSign.checked); + return true; +} + +/** + * ondialogcancel() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogCancel() { + let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2); + retVals.setPropertyAsBool("importConfirmed", false); + return true; +} diff --git a/security/manager/pki/resources/content/downloadcert.xul b/security/manager/pki/resources/content/downloadcert.xul new file mode 100644 index 000000000..9ddc88a17 --- /dev/null +++ b/security/manager/pki/resources/content/downloadcert.xul @@ -0,0 +1,69 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<dialog id="download_cert" + title="&downloadCert.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 46em;" + buttons="accept,cancel" + ondialogaccept="return onDialogAccept();" + ondialogcancel="return onDialogCancel();" + onload="onLoad();"> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" src="chrome://pippki/content/downloadcert.js"/> +<script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + + + <!-- Let 'em know what they're doing --> + <vbox> + <description>&downloadCert.message1;</description> + </vbox> + + <separator/> + + <!-- checkboxes for trust bits + - "do you want to?" + - * trust for SSL + - * trust for email + - * trust for object signing + --> + <vbox> + <description id="trustHeader"/> + <checkbox label="&downloadCert.trustSSL;" + id="trustSSL"/> + <checkbox label="&downloadCert.trustEmail;" + id="trustEmail"/> + <checkbox label="&downloadCert.trustObjSign;" + id="trustObjSign"/> + </vbox> + + <separator/> + + <vbox> + <description>&downloadCert.message3;</description> + <separator/> + <grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row> + <button id="viewC-button" + label="&downloadCert.viewCert.label;" + oncommand="viewCert();"/> + <description style="margin: 4px;">&downloadCert.viewCert.text;</description> + </row> + </rows> + </grid> + </vbox> + +</dialog> diff --git a/security/manager/pki/resources/content/editcacert.js b/security/manager/pki/resources/content/editcacert.js new file mode 100644 index 000000000..405d5281a --- /dev/null +++ b/security/manager/pki/resources/content/editcacert.js @@ -0,0 +1,58 @@ +/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +var gCertDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); +/** + * Cert to edit the trust of. + * @type nsIX509Cert + */ +var gCert; + +/** + * onload() handler. + */ +function onLoad() { + gCert = window.arguments[0]; + + let bundle = document.getElementById("pippki_bundle"); + setText("certmsg", + bundle.getFormattedString("editTrustCA", [gCert.commonName])); + + 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 objSignCheckbox = document.getElementById("trustObjSign"); + objSignCheckbox.checked = + gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT, + Ci.nsIX509CertDB.TRUSTED_OBJSIGN); +} + +/** + * ondialogaccept() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogAccept() { + let sslCheckbox = document.getElementById("trustSSL"); + let emailCheckbox = document.getElementById("trustEmail"); + let objSignCheckbox = document.getElementById("trustObjSign"); + let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0; + let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0; + let trustObjSign = objSignCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_OBJSIGN + : 0; + + gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT, + trustSSL | trustEmail | trustObjSign); + return true; +} diff --git a/security/manager/pki/resources/content/editcacert.xul b/security/manager/pki/resources/content/editcacert.xul new file mode 100644 index 000000000..46ea4f1f9 --- /dev/null +++ b/security/manager/pki/resources/content/editcacert.xul @@ -0,0 +1,36 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<dialog id="editCaCert" + title="&certmgr.editcacert.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + ondialogaccept="return onDialogAccept();" + onload="onLoad();" +> + + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + + <script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + <script type="application/javascript" + src="chrome://pippki/content/editcacert.js"/> + + <description id="certmsg"/> + <separator/> + <description>&certmgr.editcert.edittrust;</description> + <vbox align="start"> + <checkbox label="&certmgr.editcert.trustssl;" + id="trustSSL"/> + <checkbox label="&certmgr.editcert.trustemail;" + id="trustEmail"/> + <checkbox label="&certmgr.editcert.trustobjsign;" + id="trustObjSign"/> + </vbox> + +</dialog> diff --git a/security/manager/pki/resources/content/exceptionDialog.js b/security/manager/pki/resources/content/exceptionDialog.js new file mode 100644 index 000000000..0ca24a614 --- /dev/null +++ b/security/manager/pki/resources/content/exceptionDialog.js @@ -0,0 +1,368 @@ +/* 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 gBundleBrand; +var gPKIBundle; +var gSSLStatus; +var gCert; +var gChecking; +var gBroken; +var gNeedReset; +var gSecHistogram; +var gNsISecTel; + +Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); + +function badCertListener() {} +badCertListener.prototype = { + getInterface: function (aIID) { + return this.QueryInterface(aIID); + }, + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsIBadCertListener2) || + aIID.equals(Components.interfaces.nsIInterfaceRequestor) || + aIID.equals(Components.interfaces.nsISupports)) { + return this; + } + + throw new Error(Components.results.NS_ERROR_NO_INTERFACE); + }, + handle_test_result: function () { + if (gSSLStatus) { + gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert; + } + }, + notifyCertProblem: function MSR_notifyCertProblem(socketInfo, sslStatus, targetHost) { + gBroken = true; + gSSLStatus = sslStatus; + this.handle_test_result(); + return true; // suppress error UI + } +}; + +function initExceptionDialog() { + gNeedReset = false; + gDialog = document.documentElement; + gBundleBrand = document.getElementById("brand_bundle"); + gPKIBundle = document.getElementById("pippki_bundle"); + gSecHistogram = Components.classes["@mozilla.org/base/telemetry;1"]. + getService(Components.interfaces.nsITelemetry). + getHistogramById("SECURITY_UI"); + gNsISecTel = Components.interfaces.nsISecurityUITelemetry; + + var brandName = gBundleBrand.getString("brandShortName"); + setText("warningText", gPKIBundle.getFormattedString("addExceptionBrandedWarning2", [brandName])); + gDialog.getButton("extra1").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].sslStatus) { + gSSLStatus = args[0].sslStatus; + gCert = gSSLStatus.serverCert; + gBroken = true; + 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; + updateCertStatus(); + + window.setTimeout(checkCert, 0); + } + } + + // Set out parameter to false by default + args[0].exceptionAdded = false; + } +} + +/** + * Attempt to download the certificate for the location specified, and populate + * the Certificate Status section with the result. + */ +function checkCert() { + gCert = null; + gSSLStatus = null; + gChecking = true; + gBroken = false; + updateCertStatus(); + + var uri = getURI(); + + var req = new XMLHttpRequest(); + try { + if (uri) { + req.open('GET', uri.prePath, false); + req.channel.notificationCallbacks = new badCertListener(); + req.send(null); + } + } catch (e) { + // We *expect* exceptions if there are problems with the certificate + // presented by the site. Log it, just in case, but we can proceed here, + // with appropriate sanity checks + Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " + + "This results in a (mostly harmless) exception being thrown. " + + "Logged for information purposes only: " + e); + } finally { + gChecking = false; + } + + if (req.channel && req.channel.securityInfo) { + const Ci = Components.interfaces; + gSSLStatus = req.channel.securityInfo + .QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; + gCert = gSSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; + } + + updateCertStatus(); +} + +/** + * Build and return a URI, based on the information supplied in the + * Certificate Location fields + */ +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 fus = Components.classes["@mozilla.org/docshell/urifixup;1"] + .getService(Components.interfaces.nsIURIFixup); + let locationTextBox = document.getElementById("locationTextBox"); + let uri = fus.createFixupURI(locationTextBox.value, 0); + + if (!uri) { + return null; + } + + if (uri.scheme == "http") { + uri.scheme = "https"; + } + + if (uri.port == -1) { + uri.port = 443; + } + + return uri; +} + +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", ""); +} + +/** + * 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; + var shortDesc2, longDesc2; + var shortDesc3, longDesc3; + var use2 = false; + var use3 = false; + let bucketId = gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASE; + if (gCert) { + if (gBroken) { + var mms = "addExceptionDomainMismatchShort"; + var mml = "addExceptionDomainMismatchLong2"; + var exs = "addExceptionExpiredShort"; + var exl = "addExceptionExpiredLong2"; + var uts = "addExceptionUnverifiedOrBadSignatureShort"; + var utl = "addExceptionUnverifiedOrBadSignatureLong2"; + var use1 = false; + if (gSSLStatus.isDomainMismatch) { + bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN; + use1 = true; + shortDesc = mms; + longDesc = mml; + } + if (gSSLStatus.isNotValidAtThisTime) { + bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME; + if (!use1) { + use1 = true; + shortDesc = exs; + longDesc = exl; + } + else { + use2 = true; + shortDesc2 = exs; + longDesc2 = exl; + } + } + if (gSSLStatus.isUntrusted) { + bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED; + if (!use1) { + use1 = true; + shortDesc = uts; + longDesc = utl; + } else if (!use2) { + use2 = true; + shortDesc2 = uts; + longDesc2 = utl; + } else { + use3 = true; + shortDesc3 = uts; + longDesc3 = utl; + } + } + gSecHistogram.add(bucketId); + + // 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; + + setText("headerDescription", gPKIBundle.getString("addExceptionInvalidHeader")); + } + else { + shortDesc = "addExceptionValidShort"; + longDesc = "addExceptionValidLong"; + 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 + Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService) + .notifyObservers(null, "cert-exception-ui-ready", null); + } + else if (gChecking) { + shortDesc = "addExceptionCheckingShort"; + longDesc = "addExceptionCheckingLong2"; + // 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 = "addExceptionNoCertShort"; + longDesc = "addExceptionNoCertLong2"; + // 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; + } + + setText("statusDescription", gPKIBundle.getString(shortDesc)); + setText("statusLongDescription", gPKIBundle.getString(longDesc)); + + if (use2) { + setText("status2Description", gPKIBundle.getString(shortDesc2)); + setText("status2LongDescription", gPKIBundle.getString(longDesc2)); + } + + if (use3) { + setText("status3Description", gPKIBundle.getString(shortDesc3)); + setText("status3LongDescription", gPKIBundle.getString(longDesc3)); + } + + gNeedReset = true; +} + +/** + * Handle user request to display certificate details + */ +function viewCertButtonClick() { + gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_CLICK_VIEW_CERT); + if (gCert) { + viewCertHelper(this, gCert); + } +} + +/** + * Handle user request to add an exception for the specified cert + */ +function addException() { + if (!gCert || !gSSLStatus) { + return; + } + + var overrideService = Components.classes["@mozilla.org/security/certoverride;1"] + .getService(Components.interfaces.nsICertOverrideService); + var flags = 0; + let confirmBucketId = gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE; + if (gSSLStatus.isUntrusted) { + flags |= overrideService.ERROR_UNTRUSTED; + confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED; + } + if (gSSLStatus.isDomainMismatch) { + flags |= overrideService.ERROR_MISMATCH; + confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN; + } + if (gSSLStatus.isNotValidAtThisTime) { + flags |= overrideService.ERROR_TIME; + confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIME; + } + + var permanentCheckbox = document.getElementById("permanent"); + var shouldStorePermanently = permanentCheckbox.checked && !inPrivateBrowsingMode(); + if (!permanentCheckbox.checked) { + gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTION); + } + + gSecHistogram.add(confirmBucketId); + 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 true if this dialog is in private browsing mode. + */ +function inPrivateBrowsingMode() { + return PrivateBrowsingUtils.isWindowPrivate(window); +} diff --git a/security/manager/pki/resources/content/exceptionDialog.xul b/security/manager/pki/resources/content/exceptionDialog.xul new file mode 100644 index 000000000..6d417f360 --- /dev/null +++ b/security/manager/pki/resources/content/exceptionDialog.xul @@ -0,0 +1,89 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<dialog id="exceptiondialog" + windowtype="mozilla:exceptiondialog" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&exceptionMgr.title;" + buttons="cancel,extra1,extra2" + buttonlabelextra1="&exceptionMgr.exceptionButton.label;" + buttonaccesskeyextra1="&exceptionMgr.exceptionButton.accesskey;" + style="width: 46em; min-height: 38em;" + onload="initExceptionDialog();" + ondialogextra1="addException();" + ondialogextra2="checkCert();" + persist="screenX screenY width height" + defaultButton="extra2"> + + <stringbundleset id="stringbundleset"> + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + <stringbundle id="brand_bundle" src="chrome://branding/locale/brand.properties"/> + </stringbundleset> + + <script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + <script type="application/javascript" 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" + style="white-space: pre-wrap"/> + <description id="warningSupplemental" + style="font-weight: bold; white-space: pre-wrap;" + >&exceptionMgr.supplementalWarning;</description> + </vbox> + </hbox> + + <groupbox id="locationGroupBox"> + <caption label="&exceptionMgr.certlocation.caption2;"/> + <hbox align="center"> + <label control="locationTextBox" value="&exceptionMgr.certlocation.url;"/> + <textbox id="locationTextBox" flex="1" oninput="handleTextChange();" + value="https://" class="uri-element"/> + <button id="checkCertButton" disabled="true" dlgtype="extra2" + accesskey="&exceptionMgr.certlocation.accesskey;" + label="&exceptionMgr.certlocation.download;"/> + </hbox> + </groupbox> + + <groupbox id="certStatusGroupBox" flex="1"> + <caption label="&exceptionMgr.certstatus.caption;"/> + <hbox> + <description id="headerDescription" style="white-space: pre-wrap;" + flex="1"/> + <vbox> + <button id="viewCertButton" label="&exceptionMgr.certstatus.viewCert;" + accesskey="&exceptionMgr.certstatus.accesskey;" + disabled="true" oncommand="viewCertButtonClick();"/> + </vbox> + </hbox> + <description id="statusDescription" + style="font-weight: bold; padding-bottom: 1em;"/> + <description id="statusLongDescription" style="white-space: pre-wrap;"/> + <description id="status2Description" + style="font-weight: bold; padding-bottom: 1em;"/> + <description id="status2LongDescription" style="white-space: pre-wrap;"/> + <description id="status3Description" + style="font-weight: bold; padding-bottom: 1em;"/> + <description id="status3LongDescription" style="white-space: pre-wrap;"/> + <spacer flex="1"/> + <checkbox id="permanent" disabled="true" + label="&exceptionMgr.permanent.label;" + accesskey="&exceptionMgr.permanent.accesskey;"/> + </groupbox> +</dialog> diff --git a/security/manager/pki/resources/content/load_device.xul b/security/manager/pki/resources/content/load_device.xul new file mode 100644 index 000000000..45fffcee7 --- /dev/null +++ b/security/manager/pki/resources/content/load_device.xul @@ -0,0 +1,42 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % deviceManangerDTD SYSTEM "chrome://pippki/locale/deviceManager.dtd"> +%deviceManangerDTD; +<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" > +%pippkiDTD; +]> + +<dialog id="loaddevice" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="&loaddevice.title;" + buttons="accept,cancel" + ondialogaccept="return doLoadDevice();"> + +<stringbundleset id="stringbundleset"> + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + <stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/> +</stringbundleset> + + <script type="application/javascript" src="chrome://pippki/content/device_manager.js"/> + + <description>&loaddevice.info;</description> + <hbox align="center"> + <label value="&loaddevice.modname;" accesskey="&loaddevice.modname.accesskey;" + control="device_name"/> + <textbox id="device_name" flex="1" value="&loaddevice.modname.default;"/> + </hbox> + <hbox align="center"> + <label value="&loaddevice.filename;" accesskey="&loaddevice.filename.accesskey;" + control="device_path"/> + <textbox id="device_path" flex="1"/> + <button label="&loaddevice.browse;" flex="1" + accesskey="&loaddevice.browse.accesskey;" oncommand="doBrowseFiles();"/> + </hbox> + +</dialog> diff --git a/security/manager/pki/resources/content/pippki.js b/security/manager/pki/resources/content/pippki.js new file mode 100644 index 000000000..b660d2d93 --- /dev/null +++ b/security/manager/pki/resources/content/pippki.js @@ -0,0 +1,191 @@ +/* -*- 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.removeChild(element.firstChild); + } + element.appendChild(document.createTextNode(value)); +} + +const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs; +const nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1"; + +function viewCertHelper(parent, cert) { + if (!cert) { + return; + } + + var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs); + cd.viewCert(parent, cert); +} + +function getDERString(cert) +{ + var length = {}; + var derArray = cert.getRawDER(length); + var derString = ''; + for (var i = 0; i < derArray.length; i++) { + derString += String.fromCharCode(derArray[i]); + } + return derString; +} + +function getPKCS7String(cert, chainMode) +{ + var length = {}; + var pkcs7Array = cert.exportAsCMS(chainMode, length); + var pkcs7String = ''; + for (var i = 0; i < pkcs7Array.length; i++) { + pkcs7String += String.fromCharCode(pkcs7Array[i]); + } + return pkcs7String; +} + +function getPEMString(cert) +{ + var derb64 = btoa(getDERString(cert)); + // 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) +{ + var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.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 nsIFilePicker. + * + * @param {nsIX509Cert} cert + * The cert to generate a filename for. + * @returns {String} + * Generated filename. + */ +function certToFilename(cert) { + let filename = cert.commonName; + if (!filename) { + filename = cert.windowTitle; + } + + // Remove unneeded and/or unsafe characters. + filename = filename.replace(/\s/g, "") + .replace(/\./g, "") + .replace(/\\/g, "") + .replace(/\//g, ""); + + // 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 nsIFilePicker.defaultString says we should do + // anyways. + return `${filename}.${DEFAULT_CERT_EXTENSION}`; +} + +function exportToFile(parent, cert) +{ + var bundle = document.getElementById("pippki_bundle"); + if (!cert) { + return; + } + + var nsIFilePicker = Components.interfaces.nsIFilePicker; + var fp = Components.classes["@mozilla.org/filepicker;1"]. + createInstance(nsIFilePicker); + fp.init(parent, bundle.getString("SaveCertAs"), + nsIFilePicker.modeSave); + fp.defaultString = certToFilename(cert); + fp.defaultExtension = DEFAULT_CERT_EXTENSION; + fp.appendFilter(bundle.getString("CertFormatBase64"), "*.crt; *.pem"); + fp.appendFilter(bundle.getString("CertFormatBase64Chain"), "*.crt; *.pem"); + fp.appendFilter(bundle.getString("CertFormatDER"), "*.der"); + fp.appendFilter(bundle.getString("CertFormatPKCS7"), "*.p7c"); + fp.appendFilter(bundle.getString("CertFormatPKCS7Chain"), "*.p7c"); + fp.appendFilters(nsIFilePicker.filterAll); + var res = fp.show(); + if (res != nsIFilePicker.returnOK && res != nsIFilePicker.returnReplace) { + return; + } + + var content = ''; + switch (fp.filterIndex) { + case 1: + content = getPEMString(cert); + var chain = cert.getChain(); + for (let i = 1; i < chain.length; i++) { + content += getPEMString(chain.queryElementAt(i, Components.interfaces.nsIX509Cert)); + } + break; + case 2: + content = getDERString(cert); + break; + case 3: + content = getPKCS7String(cert, Components.interfaces.nsIX509Cert.CMS_CHAIN_MODE_CertOnly); + break; + case 4: + content = getPKCS7String(cert, Components.interfaces.nsIX509Cert.CMS_CHAIN_MODE_CertChainWithRoot); + break; + case 0: + default: + content = getPEMString(cert); + break; + } + var msg; + var written = 0; + try { + var file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(fp.file.path); + var fos = Components.classes["@mozilla.org/network/file-output-stream;1"]. + createInstance(Components.interfaces.nsIFileOutputStream); + // flags: PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE + fos.init(file, 0x02 | 0x08 | 0x20, 0o0644, 0); + written = fos.write(content, content.length); + fos.close(); + } catch (e) { + switch (e.result) { + case Components.results.NS_ERROR_FILE_ACCESS_DENIED: + msg = bundle.getString("writeFileAccessDenied"); + break; + case Components.results.NS_ERROR_FILE_IS_LOCKED: + msg = bundle.getString("writeFileIsLocked"); + break; + case Components.results.NS_ERROR_FILE_NO_DEVICE_SPACE: + case Components.results.NS_ERROR_FILE_DISK_FULL: + msg = bundle.getString("writeFileNoDeviceSpace"); + break; + default: + msg = e.message; + break; + } + } + if (written != content.length) { + if (msg.length == 0) { + msg = bundle.getString("writeFileUnknownError"); + } + alertPromptService(bundle.getString("writeFileFailure"), + bundle.getFormattedString("writeFileFailed", + [fp.file.path, msg])); + } +} diff --git a/security/manager/pki/resources/content/protectedAuth.js b/security/manager/pki/resources/content/protectedAuth.js new file mode 100644 index 000000000..3ffd96a83 --- /dev/null +++ b/security/manager/pki/resources/content/protectedAuth.js @@ -0,0 +1,42 @@ +/* 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"; + +function onLoad() +{ + let protectedAuthThread = + window.arguments[0].QueryInterface(Components.interfaces.nsIProtectedAuthThread); + + if (!protectedAuthThread) { + window.close(); + return; + } + + try { + let tokenName = protectedAuthThread.getTokenName(); + + let tag = document.getElementById("tokenName"); + tag.setAttribute("value", tokenName); + + window.setCursor("wait"); + + let obs = { + observe: function protectedAuthListenerObserve(subject, topic, data) { + if (topic == "operation-completed") { + window.close(); + } + } + }; + + protectedAuthThread.login(obs); + } catch (exception) { + window.close(); + } +} + +function onClose() +{ + window.setCursor("auto"); +} diff --git a/security/manager/pki/resources/content/protectedAuth.xul b/security/manager/pki/resources/content/protectedAuth.xul new file mode 100644 index 000000000..46aba7cce --- /dev/null +++ b/security/manager/pki/resources/content/protectedAuth.xul @@ -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/" type="text/css"?> + +<!DOCTYPE window SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<window + id="protectedAuth" title="&protectedAuth.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad();" + onclose="onClose();" +> + +<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + +<script type="application/javascript" src="pippki.js" /> +<script type="application/javascript" src="protectedAuth.js" /> +<script type="application/javascript" src="chrome://help/content/help.js" /> + +<vbox style="margin: 5px; max-width: 50em;"> + + <description>&protectedAuth.msg;</description> + + <hbox> + <description>&protectedAuth.tokenName.label;</description> + <description id="tokenName"></description> + </hbox> + +</vbox> +</window> diff --git a/security/manager/pki/resources/content/resetpassword.js b/security/manager/pki/resources/content/resetpassword.js new file mode 100644 index 000000000..35dc5b2e5 --- /dev/null +++ b/security/manager/pki/resources/content/resetpassword.js @@ -0,0 +1,47 @@ +/* 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 nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1"; +const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB; +const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; + +var tokenName; + +function onLoad() +{ + if ("arguments" in window) { + var params = window.arguments[0].QueryInterface(nsIDialogParamBlock); + tokenName = params.GetString(1); + } else { + tokenName = self.name; + } +} + +function resetPassword() +{ + var pk11db = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB); + var token = pk11db.findTokenByName(tokenName); + token.reset(); + + try { + var loginManager = Components.classes["@mozilla.org/login-manager;1"]. + getService(Components.interfaces.nsILoginManager); + loginManager.removeAllLogins(); + } catch (e) { + } + + var bundle = document.getElementById("pippki_bundle"); + var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(); + promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService); + if (promptService && bundle) { + promptService.alert(window, + bundle.getString("resetPasswordConfirmationTitle"), + bundle.getString("resetPasswordConfirmationMessage")); + } + + return true; +} + diff --git a/security/manager/pki/resources/content/resetpassword.xul b/security/manager/pki/resources/content/resetpassword.xul new file mode 100644 index 000000000..6da79b0d9 --- /dev/null +++ b/security/manager/pki/resources/content/resetpassword.xul @@ -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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<dialog id="reset_password" title="&resetPassword.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + buttonlabelaccept="&resetPasswordButtonLabel;" + defaultButton="cancel" + ondialogaccept="return resetPassword();" + style="width: 40em;" onload="onLoad();"> + + <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/> + + <script type="application/javascript" src="chrome://pippki/content/pippki.js"/> + <script type="application/javascript" 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>&resetPassword.text;</description> + </vbox> + </hbox> + </vbox> + </hbox> +</dialog> diff --git a/security/manager/pki/resources/content/setp12password.js b/security/manager/pki/resources/content/setp12password.js new file mode 100644 index 000000000..5a4836735 --- /dev/null +++ b/security/manager/pki/resources/content/setp12password.js @@ -0,0 +1,128 @@ +/* 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.xul: a dialog that lets + * the user confirm the password to set on a PKCS #12 file. + * @argument {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. + */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +/** + * onload() handler. + */ +function onLoad() { + // Ensure the first password textbox has focus. + document.getElementById("pw1").focus(); +} + +/** + * ondialogaccept() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogAccept() { + let password = document.getElementById("pw1").value; + + let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2); + retVals.setPropertyAsBool("confirmedPassword", true); + retVals.setPropertyAsAString("password", password); + return true; +} + +/** + * ondialogcancel() handler. + * + * @returns {Boolean} true to make the dialog close, false otherwise. + */ +function onDialogCancel() { + let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2); + retVals.setPropertyAsBool("confirmedPassword", false); + return true; +} + +/** + * 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.documentElement.getButton("accept").disabled = (pw1 != pw2); +} diff --git a/security/manager/pki/resources/content/setp12password.xul b/security/manager/pki/resources/content/setp12password.xul new file mode 100644 index 000000000..2bdcd5fe9 --- /dev/null +++ b/security/manager/pki/resources/content/setp12password.xul @@ -0,0 +1,51 @@ +<?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/" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/pippki.dtd"> + +<dialog id="setp12password" + title="&pkcs12.setpassword.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 48em;" + buttons="accept,cancel" + ondialogaccept="return onDialogAccept();" + ondialogcancel="return onDialogCancel();" + onload="onLoad();"> + + <script type="application/javascript" + src="chrome://pippki/content/setp12password.js"/> + + <description>&pkcs12.setpassword.message;</description> + <separator /> + <grid> + <columns> <column/> <column/> </columns> + <rows> + <row> + <label value="&pkcs12.setpassword.label1;"/> + <textbox id="pw1" type="password" oninput="onPasswordInput(true);"/> + </row> + <row> + <label value="&pkcs12.setpassword.label2;"/> + <textbox id="pw2" type="password" oninput="onPasswordInput(false);"/> + </row> + </rows> + </grid> + <separator/> + <description>&pkcs12.setpassword.reminder;</description> + <separator/> + <label value="&setPassword.meter.label;"/> + <grid style="margin: 4px;"> + <rows> <row/> </rows> + <columns> + <column style="margin: 5px;"> + <progressmeter flex="1" id="pwmeter" mode="determined" value="0" + orient="horizontal" + style="width: 20em; foreground-color: red"/> + </column> + </columns> + </grid> +</dialog> diff --git a/security/manager/pki/resources/content/viewCertDetails.xul b/security/manager/pki/resources/content/viewCertDetails.xul new file mode 100644 index 000000000..86acb751c --- /dev/null +++ b/security/manager/pki/resources/content/viewCertDetails.xul @@ -0,0 +1,104 @@ +<?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/. --> +<!DOCTYPE overlay SYSTEM "chrome://pippki/locale/certManager.dtd"> + +<overlay id="certViewerOverlay" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cert="http://netscape.com/rdf-cert#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<vbox class="box-padded" id="general_info"> + <vbox id="verify_info_box"> + <label id="verify_pending" value="&certmgr.pending.label;"/> + <label class="header" id="verified"/> + </vbox> + <separator class="groove"/> + <vbox flex="1"> + <grid> + <columns> + <column/> + <column flex="1"/> + </columns> + <rows> + <row> + <label class="header" value="&certmgr.subjectinfo.label;"/> + <spacer/> + <spacer/> + </row> + <row> + <label value="&certmgr.certdetail.cn;"/> + <textbox id="commonname" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.certdetail.o;"/> + <textbox id="organization" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.certdetail.ou;"/> + <textbox id="orgunit" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.certdetail.serialnumber;"/> + <textbox id="serialnumber" class="plain" readonly="true"/> + </row> + <row> + <separator class="thin"/> + <spacer/> + </row> + <row> + <label class="header" value="&certmgr.issuerinfo.label;"/> + <spacer/> + </row> + <row> + <label value="&certmgr.certdetail.cn;"/> + <textbox id="issuercommonname" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.certdetail.o;"/> + <textbox id="issuerorganization" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.certdetail.ou;"/> + <textbox id="issuerorgunit" class="plain" readonly="true"/> + </row> + <row> + <separator class="thin"/> + <spacer/> + </row> + <row> + <label class ="header" value="&certmgr.periodofvalidity.label;"/> + <spacer/> + </row> + <row> + <label value="&certmgr.begins;"/> + <textbox id="validitystart" class="plain" readonly="true"/> + </row> + <row> + <label value="&certmgr.expires;"/> + <textbox id="validityend" class="plain" readonly="true"/> + </row> + <row> + <separator class="thin"/> + <spacer/> + </row> + <row> + <label class="header" value="&certmgr.fingerprints.label;"/> + <spacer/> + </row> + <row> + <label value="&certmgr.certdetail.sha256fingerprint;"/> + <hbox> + <textbox id="sha256fingerprint" class="plain" readonly="true" multiline="true" + style="height: 6ex; width: 48ch; font-family: monospace;"/> + </hbox> + </row> + <row> + <label value="&certmgr.certdetail.sha1fingerprint;"/> + <textbox id="sha1fingerprint" class="plain" readonly="true" style="min-width:34em;"/> + </row> + </rows> + </grid> + </vbox> +</vbox> +</overlay> diff --git a/security/manager/pki/resources/jar.mn b/security/manager/pki/resources/jar.mn new file mode 100644 index 000000000..36018524f --- /dev/null +++ b/security/manager/pki/resources/jar.mn @@ -0,0 +1,43 @@ +# 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/CAOverlay.xul (content/CAOverlay.xul) + content/pippki/MineOverlay.xul (content/MineOverlay.xul) + content/pippki/OrphanOverlay.xul (content/OrphanOverlay.xul) + content/pippki/OthersOverlay.xul (content/OthersOverlay.xul) + content/pippki/WebSitesOverlay.xul (content/WebSitesOverlay.xul) + content/pippki/certDump.xul (content/certDump.xul) + content/pippki/certManager.js (content/certManager.js) + content/pippki/certManager.xul (content/certManager.xul) + content/pippki/certViewer.js (content/certViewer.js) + content/pippki/certViewer.xul (content/certViewer.xul) + content/pippki/changepassword.js (content/changepassword.js) + content/pippki/changepassword.xul (content/changepassword.xul) + content/pippki/choosetoken.js (content/choosetoken.js) + content/pippki/choosetoken.xul (content/choosetoken.xul) + content/pippki/clientauthask.js (content/clientauthask.js) + content/pippki/clientauthask.xul (content/clientauthask.xul) + content/pippki/createCertInfo.js (content/createCertInfo.js) + content/pippki/createCertInfo.xul (content/createCertInfo.xul) + content/pippki/deletecert.js (content/deletecert.js) + content/pippki/deletecert.xul (content/deletecert.xul) + content/pippki/device_manager.js (content/device_manager.js) + content/pippki/device_manager.xul (content/device_manager.xul) + content/pippki/downloadcert.js (content/downloadcert.js) + content/pippki/downloadcert.xul (content/downloadcert.xul) + content/pippki/editcacert.js (content/editcacert.js) + content/pippki/editcacert.xul (content/editcacert.xul) + content/pippki/exceptionDialog.js (content/exceptionDialog.js) +* content/pippki/exceptionDialog.xul (content/exceptionDialog.xul) + content/pippki/load_device.xul (content/load_device.xul) + content/pippki/pippki.js (content/pippki.js) + content/pippki/protectedAuth.js (content/protectedAuth.js) + content/pippki/protectedAuth.xul (content/protectedAuth.xul) + content/pippki/resetpassword.js (content/resetpassword.js) + content/pippki/resetpassword.xul (content/resetpassword.xul) + content/pippki/setp12password.js (content/setp12password.js) + content/pippki/setp12password.xul (content/setp12password.xul) + content/pippki/viewCertDetails.xul (content/viewCertDetails.xul) diff --git a/security/manager/pki/resources/moz.build b/security/manager/pki/resources/moz.build new file mode 100644 index 000000000..eb4454d28 --- /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']
\ No newline at end of file |