Bug 1186286 - Consult more than just the CN when deleting a cert so it's always clear what's being deleted. r=mgoodwin draft
authorCykesiopka <cykesiopka.bmo@gmail.com>
Fri, 07 Oct 2016 20:14:27 +0800
changeset 422091 50ce122972802693fcfa3a16f3166a377c056eb5
parent 422090 ac1f8bddeb3f2f6bf3457909b637d07c7e93fa00
child 533255 1323922fab36efd6229f50f48dfcce79c623490f
push id31687
push usercykesiopka.bmo@gmail.com
push dateFri, 07 Oct 2016 12:33:49 +0000
reviewersmgoodwin
bugs1186286
milestone52.0a1
Bug 1186286 - Consult more than just the CN when deleting a cert so it's always clear what's being deleted. r=mgoodwin Deleting certs via the cert manager results in a confirmation dialog popping up. This dialog contains a list of certs that were selected for deletion. Currently, only the Common Name of each cert is used to represent it in the list. This is a problem for certs that don't have a CN. More jarringly, the cert manager itself falls back to various other attributes, so for example selecting a cert that is represented by its OU ends up popping up a dialog where an empty list is shown. MozReview-Commit-ID: 3z4BebW8Ucd
security/manager/locales/en-US/chrome/pippki/pippki.properties
security/manager/pki/resources/content/certManager.js
security/manager/pki/resources/content/deletecert.js
security/manager/ssl/tests/mochitest/browser/browser.ini
security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
security/manager/ssl/tests/mochitest/browser/has-cn.pem
security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec
security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem
security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec
security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem
security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec
security/manager/ssl/tests/mochitest/browser/has-o.pem
security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec
security/manager/ssl/tests/mochitest/browser/has-ou.pem
security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec
security/manager/ssl/tests/mochitest/browser/moz.build
--- a/security/manager/locales/en-US/chrome/pippki/pippki.properties
+++ b/security/manager/locales/en-US/chrome/pippki/pippki.properties
@@ -1,24 +1,26 @@
-#
 # 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/.
 
 CertPassPrompt=Please enter the Personal Security Password for the PSM Private Keys security device.
 
-#These are for dialogs
-#Download Cert dialog
+# LOCALIZATION NOTE(certWithSerial): Used for semi-uniquely representing a cert.
+# %1$S is the serial number of the cert in AA:BB:CC hex format.
+certWithSerial=Certificate with serial number: %1$S
+
+# Download Cert dialog
 newCAMessage1=Do you want to trust “%S” for the following purposes?
 unnamedCA=Certificate Authority (unnamed)
 
-#For editing cert trust
+# For editing cert trust
 editTrustCA=The certificate “%S” represents a Certificate Authority.
 
-#For Deleting Certificates
+# For Deleting Certificates
 deleteSslCertConfirm3=Are you sure you want to delete these server exceptions?
 deleteSslCertImpact3=If you delete a server exception, you restore the usual security checks for that server and require it uses a valid certificate.
 deleteSslCertTitle3=Delete Server Certificate Exceptions
 
 deleteUserCertConfirm=Are you sure you want to delete these certificates?
 deleteUserCertImpact=If you delete one of your own certificates, you can no longer use it to identify yourself.
 deleteUserCertTitle=Delete your Certificates
 
@@ -28,35 +30,34 @@ deleteCaCertTitle2=Delete or Distrust CA
 
 deleteEmailCertConfirm=Are you sure you want to delete these people’s e-mail certificates?
 deleteEmailCertImpactDesc=If you delete a person’s e-mail certificate, you will no longer be able to send encrypted e-mail to that person.
 deleteEmailCertTitle=Delete E-Mail Certificates
 
 deleteOrphanCertConfirm=Are you sure you want to delete these certificates?
 deleteOrphanCertTitle=Delete Certificates
 
-
-#PKCS#12 file dialogs
+# PKCS#12 file dialogs
 chooseP12RestoreFileDialog2=Certificate File to Import
 chooseP12BackupFileDialog=File Name to Backup
 file_browse_PKCS12_spec=PKCS12 Files
 getPKCS12FilePasswordMessage=Please enter the password that was used to encrypt this certificate backup:
 
