diff --git a/security/manager/locales/en-US/chrome/pippki/certManager.dtd b/security/manager/locales/en-US/chrome/pippki/certManager.dtd
index 804eaead702..762af9fd7f4 100644
--- a/security/manager/locales/en-US/chrome/pippki/certManager.dtd
+++ b/security/manager/locales/en-US/chrome/pippki/certManager.dtd
@@ -102,21 +102,23 @@
-
+
-
+
-
+
+
+
-
+
-
+
-
+
diff --git a/security/manager/locales/en-US/chrome/pippki/pippki.properties b/security/manager/locales/en-US/chrome/pippki/pippki.properties
index a1a0ab29bb2..65baa063e34 100644
--- a/security/manager/locales/en-US/chrome/pippki/pippki.properties
+++ b/security/manager/locales/en-US/chrome/pippki/pippki.properties
@@ -195,3 +195,17 @@ file_browse_Certificate_spec=Certificate Files
# Form Signing confirmation prompt
formSigningIntro=The site '%S' has requested that you sign the following text message:
+
+# Cert export
+SaveCertAs=Save Certificate To File
+CertFormatBase64=X.509 Certificate (PEM)
+CertFormatBase64Chain=X.509 Certificate with chain (PEM)
+CertFormatDER=X.509 Certificate (DER)
+CertFormatPKCS7=X.509 Certificate (PKCS#7)
+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
diff --git a/security/manager/pki/resources/content/CAOverlay.xul b/security/manager/pki/resources/content/CAOverlay.xul
index 0357a913cfb..4e52e3b51c4 100644
--- a/security/manager/pki/resources/content/CAOverlay.xul
+++ b/security/manager/pki/resources/content/CAOverlay.xul
@@ -66,19 +66,23 @@
+
diff --git a/security/manager/pki/resources/content/MineOverlay.xul b/security/manager/pki/resources/content/MineOverlay.xul
index 798e184eaee..e6a1015140f 100644
--- a/security/manager/pki/resources/content/MineOverlay.xul
+++ b/security/manager/pki/resources/content/MineOverlay.xul
@@ -82,23 +82,23 @@
diff --git a/security/manager/pki/resources/content/OrphanOverlay.xul b/security/manager/pki/resources/content/OrphanOverlay.xul
index 293807ad848..1a66fff60ca 100644
--- a/security/manager/pki/resources/content/OrphanOverlay.xul
+++ b/security/manager/pki/resources/content/OrphanOverlay.xul
@@ -67,11 +67,15 @@
+
diff --git a/security/manager/pki/resources/content/OthersOverlay.xul b/security/manager/pki/resources/content/OthersOverlay.xul
index e4cd82f6b5b..8a5dc4e81f2 100644
--- a/security/manager/pki/resources/content/OthersOverlay.xul
+++ b/security/manager/pki/resources/content/OthersOverlay.xul
@@ -73,19 +73,23 @@
+
diff --git a/security/manager/pki/resources/content/WebSitesOverlay.xul b/security/manager/pki/resources/content/WebSitesOverlay.xul
index dfe1f917f72..ce8da8d4e6a 100644
--- a/security/manager/pki/resources/content/WebSitesOverlay.xul
+++ b/security/manager/pki/resources/content/WebSitesOverlay.xul
@@ -72,19 +72,23 @@
+
diff --git a/security/manager/pki/resources/content/certDump.xul b/security/manager/pki/resources/content/certDump.xul
index ad680b67df4..590cc1083e5 100644
--- a/security/manager/pki/resources/content/certDump.xul
+++ b/security/manager/pki/resources/content/certDump.xul
@@ -65,5 +65,12 @@
+
+
+
+
+
diff --git a/security/manager/pki/resources/content/certManager.js b/security/manager/pki/resources/content/certManager.js
index 6c081292d5d..042f6b7f115 100644
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -198,6 +198,8 @@ function ca_enableButtons()
enableViewButton.setAttribute("disabled",toggle);
var enableEditButton=document.getElementById('ca_editButton');
enableEditButton.setAttribute("disabled",edit_toggle);
+ var enableExportButton=document.getElementById('ca_exportButton');
+ enableExportButton.setAttribute("disabled",toggle);
var enableDeleteButton=document.getElementById('ca_deleteButton');
enableDeleteButton.setAttribute("disabled",toggle);
}
@@ -228,6 +230,8 @@ function websites_enableButtons()
enableViewButton.setAttribute("disabled",toggle);
var enableEditButton=document.getElementById('websites_editButton');
enableEditButton.setAttribute("disabled",toggle);
+ var enableExportButton=document.getElementById('websites_exportButton');
+ enableExportButton.setAttribute("disabled",toggle);
var enableDeleteButton=document.getElementById('websites_deleteButton');
enableDeleteButton.setAttribute("disabled",toggle);
}
@@ -243,6 +247,8 @@ function email_enableButtons()
enableViewButton.setAttribute("disabled",toggle);
var enableEditButton=document.getElementById('email_editButton');
enableEditButton.setAttribute("disabled",toggle);
+ var enableExportButton=document.getElementById('email_exportButton');
+ enableExportButton.setAttribute("disabled",toggle);
var enableDeleteButton=document.getElementById('email_deleteButton');
enableDeleteButton.setAttribute("disabled",toggle);
}
@@ -256,6 +262,8 @@ function orphan_enableButtons()
}
var enableViewButton=document.getElementById('orphan_viewButton');
enableViewButton.setAttribute("disabled",toggle);
+ var enableExportButton=document.getElementById('orphan_exportButton');
+ enableExportButton.setAttribute("disabled",toggle);
var enableDeleteButton=document.getElementById('orphan_deleteButton');
enableDeleteButton.setAttribute("disabled",toggle);
}
@@ -334,6 +342,18 @@ function restoreCerts()
}
}
+function exportCerts()
+{
+ getSelectedCerts();
+ var numcerts = selected_certs.length;
+ if (!numcerts)
+ return;
+
+ for (var t=0; t
diff --git a/security/manager/pki/resources/content/pippki.js b/security/manager/pki/resources/content/pippki.js
index e36dfa5ba2b..916a7d13a54 100644
--- a/security/manager/pki/resources/content/pippki.js
+++ b/security/manager/pki/resources/content/pippki.js
@@ -22,6 +22,7 @@
*
* Contributor(s):
* Javier Delgadillo
+ * Kaspar Brand
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -61,3 +62,133 @@ function viewCertHelper(parent, cert) {
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 = {};
+ cert.QueryInterface(Components.interfaces.nsIX509Cert3);
+ 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 = null;
+ var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Components.interfaces.nsIPromptService);
+ ps.alert(window, title, message);
+}
+
+function exportToFile(parent, cert)
+{
+ var bundle = srGetStrBundle("chrome://pippki/locale/pippki.properties");
+ if (!cert)
+ return;
+
+ var nsIFilePicker = Components.interfaces.nsIFilePicker;
+ var fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ fp.init(parent, bundle.GetStringFromName("SaveCertAs"),
+ nsIFilePicker.modeSave);
+ var filename = cert.commonName;
+ if (!filename.length)
+ filename = cert.windowTitle;
+ // remove all whitespace from the default filename
+ fp.defaultString = filename.replace(/\s*/g,'');
+ fp.defaultExtension = "crt";
+ fp.appendFilter(bundle.GetStringFromName("CertFormatBase64"), "*.crt; *.pem");
+ fp.appendFilter(bundle.GetStringFromName("CertFormatBase64Chain"), "*.crt; *.pem");
+ fp.appendFilter(bundle.GetStringFromName("CertFormatDER"), "*.der");
+ fp.appendFilter(bundle.GetStringFromName("CertFormatPKCS7"), "*.p7c");
+ fp.appendFilter(bundle.GetStringFromName("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 (var 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.nsIX509Cert3.CMS_CHAIN_MODE_CertOnly);
+ break;
+ case 4:
+ content = getPKCS7String(cert, Components.interfaces.nsIX509Cert3.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, 00644, 0);
+ written = fos.write(content, content.length);
+ fos.close();
+ }
+ catch(e) {
+ switch (e.result) {
+ case Components.results.NS_ERROR_FILE_ACCESS_DENIED:
+ msg = bundle.GetStringFromName("writeFileAccessDenied");
+ break;
+ case Components.results.NS_ERROR_FILE_IS_LOCKED:
+ msg = bundle.GetStringFromName("writeFileIsLocked");
+ break;
+ case Components.results.NS_ERROR_FILE_NO_DEVICE_SPACE:
+ case Components.results.NS_ERROR_FILE_DISK_FULL:
+ msg = bundle.GetStringFromName("writeFileNoDeviceSpace");
+ break;
+ default:
+ msg = e.message;
+ break;
+ }
+ }
+ if (written != content.length) {
+ if (!msg.length)
+ msg = bundle.GetStringFromName("writeFileUnknownError");
+ alertPromptService(bundle.GetStringFromName("writeFileFailure"),
+ bundle.formatStringFromName("writeFileFailed",
+ [ fp.file.path, msg ], 2));
+ }
+}
diff --git a/security/manager/pki/resources/content/viewCertDetails.js b/security/manager/pki/resources/content/viewCertDetails.js
index 766e6353833..2917c7ad27f 100644
--- a/security/manager/pki/resources/content/viewCertDetails.js
+++ b/security/manager/pki/resources/content/viewCertDetails.js
@@ -23,6 +23,7 @@
* Ian McGreer
* Javier Delgadillo
* Kai Engert
+ * Kaspar Brand
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -321,3 +322,29 @@ function getProxyOnUIThread(aObject, aInterface) {
aInterface, aObject, 5);
// 5 == NS_PROXY_ALWAYS | NS_PROXY_SYNC
}
+
+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,null);
+ return cert;
+ }
+ /* shouldn't really happen */
+ return null;
+}
diff --git a/security/manager/ssl/public/nsIX509Cert3.idl b/security/manager/ssl/public/nsIX509Cert3.idl
index 9ce589ac1ca..fbe7a9f0fb7 100644
--- a/security/manager/ssl/public/nsIX509Cert3.idl
+++ b/security/manager/ssl/public/nsIX509Cert3.idl
@@ -46,9 +46,16 @@ interface nsICertVerificationListener;
* TODO: nsIX509Cert3 should be derived from nsIX509Cert2
* (and nsIX509Cert2 derived from nsIX509Cert)
*/
-[scriptable, uuid(402aee39-653c-403f-8be1-6d1824223bf9)]
+[scriptable, uuid(89d9f248-1160-4935-9f99-2bdbf67b5849)]
interface nsIX509Cert3 : nsISupports {
+ /**
+ * Constants for specifying the chain mode when exporting a certificate
+ */
+ const unsigned long CMS_CHAIN_MODE_CertOnly = 1;
+ const unsigned long CMS_CHAIN_MODE_CertChain = 2;
+ const unsigned long CMS_CHAIN_MODE_CertChainWithRoot = 3;
+
/**
* Async version of nsIX509Cert::getUsagesArray()
*
@@ -56,6 +63,19 @@ interface nsIX509Cert3 : nsISupports {
* availability of results will be notified.
*/
void requestUsagesArrayAsync(in nsICertVerificationListener cvl);
+
+ /**
+ * Obtain the certificate wrapped in a PKCS#7 SignedData structure,
+ * with or without the certificate chain
+ *
+ * @param chainMode Whether to include the chain (with or without the root),
+ see CMS_CHAIN_MODE constants.
+ * @param length The number of bytes of the PKCS#7 data.
+ * @param data The bytes representing the PKCS#7 wrapped certificate.
+ */
+ void exportAsCMS(in unsigned long chainMode,
+ out unsigned long length,
+ [retval, array, size_is(length)] out octet data);
};
[scriptable, uuid(2fd0a785-9f2d-4327-8871-8c3e0783891d)]
diff --git a/security/manager/ssl/src/nsNSSCertificate.cpp b/security/manager/ssl/src/nsNSSCertificate.cpp
index e7311699e56..1358b811d63 100644
--- a/security/manager/ssl/src/nsNSSCertificate.cpp
+++ b/security/manager/ssl/src/nsNSSCertificate.cpp
@@ -43,6 +43,7 @@
#include "prprf.h"
#include "nsNSSComponent.h" // for PIPNSS string bundle calls.
+#include "nsNSSCleaner.h"
#include "nsCOMPtr.h"
#include "nsIMutableArray.h"
#include "nsNSSCertificate.h"
@@ -86,6 +87,8 @@ extern "C" {
#include "ssl.h"
#include "ocsp.h"
#include "plbase64.h"
+#include "cms.h"
+#include "cert.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gPIPNSSLog;
@@ -93,6 +96,12 @@ extern PRLogModuleInfo* gPIPNSSLog;
static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
+NSSCleanupAutoPtrClass(CERTCertificateList, CERT_DestroyCertificateList)
+NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate)
+NSSCleanupAutoPtrClass(NSSCMSMessage, NSS_CMSMessage_Destroy)
+NSSCleanupAutoPtrClass_WithParam(PLArenaPool, PORT_FreeArena, FalseParam, PR_FALSE)
+NSSCleanupAutoPtrClass(NSSCMSSignedData, NSS_CMSSignedData_Destroy)
+
// This is being stored in an PRUint32 that can otherwise
// only take values from nsIX509Cert's list of cert types.
// As nsIX509Cert is frozen, we choose a value not contained
@@ -967,6 +976,137 @@ nsNSSCertificate::GetRawDER(PRUint32 *aLength, PRUint8 **aArray)
return NS_ERROR_FAILURE;
}
+NS_IMETHODIMP
+nsNSSCertificate::ExportAsCMS(PRUint32 chainMode,
+ PRUint32 *aLength, PRUint8 **aArray)
+{
+ NS_ENSURE_ARG(aLength);
+ NS_ENSURE_ARG(aArray);
+
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mCert)
+ return NS_ERROR_FAILURE;
+
+ switch (chainMode) {
+ case nsIX509Cert3::CMS_CHAIN_MODE_CertOnly:
+ case nsIX509Cert3::CMS_CHAIN_MODE_CertChain:
+ case nsIX509Cert3::CMS_CHAIN_MODE_CertChainWithRoot:
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ };
+
+ PLArenaPool *arena = PORT_NewArena(1024);
+ PLArenaPoolCleanerFalseParam arenaCleaner(arena);
+ if (!arena) {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - out of memory\n"));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NSSCMSMessage *cmsg = NSS_CMSMessage_Create(nsnull);
+ NSSCMSMessageCleaner cmsgCleaner(cmsg);
+ if (!cmsg) {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't create CMS message\n"));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ /*
+ * first, create SignedData with the certificate only (no chain)
+ */
+ NSSCMSSignedData *sigd = NSS_CMSSignedData_CreateCertsOnly(cmsg, mCert, PR_FALSE);
+ NSSCMSSignedDataCleaner sigdCleaner(sigd);
+ if (!sigd) {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't create SignedData\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ /*
+ * Calling NSS_CMSSignedData_CreateCertsOnly() will not allow us
+ * to specify the inclusion of the root, but CERT_CertChainFromCert() does.
+ * Since CERT_CertChainFromCert() also includes the certificate itself,
+ * we have to start at the issuing cert (to avoid duplicate certs
+ * in the SignedData).
+ */
+ if (chainMode == nsIX509Cert3::CMS_CHAIN_MODE_CertChain ||
+ chainMode == nsIX509Cert3::CMS_CHAIN_MODE_CertChainWithRoot) {
+ CERTCertificate *issuerCert = CERT_FindCertIssuer(mCert, PR_Now(), certUsageAnyCA);
+ CERTCertificateCleaner issuerCertCleaner(issuerCert);
+ /*
+ * the issuerCert of a self signed root is the cert itself,
+ * so make sure we're not adding duplicates, again
+ */
+ if (issuerCert && issuerCert != mCert) {
+ PRBool includeRoot =
+ (chainMode == nsIX509Cert3::CMS_CHAIN_MODE_CertChainWithRoot);
+ CERTCertificateList *certChain = CERT_CertChainFromCert(issuerCert, certUsageAnyCA, includeRoot);
+ CERTCertificateListCleaner certChainCleaner(certChain);
+ if (certChain) {
+ if (NSS_CMSSignedData_AddCertList(sigd, certChain) == SECSuccess) {
+ certChainCleaner.detach();
+ }
+ else {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't add chain\n"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+ else {
+ /* try to add the issuerCert, at least */
+ if (NSS_CMSSignedData_AddCertificate(sigd, issuerCert)
+ == SECSuccess) {
+ issuerCertCleaner.detach();
+ }
+ else {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't add issuer cert\n"));
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ NSSCMSContentInfo *cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd)
+ == SECSuccess) {
+ sigdCleaner.detach();
+ }
+ else {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't attach SignedData\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ SECItem certP7 = { siBuffer, nsnull, 0 };
+ NSSCMSEncoderContext *ecx = NSS_CMSEncoder_Start(cmsg, nsnull, nsnull, &certP7, arena,
+ nsnull, nsnull, nsnull, nsnull, nsnull,
+ nsnull);
+ if (!ecx) {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - can't create encoder context\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) {
+ PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+ ("nsNSSCertificate::ExportAsCMS - failed to add encoded data\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ *aArray = (PRUint8*)nsMemory::Alloc(certP7.len);
+ if (!*aArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(*aArray, certP7.data, certP7.len);
+ *aLength = certP7.len;
+ return NS_OK;
+}
+
CERTCertificate *
nsNSSCertificate::GetCert()
{
diff --git a/security/manager/ssl/src/nsNSSCleaner.h b/security/manager/ssl/src/nsNSSCleaner.h
index 676387d45c9..1bb6683f0d9 100644
--- a/security/manager/ssl/src/nsNSSCleaner.h
+++ b/security/manager/ssl/src/nsNSSCleaner.h
@@ -101,6 +101,7 @@ public: \
object = nsnull; \
} \
} \
+ void detach() {object=nsnull;} \
};
#define NSSCleanupAutoPtrClass_WithParam(nsstype, cleanfunc, namesuffix, paramvalue) \
@@ -120,6 +121,7 @@ public: \
object = nsnull; \
} \
} \
+ void detach() {object=nsnull;} \
};
#endif