-#Cert verification
+# Cert verification
 certVerified=This certificate has been verified for the following uses:
 certNotVerified_CertRevoked=Could not verify this certificate because it has been revoked.
 certNotVerified_CertExpired=Could not verify this certificate because it has expired.
 certNotVerified_CertNotTrusted=Could not verify this certificate because it is not trusted.
 certNotVerified_IssuerNotTrusted=Could not verify this certificate because the issuer is not trusted.
 certNotVerified_IssuerUnknown=Could not verify this certificate because the issuer is unknown.
 certNotVerified_CAInvalid=Could not verify this certificate because the CA certificate is invalid.
 certNotVerified_AlgorithmDisabled=Could not verify this certificate because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
 certNotVerified_Unknown=Could not verify this certificate for unknown reasons.
 
-#Client auth
+# Client auth
 clientAuthRemember=Remember this decision
 # LOCALIZATION NOTE(clientAuthNickAndSerial): Represents a single cert when the
 # user is choosing from a list of certificates.
 # %1$S is the nickname of the cert.
 # %2$S is the serial number of the cert in AA:BB:CC hex format.
 clientAuthNickAndSerial=%1$S [%2$S]
 # LOCALIZATION NOTE(clientAuthHostnameAndPort):
 # %1$S is the hostname of the server.
@@ -87,38 +88,38 @@ clientAuthKeyUsages=Key Usages: %1$S
 clientAuthEmailAddresses=Email addresses: %1$S
 # LOCALIZATION NOTE(clientAuthIssuedBy): %1$S is the Distinguished Name of the
 # cert which issued the selected cert.
 clientAuthIssuedBy=Issued by: %1$S
 # LOCALIZATION NOTE(clientAuthStoredOn): %1$S is the name of the PKCS #11 token
 # the selected cert is stored on.
 clientAuthStoredOn=Stored on: %1$S
 
-#Page Info
+# Page Info
 pageInfo_NoEncryption=Connection Not Encrypted
 pageInfo_Privacy_None1=The website %S does not support encryption for the page you are viewing.
 pageInfo_Privacy_None2=Information sent over the Internet without encryption can be seen by other people while it is in transit. 
 pageInfo_Privacy_None4=The page you are viewing was not encrypted before being transmitted over the Internet.
 # LOCALIZATION NOTE (pageInfo_EncryptionWithBitsAndProtocol and pageInfo_BrokenEncryption):
 # %1$S is the name of the encryption standard,
 # %2$S is the key size of the cipher.
 # %3$S is protocol version like "SSL 3" or "TLS 1.2"
 pageInfo_EncryptionWithBitsAndProtocol=Connection Encrypted (%1$S, %2$S bit keys, %3$S)
 pageInfo_BrokenEncryption=Broken Encryption (%1$S, %2$S bit keys, %3$S)
 pageInfo_Privacy_Encrypted1=The page you are viewing was encrypted before being transmitted over the Internet.
 pageInfo_Privacy_Encrypted2=Encryption makes it difficult for unauthorized people to view information traveling between computers. It is therefore unlikely that anyone read this page as it traveled across the network.
 pageInfo_MixedContent=Connection Partially Encrypted
 pageInfo_MixedContent2=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
 pageInfo_WeakCipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website’s behavior.
 
-#Cert Viewer
+# Cert Viewer
 certDetails=Certificate Viewer:
 notPresent=<Not Part Of Certificate>
 
-#Token Manager
+# Token Manager
 password_not_set=(not set)
 failed_pw_change=Unable to change Master Password.
 incorrect_pw=You did not enter the correct current Master Password. Please try again.
 pw_change_ok=Master Password successfully changed.
 pw_erased_ok=Warning! You have deleted your Master Password. 
 pw_not_wanted=Warning! You have decided not to use a Master Password.
 pw_empty_warning=Your stored web and email passwords, form data, and private keys will not be protected.
 pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
@@ -161,17 +162,17 @@ CertFormatPKCS7=X.509 Certificate (PKCS#
 CertFormatPKCS7Chain=X.509 Certificate with chain (PKCS#7)
 writeFileFailure=File Error
 writeFileFailed=Can’t write to file %S:\n%S.
 writeFileAccessDenied=Access denied
 writeFileIsLocked=File is locked
 writeFileNoDeviceSpace=No space left on device
 writeFileUnknownError=Unknown error
 
-#Add Security Exception dialog
+# Add Security Exception dialog
 addExceptionBrandedWarning2=You are about to override how %S identifies this site.
 addExceptionInvalidHeader=This site attempts to identify itself with invalid information.
 addExceptionDomainMismatchShort=Wrong Site
 addExceptionDomainMismatchLong2=The certificate belongs to a different site, which could mean that someone is trying to impersonate this site.
 addExceptionExpiredShort=Outdated Information
 addExceptionExpiredLong2=The certificate is not currently valid. It may have been stolen or lost, and could be used by someone to impersonate this site.
 addExceptionUnverifiedOrBadSignatureShort=Unknown Identity
 addExceptionUnverifiedOrBadSignatureLong2=The certificate is not trusted because it hasn’t been verified as issued by a trusted authority using a secure signature.
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -1,14 +1,16 @@
 /* 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 nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
@@ -404,40 +406,33 @@ function deleteCerts()
     return;
   }
 
   var params = Components.classes[nsDialogParamBlock].createInstance(nsIDialogParamBlock);
 
   var selTab = document.getElementById('certMgrTabbox').selectedItem;
   var selTabID = selTab.getAttribute('id');
 
-  params.SetNumberStrings(numcerts + 1);
-
   switch (selTabID) {
     case "mine_tab":
     case "websites_tab":
     case "ca_tab":
     case "others_tab":
     case "orphan_tab":
       params.SetString(0, selTabID);
       break;
     default:
       return;
   }
 
-  params.SetInt(0, numcerts);
-  for (let t = 0; t < numcerts; t++) {
-    let treeItem = selected_tree_items[t];
-    let cert = treeItem.cert;
-    if (!cert) {
-      params.SetString(t + 1, treeItem.hostPort);
-    } else {
-      params.SetString(t + 1, cert.commonName);
-    }
+  let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+  for (let treeItem of selected_tree_items) {
+    array.appendElement(treeItem, false);
   }
+  params.objects = array;
 
   window.openDialog('chrome://pippki/content/deletecert.xul', "",
                     'chrome,centerscreen,modal', params);
 
   if (params.GetInt(1) == 1) {
     // user closed dialog with OK
     var treeView = null;
     var loadParam = null;
--- a/security/manager/pki/resources/content/deletecert.js
+++ b/security/manager/pki/resources/content/deletecert.js
@@ -1,33 +1,60 @@
 /* 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 nsIX509Cert = Components.interfaces.nsIX509Cert;
-const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
-const nsIX509CertDB = Components.interfaces.nsIX509CertDB;
-const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+/**
+ * Param block to get passed in args and to set return values to.
+ * @type nsIDialogParamBlock
+ */
+var gParams;
 
-var certdb;
-var gParams;
+/**
+ * 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]);
+}
 
 function setWindowName()
 {
-  gParams = window.arguments[0].QueryInterface(nsIDialogParamBlock);
-
-  var typeFlag = gParams.GetString(0);
-  var numberOfCerts = gParams.GetInt(0);
+  gParams = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
 
-  var bundle = document.getElementById("pippki_bundle");
-  var title;
-  var confirm;
-  var impact;
+  let typeFlag = gParams.GetString(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":
@@ -49,27 +76,26 @@ function setWindowName()
       title = bundle.getString("deleteOrphanCertTitle");
       confirm = bundle.getString("deleteOrphanCertConfirm");
       impact = "";
       break;
     default:
       return;
   }
 
-  var confirReference = document.getElementById('confirm');
-  var impactReference = document.getElementById('impact');
   document.title = title;
 
   setText("confirm", confirm);
 
   let box = document.getElementById("certlist");
-  for (let x = 0; x < numberOfCerts; x++) {
-    var listItem = document.createElement("richlistitem");
-    var label = document.createElement("label");
-    label.setAttribute("value", gParams.GetString(x + 1));
+  for (let x = 0; x < gParams.objects.length; x++) {
+    let listItem = document.createElement("richlistitem");
+    let label = document.createElement("label");
+    let certTreeItem = gParams.objects.queryElementAt(x, Ci.nsICertTreeItem);
+    label.setAttribute("value", certTreeItemToString(certTreeItem));
     listItem.appendChild(label);
     box.appendChild(listItem);
   }
 
   setText("impact", impact);
 }
 
 function doOK()
--- a/security/manager/ssl/tests/mochitest/browser/browser.ini
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -4,8 +4,9 @@ support-files =
   head.js
   *.pem
 
 [browser_bug627234_perwindowpb.js]
 [browser_certificateManagerLeak.js]
 [browser_certViewer.js]
 [browser_clientAuth_connection.js]
 [browser_clientAuth_ui.js]
+[browser_deleteCert_ui.js]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_deleteCert_ui.js
@@ -0,0 +1,226 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests various aspects of the cert delete confirmation dialog.
+// Among other things, tests that for each type of cert that can be deleted:
+// 1. The various lines of explanation text are correctly set.
+// 2. The implementation correctly falls back through multiple cert attributes
+//    to determine what to display to represent a cert.
+
+/**
+ * An array of tree items corresponding to TEST_CASES.
+ * @type nsIMutableArray<nsICertTreeItem>
+ */
+var gCertArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+var gImportedCerts = [];
+
+const FAKE_HOST_PORT = "Fake host and port";
+
+/**
+ * @typedef {TestCase}
+ * @type Object
+ * @property {String} certFilename
+ *           Filename of the cert, or null if we don't want to import a cert for
+ *           this test case (i.e. we expect the hostPort attribute of
+ *           nsICertTreeItem to be used).
+ * @property {String} expectedDisplayString
+ *           The string we expect the UI to display to represent the given cert.
+ */
+
+/**
+ * A list of test cases representing certs that get "deleted".
+ * @type TestCase[]
+ */
+const TEST_CASES = [
+  { certFilename: null,
+    expectedDisplayString: FAKE_HOST_PORT },
+  { certFilename: "has-cn.pem",
+    expectedDisplayString: "Foo" },
+  { certFilename: "has-ou.pem",
+    expectedDisplayString: "Bar" },
+  { certFilename: "has-o.pem",
+    expectedDisplayString: "Baz" },
+  { certFilename: "has-non-empty-subject.pem",
+    expectedDisplayString: "C=US" },
+  { certFilename: "has-empty-subject.pem",
+    expectedDisplayString: "Certificate with serial number: 0A" },
+];
+
+/**
+ * Opens the cert delete confirmation dialog.
+ *
+ * @param {String} tabID
+ *        The ID of the cert category tab the certs to delete belong to.
+ * @returns {Promise}
+ *          A promise that resolves when the dialog has finished loading, with
+ *          an array consisting of:
+ *            1. The window of the opened dialog.
+ *            2. The nsIDialogParamBlock passed to the dialog.
+ */
+function openDeleteCertConfirmDialog(tabID) {
+  let params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
+                 .createInstance(Ci.nsIDialogParamBlock);
+  params.SetString(0, tabID);
+  params.objects = gCertArray;
+
+  let win = window.openDialog("chrome://pippki/content/deletecert.xul", "", "",
+                              params);
+  return new Promise((resolve, reject) => {
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad);
+      resolve([win, params]);
+    });
+  });
+}
+
+registerCleanupFunction(() => {
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+  for (let cert of gImportedCerts) {
+    certdb.deleteCertificate(cert);
+  }
+});
+
+add_task(function* setup() {
+  for (let testCase of TEST_CASES) {
+    let cert = null;
+    if (testCase.certFilename) {
+      cert = yield readCertificate(testCase.certFilename, ",,", gImportedCerts);
+    }
+    let certTreeItem = {
+      hostPort: FAKE_HOST_PORT,
+      cert: cert,
+      QueryInterface(iid) {
+        if (iid.equals(Ci.nsICertTreeItem)) {
+          return this;
+        }
+
+        throw new Error(Cr.NS_ERROR_NO_INTERFACE);
+      }
+    };
+    gCertArray.appendElement(certTreeItem, false);
+  }
+});
+
+/**
+ * Test helper for the below test cases.
+ *
+ * @param {String} tabID
+ *        ID of the cert category tab the certs to delete belong to.
+ * @param {String} expectedTitle
+ *        Title the dialog is expected to have.
+ * @param {String} expectedConfirmMsg
+ *        Confirmation message the dialog is expected to show.
+ * @param {String} expectedImpact
+ *        Impact the dialog is expected to show.
+ */
+function* testHelper(tabID, expectedTitle, expectedConfirmMsg, expectedImpact) {
+  let [win, params] = yield openDeleteCertConfirmDialog(tabID);
+  let certList = win.document.getElementById("certlist");
+
+  Assert.equal(win.document.title, expectedTitle,
+               `Actual and expected titles should match for ${tabID}`);
+  Assert.equal(win.document.getElementById("confirm").textContent,
+               expectedConfirmMsg,
+               `Actual and expected confirm message should match for ${tabID}`);
+  Assert.equal(win.document.getElementById("impact").textContent,
+               expectedImpact,
+               `Actual and expected impact should match for ${tabID}`);
+
+  Assert.equal(certList.itemCount, TEST_CASES.length,
+               `No. of certs displayed should match for ${tabID}`);
+  for (let i = 0; i < certList.itemCount; i++) {
+    Assert.equal(certList.getItemAtIndex(i).label,
+                 TEST_CASES[i].expectedDisplayString,
+                 "Actual and expected display string should match for " +
+                 `index ${i} for ${tabID}`);
+  }
+
+  yield BrowserTestUtils.closeWindow(win);
+}
+
+// Test deleting certs from the "Your Certificates" tab.
+add_task(function* testDeletePersonalCerts() {
+  const expectedTitle = "Delete your Certificates";
+  const expectedConfirmMsg =
+    "Are you sure you want to delete these certificates?";
+  const expectedImpact =
+    "If you delete one of your own certificates, you can no longer use it to " +
+    "identify yourself.";
+  yield* testHelper("mine_tab", expectedTitle, expectedConfirmMsg,
+                    expectedImpact);
+});
+
+// Test deleting certs from the "People" tab.
+add_task(function* testDeleteOtherPeopleCerts() {
+  const expectedTitle = "Delete E-Mail Certificates";
+  // ’ doesn't seem to work when embedded in the following literals, which is
+  // why escape codes are used instead.
+  const expectedConfirmMsg =
+    "Are you sure you want to delete these people\u2019s e-mail certificates?";
+  const expectedImpact =
+    "If you delete a person\u2019s e-mail certificate, you will no longer be " +
+    "able to send encrypted e-mail to that person.";
+  yield* testHelper("others_tab", expectedTitle, expectedConfirmMsg,
+                    expectedImpact);
+});
+
+// Test deleting certs from the "Servers" tab.
+add_task(function* testDeleteServerCerts() {
+  const expectedTitle = "Delete Server Certificate Exceptions";
+  const expectedConfirmMsg =
+    "Are you sure you want to delete these server exceptions?";
+  const expectedImpact =
+    "If you delete a server exception, you restore the usual security checks " +
+    "for that server and require it uses a valid certificate.";
+  yield* testHelper("websites_tab", expectedTitle, expectedConfirmMsg,
+                    expectedImpact);
+});
+
+// Test deleting certs from the "Authorities" tab.
+add_task(function* testDeleteCACerts() {
+  const expectedTitle = "Delete or Distrust CA Certificates";
+  const expectedConfirmMsg =
+    "You have requested to delete these CA certificates. For built-in " +
+    "certificates all trust will be removed, which has the same effect. Are " +
+    "you sure you want to delete or distrust?";
+  const expectedImpact =
+    "If you delete or distrust a certificate authority (CA) certificate, " +
+    "this application will no longer trust any certificates issued by that CA.";
+  yield* testHelper("ca_tab", expectedTitle, expectedConfirmMsg,
+                    expectedImpact);
+});
+
+// Test deleting certs from the "Other" tab.
+add_task(function* testDeleteOtherCerts() {
+  const expectedTitle = "Delete Certificates";
+  const expectedConfirmMsg =
+    "Are you sure you want to delete these certificates?";
+  const expectedImpact = "";
+  yield* testHelper("orphan_tab", expectedTitle, expectedConfirmMsg,
+                    expectedImpact);
+});
+
+// Test that the right values are returned when the dialog is accepted.
+add_task(function* testAcceptDialogReturnValues() {
+  let [win, params] = yield openDeleteCertConfirmDialog("ca_tab" /*arbitrary*/);
+  info("Accepting dialog");
+  win.document.getElementById("deleteCertificate").acceptDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.equal(params.GetInt(1), 1,
+               "1 should be returned to signal user accepted");
+});
+
+// Test that the right values are returned when the dialog is canceled.
+add_task(function* testCancelDialogReturnValues() {
+  let [win, params] = yield openDeleteCertConfirmDialog("ca_tab" /*arbitrary*/);
+  info("Canceling dialog");
+  win.document.getElementById("deleteCertificate").cancelDialog();
+  yield BrowserTestUtils.windowClosed(win);
+
+  Assert.equal(params.GetInt(1), 0,
+               "0 should be returned to signal user canceled");
+});
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-cn.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0DCCAbqgAwIBAgIUBdYRNGqgoPy8QL2qxNIAIbGiUsowCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMDcxDDAKBgNVBAMMA0ZvbzEMMAoGA1UECwwDQmFyMQwwCgYDVQQKDANCYXox
+CzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohR
+qESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+Kv
+WnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+
+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPv
+JxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5
+Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6
+clHEMdUDrNoYCjXtjQIDAQABMAsGCSqGSIb3DQEBCwOCAQEAB1QaKkpRAhxoeSa6
+vfhiJS9a3kC+ChE/0zRqu/wVaNkxlmnq6rR5e0iZ7K2Xo7FDPxfC14UolaTFs7KH
+bEA4XzCvctDD7J1MTtcGZKDjH8ua4unpm8Ux6cnXqy3SpHzirshxU/cxOm8JtjmI
+89Xq1BQ34fck+wNkK894d3+uniMyy4WrAiVeQHWQ6cUnzqt+8THgWXoRQSvMgEcM
+ItSIylwll9pILylS5p7wI1upXkZD0V72WR+Pp/XSP07MOR13MPkAjZxXP/8hGQRR
+URtLhwGrVf5Ovc9JnFhv3CqkKR/sEcGooA3VcCafVnxyucj1bmXmbkgWCE+b/zMQ
+cVSPvw==
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-cn.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/CN=Foo/OU=Bar/O=Baz/C=US
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIIChjCCAXCgAwIBAgIBCjALBgkqhkiG9w0BAQswDTELMAkGA1UEAwwCY2EwIhgP
+MjAxNDExMjcwMDAwMDBaGA8yMDE3MDIwNDAwMDAwMFowADCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhX
+bCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQ
+OCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9
+uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFb
+t+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhO
+NsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAATALBgkqhkiG
+9w0BAQsDggEBAKAFuEA9TJ+o8myZ0AciYyCjsIcQhfPvL16vfhVmbr8IXudjeIQm
+orBPLWOZWT/IVeLOYU+GecS86IFWdBjKLOMoPkZC3o6nQ95txGQrODN1uMQOQBc3
+0AMQFnKbGCgoN8VtAsn7zppipvc6FcG83fh8YnrtGa8r9LBd5r2lX3npvG8qmhWD
+J+2heHQp8D3NfCzad+jQy4hUhHalbH5aTky3T/1UGfgr/cXsDAcZb09Wd5tzDVpS
+CP6iZyumHdH24ta5yYTHEEKDtR+/YU987crD79DcBK+7K2k4feFEzLfPDzmbtM3G
+ZadO9SSf9GWHUovurJJNJaIBMYpNGeq4nT4=
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-empty-subject.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:
+serialNumber:10
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpjCCAZCgAwIBAgIUS5/RALAav3GqFmr/hG5FumIUs/gwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMA0xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptu
+Gobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO
+7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgf
+qDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/yt
+HSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcx
+uLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMAsGCSqGSIb3DQEBCwOCAQEArANWYwZp
+g5Z9sRrvPIaEjgJO4fnTf5/3nECTFljJH7YjGzFhnEGk52aRGubowhTOxAtqK1lc
+cA5QRMuQ/Qrr7xZOdGfoxoMnQXONcEQNobcWbxa0Z8VZrpanAhXevlk5Z/8it96g
+uLtCmgz32L6J2FpsftHI38+IAGjbgZjyOnyr4jGwgltZUKV1YRGClnMQnI6N/8TH
+UrESziJ6JmVWnIwvKWvtwy68gRLvSaJ+lD1KTrqOVris0yPcR+dWlXaHovegxwDB
+gkhtFOYJePK8p7op+WAyKlM922UYBC4X2h/4QeySrC2LYauGFz3ZARO2ylCJ1CsJ
+ypuqIXstbO0h3g==
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-non-empty-subject.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/C=US
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-o.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICtDCCAZ6gAwIBAgIUG62RAQRi0nQBf5QEVbVQ6tS4QOwwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMBsxDDAKBgNVBAoMA0JhejELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQC6iFGoRI4W1kH9braIBjYQPTwT2erkNUq07PVoV2wk
+e8HHJajg2B+9sZwGm24ahvJr4q9adWtqZHEIeqVap0WH9xzVJJwCfs1D/B5p0Dgg
+KZOrIMNJ5Nu5TMJrbA7tFYIP8X6taRqx0wI6iypB7qdw4A8Njf1mCyuwJJKkfbmI
+YXmQsVeQPdI7xeC4SB+oN9OIQ+8nFthVt2Zaqn4CkC86exCABiTMHGyXrZZhW7fi
+lhLAdTGjDJHdtMr3/K0dJdMJ77kXDqdo4bN7LyJvaeO0ipVhHe4m1iWdq5EITjbL
+HCQELL8Wiy/l8Y+ZFzG4s/5JI/pyUcQx1QOs2hgKNe2NAgMBAAEwCwYJKoZIhvcN
+AQELA4IBAQBKExQp1zdXXkl60yJsRcUNBPbprmHDlYLFtAcAY7s8+IKn+6kWUgNG
+1wrKsaoSJUxK8MO6ULSbK8Xb9djjZ5Xcy622cOe3wk3/AIwc8nhAE6d5mtCDzOGR
+MWEn+wGsCtW+eWDTlefx/+D3zBNAsb7hyW54JKDjfRADN6VtFO4ca4Ybw92G6CRf
+g452KvRl90oZ2cxXyxOP3qdMOpB5a9DkUVS39n7iWgv//f3qlo3eUlHHoeygJQOM
+qca5cnQbYKhat2edgK5OY4eHCFOWXpxRy83/zNywLki4zozwXnTWSmuBpe2jZEev
+nOCxqVD3xceDzZ3NNkFj+c7Q2IX0ykmf
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-o.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/O=Baz/C=US
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-ou.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICwjCCAaygAwIBAgIUI7neyiHwgLifhsVz4PP5NKkCWqIwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMCkxDDAKBgNVBAsMA0JhcjEMMAoGA1UECgwDQmF6MQswCQYDVQQGEwJVUzCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogGNhA9
+PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqnRYf3
+HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHup3Dg
+Dw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7
+EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p47SK
+lWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0C
+AwEAATALBgkqhkiG9w0BAQsDggEBAGKpDaMHux0AWDSH/3scV6W+6ZFtsAH/kTw2
+zPfPad1z7xECUDzNaLjJYdTiXplBFoB9lFNmM2pb/Z98FZPLCK7wD36jLtSrBjLm
+KlDKieE3aoHHe2RANz3fEod7jV/YVuZXLNQaMEMXAdXV1qfrMOuyiYgo+Crr/EMO
+ApAQvOPxOR34Z7mBPnxVwn3w85zxrpcRzbg+UFJcr1yk0yKrlawSCB1CV/itIkCJ
+sdpqub2wRy6w3nCwaCmp92bmDAOS9K4UG4tCvwKFihYLBx1MiVmUZC90nrXSynh8
+b1hdzFnKZxzoZgM7yNuqw06Xzx0eg8yqi4wLjj+uA3UdZHP7MAk=
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/has-ou.pem.certspec
@@ -0,0 +1,2 @@
+issuer:ca
+subject:/OU=Bar/O=Baz/C=US
--- a/security/manager/ssl/tests/mochitest/browser/moz.build
+++ b/security/manager/ssl/tests/mochitest/browser/moz.build
@@ -13,16 +13,21 @@ BROWSER_CHROME_MANIFESTS += ['browser.in
 # when they're automatically generated.)
 #test_certificates = (
 #    'ca.pem',
 #    'code-ee.pem',
 #    'ee-from-expired-ca.pem',
 #    'ee-from-untrusted-ca.pem',
 #    'email-ee.pem',
 #    'expired-ca.pem',
+#    'has-cn.pem',
+#    'has-empty-subject.pem',
+#    'has-non-empty-subject.pem',
+#    'has-o.pem',
+#    'has-ou.pem',
 #    'invalid.pem',
 #    'md5-ee.pem',
 #    'revoked.pem',
 #    'ssl-ee.pem',
 #    'unknown-issuer.pem',
 #    'untrusted-ca.pem',
 #)
 #