diff options
Diffstat (limited to 'mailnews/extensions/smime/src')
-rw-r--r-- | mailnews/extensions/smime/src/moz.build | 23 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsCertPicker.cpp | 471 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsCertPicker.h | 36 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp | 36 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h | 25 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsMsgComposeSecure.cpp | 1203 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsMsgComposeSecure.h | 106 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsMsgSMIMECID.h | 42 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsSMimeJSHelper.cpp | 335 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/nsSMimeJSHelper.h | 26 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/smime-service.js | 24 | ||||
-rw-r--r-- | mailnews/extensions/smime/src/smime-service.manifest | 3 |
12 files changed, 2330 insertions, 0 deletions
diff --git a/mailnews/extensions/smime/src/moz.build b/mailnews/extensions/smime/src/moz.build new file mode 100644 index 000000000..f3e888dd4 --- /dev/null +++ b/mailnews/extensions/smime/src/moz.build @@ -0,0 +1,23 @@ +# 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/. + +SOURCES += [ + 'nsCertPicker.cpp', + 'nsEncryptedSMIMEURIsService.cpp', + 'nsMsgComposeSecure.cpp', + 'nsSMimeJSHelper.cpp', +] + +EXTRA_COMPONENTS += [ + 'smime-service.js', + 'smime-service.manifest', +] + +FINAL_LIBRARY = 'mail' + +LOCAL_INCLUDES += [ + '/mozilla/security/manager/pki', + '/mozilla/security/pkix/include' +] diff --git a/mailnews/extensions/smime/src/nsCertPicker.cpp b/mailnews/extensions/smime/src/nsCertPicker.cpp new file mode 100644 index 000000000..183f9605c --- /dev/null +++ b/mailnews/extensions/smime/src/nsCertPicker.cpp @@ -0,0 +1,471 @@ +/* -*- 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 "nsCertPicker.h" + +#include "MainThreadUtils.h" +#include "ScopedNSSTypes.h" +#include "cert.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsICertPickDialogs.h" +#include "nsIDOMWindow.h" +#include "nsIDialogParamBlock.h" +#include "nsIInterfaceRequestor.h" +#include "nsIServiceManager.h" +#include "nsIX509CertValidity.h" +#include "nsMemory.h" +#include "nsMsgComposeSecure.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsNSSDialogHelper.h" +#include "nsNSSHelper.h" +#include "nsNSSShutDown.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "pkix/pkixtypes.h" + +using namespace mozilla; + +MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames, + CERTCertNicknames, + CERT_FreeNicknames) + +CERTCertNicknames* +getNSSCertNicknamesFromCertList(const UniqueCERTCertList& certList) +{ + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsresult rv; + + nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv)); + if (NS_FAILED(rv)) + return nullptr; + + nsAutoString expiredString, notYetValidString; + nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace; + + nssComponent->GetPIPNSSBundleString("NicknameExpired", expiredString); + nssComponent->GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString); + + expiredStringLeadingSpace.Append(' '); + expiredStringLeadingSpace.Append(expiredString); + + notYetValidStringLeadingSpace.Append(' '); + notYetValidStringLeadingSpace.Append(notYetValidString); + + NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace); + NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace); + + return CERT_NicknameStringsFromCertList(certList.get(), + const_cast<char*>(aUtf8ExpiredString.get()), + const_cast<char*>(aUtf8NotYetValidString.get())); +} + +nsresult +FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname, + nsAutoString& nickWithSerial, nsAutoString& details) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure; + if (!mcs) { + return NS_ERROR_FAILURE; + } + + nsAutoString info; + nsAutoString temp1; + + nickWithSerial.Append(nickname); + + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) { + details.Append(info); + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) { + details.Append(temp1); + } + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) { + details.Append(info); + details.AppendLiteral(": "); + } + details.Append(temp1); + + nickWithSerial.AppendLiteral(" ["); + nickWithSerial.Append(temp1); + nickWithSerial.Append(char16_t(']')); + + details.Append(char16_t('\n')); + } + + nsCOMPtr<nsIX509CertValidity> validity; + nsresult rv = cert->GetValidity(getter_AddRefs(validity)); + if (NS_SUCCEEDED(rv) && validity) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) { + details.Append(info); + } + + if (NS_SUCCEEDED(validity->GetNotBeforeLocalTime(temp1)) && !temp1.IsEmpty()) { + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) { + details.Append(info); + details.Append(char16_t(' ')); + } + details.Append(temp1); + } + + if (NS_SUCCEEDED(validity->GetNotAfterLocalTime(temp1)) && !temp1.IsEmpty()) { + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) { + details.Append(info); + details.Append(char16_t(' ')); + } + details.Append(temp1); + } + + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(cert->GetKeyUsages(temp1)) && !temp1.IsEmpty()) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpKeyUsage", info))) { + details.Append(info); + details.AppendLiteral(": "); + } + details.Append(temp1); + details.Append(char16_t('\n')); + } + + UniqueCERTCertificate nssCert(cert->GetCert()); + if (!nssCert) { + return NS_ERROR_FAILURE; + } + + nsAutoString firstEmail; + const char* aWalkAddr; + for (aWalkAddr = CERT_GetFirstEmailAddress(nssCert.get()) + ; + aWalkAddr + ; + aWalkAddr = CERT_GetNextEmailAddress(nssCert.get(), aWalkAddr)) + { + NS_ConvertUTF8toUTF16 email(aWalkAddr); + if (email.IsEmpty()) + continue; + + if (firstEmail.IsEmpty()) { + // If the first email address from the subject DN is also present + // in the subjectAltName extension, GetEmailAddresses() will return + // it twice (as received from NSS). Remember the first address so that + // we can filter out duplicates later on. + firstEmail = email; + + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoEmail", info))) { + details.Append(info); + details.AppendLiteral(": "); + } + details.Append(email); + } + else { + // Append current address if it's different from the first one. + if (!firstEmail.Equals(email)) { + details.AppendLiteral(", "); + details.Append(email); + } + } + } + + if (!firstEmail.IsEmpty()) { + // We got at least one email address, so we want a newline + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedBy", info))) { + details.Append(info); + details.Append(char16_t(' ')); + + if (NS_SUCCEEDED(cert->GetIssuerName(temp1)) && !temp1.IsEmpty()) { + details.Append(temp1); + } + + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoStoredIn", info))) { + details.Append(info); + details.Append(char16_t(' ')); + + if (NS_SUCCEEDED(cert->GetTokenName(temp1)) && !temp1.IsEmpty()) { + details.Append(temp1); + } + } + + // the above produces the following output: + // + // Issued to: $subjectName + // Serial number: $serialNumber + // Valid from: $starting_date to $expiration_date + // Certificate Key usage: $usages + // Email: $address(es) + // Issued by: $issuerName + // Stored in: $token + + return rv; +} + +NS_IMPL_ISUPPORTS(nsCertPicker, nsICertPickDialogs, nsIUserCertPicker) + +nsCertPicker::nsCertPicker() +{ +} + +nsCertPicker::~nsCertPicker() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + + shutdown(ShutdownCalledFrom::Object); +} + +nsresult +nsCertPicker::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +NS_IMETHODIMP +nsCertPicker::PickCertificate(nsIInterfaceRequestor *ctx, + const char16_t **certNickList, + const char16_t **certDetailsList, + uint32_t count, + int32_t *selectedIndex, + bool *canceled) +{ + nsresult rv; + uint32_t i; + + *canceled = false; + + nsCOMPtr<nsIDialogParamBlock> block = + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID); + if (!block) return NS_ERROR_FAILURE; + + block->SetNumberStrings(1+count*2); + + for (i = 0; i < count; i++) { + rv = block->SetString(i, certNickList[i]); + if (NS_FAILED(rv)) return rv; + } + + for (i = 0; i < count; i++) { + rv = block->SetString(i+count, certDetailsList[i]); + if (NS_FAILED(rv)) return rv; + } + + rv = block->SetInt(0, count); + if (NS_FAILED(rv)) return rv; + + rv = block->SetInt(1, *selectedIndex); + if (NS_FAILED(rv)) return rv; + + rv = nsNSSDialogHelper::openDialog(nullptr, + "chrome://messenger/content/certpicker.xul", + block); + if (NS_FAILED(rv)) return rv; + + int32_t status; + + rv = block->GetInt(0, &status); + if (NS_FAILED(rv)) return rv; + + *canceled = (status == 0)?true:false; + if (!*canceled) { + rv = block->GetInt(1, selectedIndex); + } + return rv; +} + +NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor *ctx, + const char16_t *selectedNickname, + int32_t certUsage, + bool allowInvalid, + bool allowDuplicateNicknames, + const nsAString &emailAddress, + bool *canceled, + nsIX509Cert **_retval) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + int32_t selectedIndex = -1; + bool selectionFound = false; + char16_t **certNicknameList = nullptr; + char16_t **certDetailsList = nullptr; + CERTCertListNode* node = nullptr; + nsresult rv = NS_OK; + + { + // Iterate over all certs. This assures that user is logged in to all hardware tokens. + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx)); + } + + /* find all user certs that are valid for the specified usage */ + /* note that we are allowing expired certs in this list */ + UniqueCERTCertList certList( + CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), + (SECCertUsage)certUsage, + !allowDuplicateNicknames, + !allowInvalid, + ctx)); + if (!certList) { + return NS_ERROR_NOT_AVAILABLE; + } + + /* if a (non-empty) emailAddress argument is supplied to PickByUsage, */ + /* remove non-matching certificates from the candidate list */ + + if (!emailAddress.IsEmpty()) { + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + /* if the cert has at least one e-mail address, check if suitable */ + if (CERT_GetFirstEmailAddress(node->cert)) { + RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert)); + bool match = false; + rv = tempCert->ContainsEmailAddress(emailAddress, &match); + if (NS_FAILED(rv)) { + return rv; + } + if (!match) { + /* doesn't contain the specified address, so remove from the list */ + CERTCertListNode* freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + continue; + } + } + node = CERT_LIST_NEXT(node); + } + } + + UniqueCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList)); + if (!nicknames) { + return NS_ERROR_NOT_AVAILABLE; + } + + certNicknameList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames); + certDetailsList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames); + + if (!certNicknameList || !certDetailsList) { + free(certNicknameList); + free(certDetailsList); + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t CertsToUse; + + for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get()); + !CERT_LIST_END(node, certList.get()) && + CertsToUse < nicknames->numnicknames; + node = CERT_LIST_NEXT(node) + ) + { + RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert)); + + if (tempCert) { + + nsAutoString i_nickname(NS_ConvertUTF8toUTF16(nicknames->nicknames[CertsToUse])); + nsAutoString nickWithSerial; + nsAutoString details; + + if (!selectionFound) { + /* for the case when selectedNickname refers to a bare nickname */ + if (i_nickname == nsDependentString(selectedNickname)) { + selectedIndex = CertsToUse; + selectionFound = true; + } + } + + if (NS_SUCCEEDED(FormatUIStrings(tempCert, i_nickname, nickWithSerial, + details))) { + certNicknameList[CertsToUse] = ToNewUnicode(nickWithSerial); + certDetailsList[CertsToUse] = ToNewUnicode(details); + if (!selectionFound) { + /* for the case when selectedNickname refers to nickname + serial */ + if (nickWithSerial == nsDependentString(selectedNickname)) { + selectedIndex = CertsToUse; + selectionFound = true; + } + } + } + else { + certNicknameList[CertsToUse] = nullptr; + certDetailsList[CertsToUse] = nullptr; + } + + ++CertsToUse; + } + } + + if (CertsToUse) { + nsCOMPtr<nsICertPickDialogs> dialogs; + rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs), + NS_CERTPICKDIALOGS_CONTRACTID); + + if (NS_SUCCEEDED(rv)) { + // Show the cert picker dialog and get the index of the selected cert. + rv = dialogs->PickCertificate(ctx, (const char16_t**)certNicknameList, + (const char16_t**)certDetailsList, + CertsToUse, &selectedIndex, canceled); + } + } + + int32_t i; + for (i = 0; i < CertsToUse; ++i) { + free(certNicknameList[i]); + free(certDetailsList[i]); + } + free(certNicknameList); + free(certDetailsList); + + if (!CertsToUse) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_SUCCEEDED(rv) && !*canceled) { + for (i = 0, node = CERT_LIST_HEAD(certList); + !CERT_LIST_END(node, certList); + ++i, node = CERT_LIST_NEXT(node)) { + + if (i == selectedIndex) { + RefPtr<nsNSSCertificate> cert = nsNSSCertificate::Create(node->cert); + if (!cert) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + + cert.forget(_retval); + break; + } + } + } + + return rv; +} diff --git a/mailnews/extensions/smime/src/nsCertPicker.h b/mailnews/extensions/smime/src/nsCertPicker.h new file mode 100644 index 000000000..e5881f14f --- /dev/null +++ b/mailnews/extensions/smime/src/nsCertPicker.h @@ -0,0 +1,36 @@ +/* -*- 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 nsCertPicker_h +#define nsCertPicker_h + +#include "nsICertPickDialogs.h" +#include "nsIUserCertPicker.h" +#include "nsNSSShutDown.h" + +#define NS_CERT_PICKER_CID \ + { 0x735959a1, 0xaf01, 0x447e, { 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 } } + +class nsCertPicker : public nsICertPickDialogs + , public nsIUserCertPicker + , public nsNSSShutDownObject +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICERTPICKDIALOGS + NS_DECL_NSIUSERCERTPICKER + + nsCertPicker(); + + // Nothing to actually release. + virtual void virtualDestroyNSSReference() override {} + + nsresult Init(); + +protected: + virtual ~nsCertPicker(); +}; + +#endif // nsCertPicker_h diff --git a/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp new file mode 100644 index 000000000..9276a07a6 --- /dev/null +++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp @@ -0,0 +1,36 @@ +/* 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 "nsEncryptedSMIMEURIsService.h" + +NS_IMPL_ISUPPORTS(nsEncryptedSMIMEURIsService, nsIEncryptedSMIMEURIsService) + +nsEncryptedSMIMEURIsService::nsEncryptedSMIMEURIsService() +{ +} + +nsEncryptedSMIMEURIsService::~nsEncryptedSMIMEURIsService() +{ +} + +NS_IMETHODIMP nsEncryptedSMIMEURIsService::RememberEncrypted(const nsACString & uri) +{ + // Assuming duplicates are allowed. + mEncryptedURIs.AppendElement(uri); + return NS_OK; +} + +NS_IMETHODIMP nsEncryptedSMIMEURIsService::ForgetEncrypted(const nsACString & uri) +{ + // Assuming, this will only remove one copy of the string, if the array + // contains multiple copies of the same string. + mEncryptedURIs.RemoveElement(uri); + return NS_OK; +} + +NS_IMETHODIMP nsEncryptedSMIMEURIsService::IsEncrypted(const nsACString & uri, bool *_retval) +{ + *_retval = mEncryptedURIs.Contains(uri); + return NS_OK; +} diff --git a/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h new file mode 100644 index 000000000..429acbf0a --- /dev/null +++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h @@ -0,0 +1,25 @@ +/* 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 _nsEncryptedSMIMEURIsService_H_ +#define _nsEncryptedSMIMEURIsService_H_ + +#include "nsIEncryptedSMIMEURIsSrvc.h" +#include "nsTArray.h" +#include "nsStringGlue.h" + +class nsEncryptedSMIMEURIsService : public nsIEncryptedSMIMEURIsService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIENCRYPTEDSMIMEURISSERVICE + + nsEncryptedSMIMEURIsService(); + +protected: + virtual ~nsEncryptedSMIMEURIsService(); + nsTArray<nsCString> mEncryptedURIs; +}; + +#endif diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp new file mode 100644 index 000000000..55383c828 --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp @@ -0,0 +1,1203 @@ +/* -*- 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 "nsMsgComposeSecure.h" + +#include <algorithm> + +#include "ScopedNSSTypes.h" +#include "cert.h" +#include "keyhi.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeEncoder.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "msgCore.h" +#include "nsAlgorithm.h" +#include "nsComponentManagerUtils.h" +#include "nsICryptoHash.h" +#include "nsIMimeConverter.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgIdentity.h" +#include "nsIX509CertDB.h" +#include "nsMemory.h" +#include "nsMimeTypes.h" +#include "nsMsgMimeCID.h" +#include "nsNSSComponent.h" +#include "nsServiceManagerUtils.h" +#include "nspr.h" +#include "pkix/Result.h" + +using namespace mozilla::mailnews; +using namespace mozilla; +using namespace mozilla::psm; + +#define MK_MIME_ERROR_WRITING_FILE -1 + +#define SMIME_STRBUNDLE_URL "chrome://messenger/locale/am-smime.properties" + +// It doesn't make sense to encode the message because the message will be +// displayed only if the MUA doesn't support MIME. +// We need to consider what to do in case the server doesn't support 8BITMIME. +// In short, we can't use non-ASCII characters here. +static const char crypto_multipart_blurb[] = "This is a cryptographically signed message in MIME format."; + +static void mime_crypto_write_base64 (void *closure, const char *buf, + unsigned long size); +static nsresult mime_encoder_output_fn(const char *buf, int32_t size, + void *closure); +static nsresult mime_nested_encoder_output_fn(const char *buf, int32_t size, + void *closure); +static nsresult make_multipart_signed_header_string(bool outer_p, + char **header_return, + char **boundary_return, + int16_t hash_type); +static char *mime_make_separator(const char *prefix); + + +static void +GenerateGlobalRandomBytes(unsigned char *buf, int32_t len) +{ + static bool firstTime = true; + + if (firstTime) + { + // Seed the random-number generator with current time so that + // the numbers will be different every time we run. + srand( (unsigned)PR_Now() ); + firstTime = false; + } + + for( int32_t i = 0; i < len; i++ ) + buf[i] = rand() % 10; +} + +char +*mime_make_separator(const char *prefix) +{ + unsigned char rand_buf[13]; + GenerateGlobalRandomBytes(rand_buf, 12); + + return PR_smprintf("------------%s" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X", + prefix, + rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], + rand_buf[4], rand_buf[5], rand_buf[6], rand_buf[7], + rand_buf[8], rand_buf[9], rand_buf[10], rand_buf[11]); +} + +// end of copied code which needs fixed.... + +///////////////////////////////////////////////////////////////////////////////////////// +// Implementation of nsMsgSMIMEComposeFields +///////////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgSMIMEComposeFields, nsIMsgSMIMECompFields) + +nsMsgSMIMEComposeFields::nsMsgSMIMEComposeFields() +:mSignMessage(false), mAlwaysEncryptMessage(false) +{ +} + +nsMsgSMIMEComposeFields::~nsMsgSMIMEComposeFields() +{ +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::SetSignMessage(bool value) +{ + mSignMessage = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::GetSignMessage(bool *_retval) +{ + *_retval = mSignMessage; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::SetRequireEncryptMessage(bool value) +{ + mAlwaysEncryptMessage = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::GetRequireEncryptMessage(bool *_retval) +{ + *_retval = mAlwaysEncryptMessage; + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Implementation of nsMsgComposeSecure +///////////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgComposeSecure, nsIMsgComposeSecure) + +nsMsgComposeSecure::nsMsgComposeSecure() +{ + /* member initializers and constructor code */ + mMultipartSignedBoundary = 0; + mBuffer = 0; + mBufferedBytes = 0; + mHashType = 0; +} + +nsMsgComposeSecure::~nsMsgComposeSecure() +{ + /* destructor code */ + if (mEncryptionContext) { + if (mBufferedBytes) { + mEncryptionContext->Update(mBuffer, mBufferedBytes); + mBufferedBytes = 0; + } + mEncryptionContext->Finish(); + } + + delete [] mBuffer; + + PR_FREEIF(mMultipartSignedBoundary); +} + +NS_IMETHODIMP nsMsgComposeSecure::RequiresCryptoEncapsulation(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aCompFields, bool * aRequiresEncryptionWork) +{ + NS_ENSURE_ARG_POINTER(aRequiresEncryptionWork); + + *aRequiresEncryptionWork = false; + + bool alwaysEncryptMessages = false; + bool signMessage = false; + nsresult rv = ExtractEncryptionState(aIdentity, aCompFields, &signMessage, &alwaysEncryptMessages); + NS_ENSURE_SUCCESS(rv, rv); + + if (alwaysEncryptMessages || signMessage) + *aRequiresEncryptionWork = true; + + return NS_OK; +} + + +nsresult nsMsgComposeSecure::GetSMIMEBundleString(const char16_t *name, + nsString &outString) +{ + outString.Truncate(); + + NS_ENSURE_ARG_POINTER(name); + + NS_ENSURE_TRUE(InitializeSMIMEBundle(), NS_ERROR_FAILURE); + + return mSMIMEBundle->GetStringFromName(name, getter_Copies(outString)); +} + +nsresult +nsMsgComposeSecure:: +SMIMEBundleFormatStringFromName(const char16_t *name, + const char16_t **params, + uint32_t numParams, + char16_t **outString) +{ + NS_ENSURE_ARG_POINTER(name); + + if (!InitializeSMIMEBundle()) + return NS_ERROR_FAILURE; + + return mSMIMEBundle->FormatStringFromName(name, params, + numParams, outString); +} + +bool nsMsgComposeSecure::InitializeSMIMEBundle() +{ + if (mSMIMEBundle) + return true; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + nsresult rv = bundleService->CreateBundle(SMIME_STRBUNDLE_URL, + getter_AddRefs(mSMIMEBundle)); + NS_ENSURE_SUCCESS(rv, false); + + return true; +} + +void nsMsgComposeSecure::SetError(nsIMsgSendReport *sendReport, const char16_t *bundle_string) +{ + if (!sendReport || !bundle_string) + return; + + if (mErrorAlreadyReported) + return; + + mErrorAlreadyReported = true; + + nsString errorString; + nsresult res = GetSMIMEBundleString(bundle_string, errorString); + if (NS_SUCCEEDED(res) && !errorString.IsEmpty()) + { + sendReport->SetMessage(nsIMsgSendReport::process_Current, + errorString.get(), + true); + } +} + +void nsMsgComposeSecure::SetErrorWithParam(nsIMsgSendReport *sendReport, const char16_t *bundle_string, const char *param) +{ + if (!sendReport || !bundle_string || !param) + return; + + if (mErrorAlreadyReported) + return; + + mErrorAlreadyReported = true; + + nsString errorString; + nsresult res; + const char16_t *params[1]; + + NS_ConvertASCIItoUTF16 ucs2(param); + params[0]= ucs2.get(); + + res = SMIMEBundleFormatStringFromName(bundle_string, + params, + 1, + getter_Copies(errorString)); + + if (NS_SUCCEEDED(res) && !errorString.IsEmpty()) + { + sendReport->SetMessage(nsIMsgSendReport::process_Current, + errorString.get(), + true); + } +} + +nsresult nsMsgComposeSecure::ExtractEncryptionState(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aComposeFields, bool * aSignMessage, bool * aEncrypt) +{ + if (!aComposeFields && !aIdentity) + return NS_ERROR_FAILURE; // kick out...invalid args.... + + NS_ENSURE_ARG_POINTER(aSignMessage); + NS_ENSURE_ARG_POINTER(aEncrypt); + + nsCOMPtr<nsISupports> securityInfo; + if (aComposeFields) + aComposeFields->GetSecurityInfo(getter_AddRefs(securityInfo)); + + if (securityInfo) // if we were given security comp fields, use them..... + { + nsCOMPtr<nsIMsgSMIMECompFields> smimeCompFields = do_QueryInterface(securityInfo); + if (smimeCompFields) + { + smimeCompFields->GetSignMessage(aSignMessage); + smimeCompFields->GetRequireEncryptMessage(aEncrypt); + return NS_OK; + } + } + + // get the default info from the identity.... + int32_t ep = 0; + nsresult testrv = aIdentity->GetIntAttribute("encryptionpolicy", &ep); + if (NS_FAILED(testrv)) { + *aEncrypt = false; + } + else { + *aEncrypt = (ep > 0); + } + + testrv = aIdentity->GetBoolAttribute("sign_mail", aSignMessage); + if (NS_FAILED(testrv)) + { + *aSignMessage = false; + } + return NS_OK; +} + +// Select a hash algorithm to sign message +// based on subject public key type and size. +static nsresult +GetSigningHashFunction(nsIX509Cert *aSigningCert, int16_t *hashType) +{ + // Get the signing certificate + CERTCertificate *scert = nullptr; + if (aSigningCert) { + scert = aSigningCert->GetCert(); + } + if (!scert) { + return NS_ERROR_FAILURE; + } + + UniqueSECKEYPublicKey scertPublicKey(CERT_ExtractPublicKey(scert)); + if (!scertPublicKey) { + return mozilla::MapSECStatus(SECFailure); + } + KeyType subjectPublicKeyType = SECKEY_GetPublicKeyType(scertPublicKey.get()); + + // Get the length of the signature in bits. + unsigned siglen = SECKEY_SignatureLen(scertPublicKey.get()) * 8; + if (!siglen) { + return mozilla::MapSECStatus(SECFailure); + } + + // Select a hash function for signature generation whose security strength + // meets or exceeds the security strength of the public key, using NIST + // Special Publication 800-57, Recommendation for Key Management - Part 1: + // General (Revision 3), where Table 2 specifies the security strength of + // the public key and Table 3 lists acceptable hash functions. (The security + // strength of the hash (for digital signatures) is half the length of the + // output.) + // [SP 800-57 is available at http://csrc.nist.gov/publications/PubsSPs.html.] + if (subjectPublicKeyType == rsaKey) { + // For RSA, siglen is the same as the length of the modulus. + + // SHA-1 provides equivalent security strength for up to 1024 bits + // SHA-256 provides equivalent security strength for up to 3072 bits + + if (siglen > 3072) { + *hashType = nsICryptoHash::SHA512; + } else if (siglen > 1024) { + *hashType = nsICryptoHash::SHA256; + } else { + *hashType = nsICryptoHash::SHA1; + } + } else if (subjectPublicKeyType == dsaKey) { + // For DSA, siglen is twice the length of the q parameter of the key. + // The security strength of the key is half the length (in bits) of + // the q parameter of the key. + + // NSS only supports SHA-1, SHA-224, and SHA-256 for DSA signatures. + // The S/MIME code does not support SHA-224. + + if (siglen >= 512) { // 512-bit signature = 256-bit q parameter + *hashType = nsICryptoHash::SHA256; + } else { + *hashType = nsICryptoHash::SHA1; + } + } else if (subjectPublicKeyType == ecKey) { + // For ECDSA, siglen is twice the length of the field size. The security + // strength of the key is half the length (in bits) of the field size. + + if (siglen >= 1024) { // 1024-bit signature = 512-bit field size + *hashType = nsICryptoHash::SHA512; + } else if (siglen >= 768) { // 768-bit signature = 384-bit field size + *hashType = nsICryptoHash::SHA384; + } else if (siglen >= 512) { // 512-bit signature = 256-bit field size + *hashType = nsICryptoHash::SHA256; + } else { + *hashType = nsICryptoHash::SHA1; + } + } else { + // Unknown key type + *hashType = nsICryptoHash::SHA256; + NS_WARNING("GetSigningHashFunction: Subject public key type unknown."); + } + return NS_OK; +} + +/* void beginCryptoEncapsulation (in nsOutputFileStream aStream, in boolean aEncrypt, in boolean aSign, in string aRecipeints, in boolean aIsDraft); */ +NS_IMETHODIMP nsMsgComposeSecure::BeginCryptoEncapsulation(nsIOutputStream * aStream, + const char * aRecipients, + nsIMsgCompFields * aCompFields, + nsIMsgIdentity * aIdentity, + nsIMsgSendReport *sendReport, + bool aIsDraft) +{ + mErrorAlreadyReported = false; + nsresult rv = NS_OK; + + bool encryptMessages = false; + bool signMessage = false; + ExtractEncryptionState(aIdentity, aCompFields, &signMessage, &encryptMessages); + + if (!signMessage && !encryptMessages) return NS_ERROR_FAILURE; + + mStream = aStream; + mIsDraft = aIsDraft; + + if (encryptMessages && signMessage) + mCryptoState = mime_crypto_signed_encrypted; + else if (encryptMessages) + mCryptoState = mime_crypto_encrypted; + else if (signMessage) + mCryptoState = mime_crypto_clear_signed; + else + PR_ASSERT(0); + + aIdentity->GetUnicharAttribute("signing_cert_name", mSigningCertName); + aIdentity->GetCharAttribute("signing_cert_dbkey", mSigningCertDBKey); + aIdentity->GetUnicharAttribute("encryption_cert_name", mEncryptionCertName); + aIdentity->GetCharAttribute("encryption_cert_dbkey", mEncryptionCertDBKey); + + rv = MimeCryptoHackCerts(aRecipients, sendReport, encryptMessages, signMessage, aIdentity); + if (NS_FAILED(rv)) { + goto FAIL; + } + + if (signMessage && mSelfSigningCert) { + rv = GetSigningHashFunction(mSelfSigningCert, &mHashType); + NS_ENSURE_SUCCESS(rv, rv); + } + + switch (mCryptoState) + { + case mime_crypto_clear_signed: + rv = MimeInitMultipartSigned(true, sendReport); + break; + case mime_crypto_opaque_signed: + PR_ASSERT(0); /* #### no api for this yet */ + rv = NS_ERROR_NOT_IMPLEMENTED; + break; + case mime_crypto_signed_encrypted: + rv = MimeInitEncryption(true, sendReport); + break; + case mime_crypto_encrypted: + rv = MimeInitEncryption(false, sendReport); + break; + case mime_crypto_none: + /* This can happen if mime_crypto_hack_certs() decided to turn off + encryption (by asking the user.) */ + // XXX 1 is not a valid nsresult + rv = static_cast<nsresult>(1); + break; + default: + PR_ASSERT(0); + break; + } + +FAIL: + return rv; +} + +/* void finishCryptoEncapsulation (in boolean aAbort); */ +NS_IMETHODIMP nsMsgComposeSecure::FinishCryptoEncapsulation(bool aAbort, nsIMsgSendReport *sendReport) +{ + nsresult rv = NS_OK; + + if (!aAbort) { + switch (mCryptoState) { + case mime_crypto_clear_signed: + rv = MimeFinishMultipartSigned (true, sendReport); + break; + case mime_crypto_opaque_signed: + PR_ASSERT(0); /* #### no api for this yet */ + rv = NS_ERROR_FAILURE; + break; + case mime_crypto_signed_encrypted: + rv = MimeFinishEncryption (true, sendReport); + break; + case mime_crypto_encrypted: + rv = MimeFinishEncryption (false, sendReport); + break; + default: + PR_ASSERT(0); + rv = NS_ERROR_FAILURE; + break; + } + } + return rv; +} + +nsresult nsMsgComposeSecure::MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport *sendReport) +{ + /* First, construct and write out the multipart/signed MIME header data. + */ + nsresult rv = NS_OK; + char *header = 0; + uint32_t L; + + rv = make_multipart_signed_header_string(aOuter, &header, + &mMultipartSignedBoundary, mHashType); + + NS_ENSURE_SUCCESS(rv, rv); + + L = strlen(header); + + if (aOuter){ + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) { + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + + PR_Free(header); + NS_ENSURE_SUCCESS(rv, rv); + + /* Now initialize the crypto library, so that we can compute a hash + on the object which we are signing. + */ + + PR_SetError(0,0); + mDataHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDataHash->Init(mHashType); + NS_ENSURE_SUCCESS(rv, rv); + + PR_SetError(0,0); + return rv; +} + +nsresult nsMsgComposeSecure::MimeInitEncryption(bool aSign, nsIMsgSendReport *sendReport) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> sMIMEBundle; + nsString mime_smime_enc_content_desc; + + bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle)); + + if (!sMIMEBundle) + return NS_ERROR_FAILURE; + + sMIMEBundle->GetStringFromName(u"mime_smimeEncryptedContentDesc", + getter_Copies(mime_smime_enc_content_desc)); + NS_ConvertUTF16toUTF8 enc_content_desc_utf8(mime_smime_enc_content_desc); + + nsCOMPtr<nsIMimeConverter> mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCString encodedContentDescription; + mimeConverter->EncodeMimePartIIStr_UTF8(enc_content_desc_utf8, false, "UTF-8", + sizeof("Content-Description: "), + nsIMimeConverter::MIME_ENCODED_WORD_SIZE, + encodedContentDescription); + + /* First, construct and write out the opaque-crypto-blob MIME header data. + */ + + char *s = + PR_smprintf("Content-Type: " APPLICATION_PKCS7_MIME + "; name=\"smime.p7m\"; smime-type=enveloped-data" CRLF + "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF + "Content-Disposition: attachment" + "; filename=\"smime.p7m\"" CRLF + "Content-Description: %s" CRLF + CRLF, + encodedContentDescription.get()); + + uint32_t L; + if (!s) return NS_ERROR_OUT_OF_MEMORY; + L = strlen(s); + uint32_t n; + rv = mStream->Write(s, L, &n); + if (NS_FAILED(rv) || n < L) { + return NS_ERROR_FAILURE; + } + PR_Free(s); + s = 0; + + /* Now initialize the crypto library, so that we can filter the object + to be encrypted through it. + */ + + if (!mIsDraft) { + uint32_t numCerts; + mCerts->GetLength(&numCerts); + PR_ASSERT(numCerts > 0); + if (numCerts == 0) return NS_ERROR_FAILURE; + } + + // Initialize the base64 encoder + MOZ_ASSERT(!mCryptoEncoder, "Shouldn't have an encoder already"); + mCryptoEncoder = MimeEncoder::GetBase64Encoder(mime_encoder_output_fn, + this); + + /* Initialize the encrypter (and add the sender's cert.) */ + PR_ASSERT(mSelfEncryptionCert); + PR_SetError(0,0); + mEncryptionCinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + rv = mEncryptionCinfo->CreateEncrypted(mCerts); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorEncryptMail"); + goto FAIL; + } + + mEncryptionContext = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + if (!mBuffer) { + mBuffer = new char[eBufferSize]; + if (!mBuffer) + return NS_ERROR_OUT_OF_MEMORY; + } + + mBufferedBytes = 0; + + rv = mEncryptionContext->Start(mEncryptionCinfo, mime_crypto_write_base64, mCryptoEncoder); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorEncryptMail"); + goto FAIL; + } + + /* If we're signing, tack a multipart/signed header onto the front of + the data to be encrypted, and initialize the sign-hashing code too. + */ + if (aSign) { + rv = MimeInitMultipartSigned(false, sendReport); + if (NS_FAILED(rv)) goto FAIL; + } + + FAIL: + return rv; +} + +nsresult nsMsgComposeSecure::MimeFinishMultipartSigned (bool aOuter, nsIMsgSendReport *sendReport) +{ + int status; + nsresult rv; + nsCOMPtr<nsICMSMessage> cinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsICMSEncoder> encoder = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + char * header = nullptr; + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> sMIMEBundle; + nsString mime_smime_sig_content_desc; + + bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle)); + + if (!sMIMEBundle) + return NS_ERROR_FAILURE; + + sMIMEBundle->GetStringFromName(u"mime_smimeSignatureContentDesc", + getter_Copies(mime_smime_sig_content_desc)); + + NS_ConvertUTF16toUTF8 sig_content_desc_utf8(mime_smime_sig_content_desc); + + /* Compute the hash... + */ + + nsAutoCString hashString; + mDataHash->Finish(false, hashString); + + mDataHash = nullptr; + + status = PR_GetError(); + if (status < 0) goto FAIL; + + /* Write out the headers for the signature. + */ + uint32_t L; + header = + PR_smprintf(CRLF + "--%s" CRLF + "Content-Type: " APPLICATION_PKCS7_SIGNATURE + "; name=\"smime.p7s\"" CRLF + "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF + "Content-Disposition: attachment; " + "filename=\"smime.p7s\"" CRLF + "Content-Description: %s" CRLF + CRLF, + mMultipartSignedBoundary, + sig_content_desc_utf8.get()); + + if (!header) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + L = strlen(header); + if (aOuter) { + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) { + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + + PR_Free(header); + + /* Create the signature... + */ + + NS_ASSERTION(mHashType, "Hash function for signature has not been set."); + + PR_ASSERT (mSelfSigningCert); + PR_SetError(0,0); + + rv = cinfo->CreateSigned(mSelfSigningCert, mSelfEncryptionCert, + (unsigned char*)hashString.get(), hashString.Length(), mHashType); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // Initialize the base64 encoder for the signature data. + MOZ_ASSERT(!mSigEncoder, "Shouldn't already have a mSigEncoder"); + mSigEncoder = MimeEncoder::GetBase64Encoder( + (aOuter ? mime_encoder_output_fn : mime_nested_encoder_output_fn), this); + + /* Write out the signature. + */ + PR_SetError(0,0); + rv = encoder->Start(cinfo, mime_crypto_write_base64, mSigEncoder); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // We're not passing in any data, so no update needed. + rv = encoder->Finish(); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // Shut down the sig's base64 encoder. + rv = mSigEncoder->Flush(); + mSigEncoder = nullptr; + if (NS_FAILED(rv)) { + goto FAIL; + } + + /* Now write out the terminating boundary. + */ + { + uint32_t L; + char *header = PR_smprintf(CRLF "--%s--" CRLF, + mMultipartSignedBoundary); + PR_Free(mMultipartSignedBoundary); + mMultipartSignedBoundary = 0; + + if (!header) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + L = strlen(header); + if (aOuter) { + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + } + +FAIL: + return rv; +} + + +/* Helper function for mime_finish_crypto_encapsulation() to close off + an opaque crypto object (for encrypted or signed-and-encrypted messages.) + */ +nsresult nsMsgComposeSecure::MimeFinishEncryption (bool aSign, nsIMsgSendReport *sendReport) +{ + nsresult rv; + + /* If this object is both encrypted and signed, close off the + signature first (since it's inside.) */ + if (aSign) { + rv = MimeFinishMultipartSigned (false, sendReport); + if (NS_FAILED(rv)) { + goto FAIL; + } + } + + /* Close off the opaque encrypted blob. + */ + PR_ASSERT(mEncryptionContext); + + if (mBufferedBytes) { + rv = mEncryptionContext->Update(mBuffer, mBufferedBytes); + mBufferedBytes = 0; + if (NS_FAILED(rv)) { + PR_ASSERT(PR_GetError() < 0); + goto FAIL; + } + } + + rv = mEncryptionContext->Finish(); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorEncryptMail"); + goto FAIL; + } + + mEncryptionContext = nullptr; + + PR_ASSERT(mEncryptionCinfo); + if (!mEncryptionCinfo) { + rv = NS_ERROR_FAILURE; + } + if (mEncryptionCinfo) { + mEncryptionCinfo = nullptr; + } + + // Shut down the base64 encoder. + mCryptoEncoder->Flush(); + mCryptoEncoder = nullptr; + + uint32_t n; + rv = mStream->Write(CRLF, 2, &n); + if (NS_FAILED(rv) || n < 2) + rv = NS_ERROR_FAILURE; + + FAIL: + return rv; +} + +/* Used to figure out what certs should be used when encrypting this message. + */ +nsresult nsMsgComposeSecure::MimeCryptoHackCerts(const char *aRecipients, + nsIMsgSendReport *sendReport, + bool aEncrypt, + bool aSign, + nsIMsgIdentity *aIdentity) +{ + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + nsresult res; + + mCerts = do_CreateInstance(NS_ARRAY_CONTRACTID, &res); + if (NS_FAILED(res)) { + return res; + } + + PR_ASSERT(aEncrypt || aSign); + + /* + Signing and encryption certs use the following (per-identity) preferences: + - "signing_cert_name"/"encryption_cert_name": a string specifying the + nickname of the certificate + - "signing_cert_dbkey"/"encryption_cert_dbkey": a Base64 encoded blob + specifying an nsIX509Cert dbKey (represents serial number + and issuer DN, which is considered to be unique for X.509 certificates) + + When retrieving the prefs, we try (in this order): + 1) *_cert_dbkey, if available + 2) *_cert_name (for maintaining backwards compatibility with preference + attributes written by earlier versions) + */ + + RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier()); + NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); + + UniqueCERTCertList builtChain; + if (!mEncryptionCertDBKey.IsEmpty()) { + certdb->FindCertByDBKey(mEncryptionCertDBKey.get(), + getter_AddRefs(mSelfEncryptionCert)); + if (mSelfEncryptionCert && + (certVerifier->VerifyCert(mSelfEncryptionCert->GetCert(), + certificateUsageEmailRecipient, + mozilla::pkix::Now(), + nullptr, nullptr, + builtChain) != mozilla::pkix::Success)) { + // not suitable for encryption, so unset cert and clear pref + mSelfEncryptionCert = nullptr; + mEncryptionCertDBKey.Truncate(); + aIdentity->SetCharAttribute("encryption_cert_dbkey", + mEncryptionCertDBKey); + } + } + if (!mSelfEncryptionCert) { + certdb->FindEmailEncryptionCert(mEncryptionCertName, + getter_AddRefs(mSelfEncryptionCert)); + } + + // same procedure for the signing cert + if (!mSigningCertDBKey.IsEmpty()) { + certdb->FindCertByDBKey(mSigningCertDBKey.get(), + getter_AddRefs(mSelfSigningCert)); + if (mSelfSigningCert && + (certVerifier->VerifyCert(mSelfSigningCert->GetCert(), + certificateUsageEmailSigner, + mozilla::pkix::Now(), + nullptr, nullptr, + builtChain) != mozilla::pkix::Success)) { + // not suitable for signing, so unset cert and clear pref + mSelfSigningCert = nullptr; + mSigningCertDBKey.Truncate(); + aIdentity->SetCharAttribute("signing_cert_dbkey", mSigningCertDBKey); + } + } + if (!mSelfSigningCert) { + certdb->FindEmailSigningCert(mSigningCertName, + getter_AddRefs(mSelfSigningCert)); + } + + // must have both the signing and encryption certs to sign + if (!mSelfSigningCert && aSign) { + SetError(sendReport, u"NoSenderSigningCert"); + return NS_ERROR_FAILURE; + } + + if (!mSelfEncryptionCert && aEncrypt) { + SetError(sendReport, u"NoSenderEncryptionCert"); + return NS_ERROR_FAILURE; + } + + + if (aEncrypt && mSelfEncryptionCert) { + // Make sure self's configured cert is prepared for being used + // as an email recipient cert. + UniqueCERTCertificate nsscert(mSelfEncryptionCert->GetCert()); + if (!nsscert) { + return NS_ERROR_FAILURE; + } + // XXX: This does not respect the nsNSSShutDownObject protocol. + if (CERT_SaveSMimeProfile(nsscert.get(), nullptr, nullptr) != SECSuccess) { + return NS_ERROR_FAILURE; + } + } + + /* If the message is to be encrypted, then get the recipient certs */ + if (aEncrypt) { + nsTArray<nsCString> mailboxes; + ExtractEmails(EncodedHeader(nsDependentCString(aRecipients)), + UTF16ArrayAdapter<>(mailboxes)); + uint32_t count = mailboxes.Length(); + + bool already_added_self_cert = false; + + for (uint32_t i = 0; i < count; i++) { + nsCString mailbox_lowercase; + ToLowerCase(mailboxes[i], mailbox_lowercase); + nsCOMPtr<nsIX509Cert> cert; + res = certdb->FindCertByEmailAddress(mailbox_lowercase.get(), + getter_AddRefs(cert)); + if (NS_FAILED(res)) { + // Failure to find a valid encryption cert is fatal. + // Here I assume that mailbox is ascii rather than utf8. + SetErrorWithParam(sendReport, + u"MissingRecipientEncryptionCert", + mailboxes[i].get()); + + return res; + } + + /* #### see if recipient requests `signedData'. + if (...) no_clearsigning_p = true; + (This is the only reason we even bother looking up the certs + of the recipients if we're sending a signed-but-not-encrypted + message.) + */ + + bool isSame; + if (NS_SUCCEEDED(cert->Equals(mSelfEncryptionCert, &isSame)) + && isSame) { + already_added_self_cert = true; + } + + mCerts->AppendElement(cert, false); + } + + if (!already_added_self_cert) { + mCerts->AppendElement(mSelfEncryptionCert, false); + } + } + return res; +} + +NS_IMETHODIMP nsMsgComposeSecure::MimeCryptoWriteBlock (const char *buf, int32_t size) +{ + int status = 0; + nsresult rv; + + /* If this is a From line, mangle it before signing it. You just know + that something somewhere is going to mangle it later, and that's + going to cause the signature check to fail. + + (This assumes that, in the cases where From-mangling must happen, + this function is called a line at a time. That happens to be the + case.) + */ + if (size >= 5 && buf[0] == 'F' && !strncmp(buf, "From ", 5)) { + char mangle[] = ">"; + nsresult res = MimeCryptoWriteBlock (mangle, 1); + if (NS_FAILED(res)) + return res; + // This value will actually be cast back to an nsresult before use, so this + // cast is reasonable under the circumstances. + status = static_cast<int>(res); + } + + /* If we're signing, or signing-and-encrypting, feed this data into + the computation of the hash. */ + if (mDataHash) { + PR_SetError(0,0); + mDataHash->Update((const uint8_t*) buf, size); + status = PR_GetError(); + if (status < 0) goto FAIL; + } + + PR_SetError(0,0); + if (mEncryptionContext) { + /* If we're encrypting, or signing-and-encrypting, write this data + by filtering it through the crypto library. */ + + /* We want to create equally sized encryption strings */ + const char *inputBytesIterator = buf; + uint32_t inputBytesLeft = size; + + while (inputBytesLeft) { + const uint32_t spaceLeftInBuffer = eBufferSize - mBufferedBytes; + const uint32_t bytesToAppend = std::min(inputBytesLeft, spaceLeftInBuffer); + + memcpy(mBuffer+mBufferedBytes, inputBytesIterator, bytesToAppend); + mBufferedBytes += bytesToAppend; + + inputBytesIterator += bytesToAppend; + inputBytesLeft -= bytesToAppend; + + if (eBufferSize == mBufferedBytes) { + rv = mEncryptionContext->Update(mBuffer, mBufferedBytes); + mBufferedBytes = 0; + if (NS_FAILED(rv)) { + status = PR_GetError(); + PR_ASSERT(status < 0); + if (status >= 0) status = -1; + goto FAIL; + } + } + } + } else { + /* If we're not encrypting (presumably just signing) then write this + data directly to the file. */ + + uint32_t n; + rv = mStream->Write(buf, size, &n); + if (NS_FAILED(rv) || n < (uint32_t)size) { + // XXX MK_MIME_ERROR_WRITING_FILE is -1, which is not a valid nsresult + return static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } + FAIL: + // XXX status sometimes has invalid nsresults like -1 or PR_GetError() + // assigned to it + return static_cast<nsresult>(status); +} + +/* Returns a string consisting of a Content-Type header, and a boundary + string, suitable for moving from the header block, down into the body + of a multipart object. The boundary itself is also returned (so that + the caller knows what to write to close it off.) + */ +static nsresult +make_multipart_signed_header_string(bool outer_p, + char **header_return, + char **boundary_return, + int16_t hash_type) +{ + const char *hashStr; + *header_return = 0; + *boundary_return = mime_make_separator("ms"); + + if (!*boundary_return) + return NS_ERROR_OUT_OF_MEMORY; + + switch (hash_type) { + case nsICryptoHash::SHA1: + hashStr = PARAM_MICALG_SHA1; + break; + case nsICryptoHash::SHA256: + hashStr = PARAM_MICALG_SHA256; + break; + case nsICryptoHash::SHA384: + hashStr = PARAM_MICALG_SHA384; + break; + case nsICryptoHash::SHA512: + hashStr = PARAM_MICALG_SHA512; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + *header_return = PR_smprintf( + "Content-Type: " MULTIPART_SIGNED "; " + "protocol=\"" APPLICATION_PKCS7_SIGNATURE "\"; " + "micalg=%s; " + "boundary=\"%s\"" CRLF + CRLF + "%s%s" + "--%s" CRLF, + hashStr, + *boundary_return, + (outer_p ? crypto_multipart_blurb : ""), + (outer_p ? CRLF CRLF : ""), + *boundary_return); + + if (!*header_return) { + PR_Free(*boundary_return); + *boundary_return = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/* Used as the output function of a SEC_PKCS7EncoderContext -- we feed + plaintext into the crypto engine, and it calls this function with encrypted + data; then this function writes a base64-encoded representation of that + data to the file (by filtering it through the given MimeEncoder object.) + + Also used as the output function of SEC_PKCS7Encode() -- but in that case, + it's used to write the encoded representation of the signature. The only + difference is which MimeEncoder object is used. + */ +static void +mime_crypto_write_base64 (void *closure, const char *buf, unsigned long size) +{ + MimeEncoder *encoder = (MimeEncoder *) closure; + nsresult rv = encoder->Write(buf, size); + PR_SetError(NS_FAILED(rv) ? static_cast<uint32_t>(rv) : 0, 0); +} + + +/* Used as the output function of MimeEncoder -- when we have generated + the signature for a multipart/signed object, this is used to write the + base64-encoded representation of the signature to the file. + */ +// TODO: size should probably be converted to uint32_t +nsresult mime_encoder_output_fn(const char *buf, int32_t size, void *closure) +{ + nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure; + nsCOMPtr<nsIOutputStream> stream; + state->GetOutputStream(getter_AddRefs(stream)); + uint32_t n; + nsresult rv = stream->Write((char *) buf, size, &n); + if (NS_FAILED(rv) || n < (uint32_t)size) + return NS_ERROR_FAILURE; + else + return NS_OK; +} + +/* Like mime_encoder_output_fn, except this is used for the case where we + are both signing and encrypting -- the base64-encoded output of the + signature should be fed into the crypto engine, rather than being written + directly to the file. + */ +static nsresult +mime_nested_encoder_output_fn (const char *buf, int32_t size, void *closure) +{ + nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure; + + // Copy to new null-terminated string so JS glue doesn't crash when + // MimeCryptoWriteBlock() is implemented in JS. + nsCString bufWithNull; + bufWithNull.Assign(buf, size); + return state->MimeCryptoWriteBlock(bufWithNull.get(), size); +} diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.h b/mailnews/extensions/smime/src/nsMsgComposeSecure.h new file mode 100644 index 000000000..0f3b9ac60 --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.h @@ -0,0 +1,106 @@ +/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 _nsMsgComposeSecure_H_ +#define _nsMsgComposeSecure_H_ + +#include "nsIMsgComposeSecure.h" +#include "nsIMsgSMIMECompFields.h" +#include "nsCOMPtr.h" +#include "nsICMSEncoder.h" +#include "nsIX509Cert.h" +#include "nsIStringBundle.h" +#include "nsICryptoHash.h" +#include "nsICMSMessage.h" +#include "nsIMutableArray.h" +#include "nsStringGlue.h" +#include "nsIOutputStream.h" +#include "nsAutoPtr.h" + +class nsIMsgCompFields; +namespace mozilla { +namespace mailnews { +class MimeEncoder; +} +} + +class nsMsgSMIMEComposeFields : public nsIMsgSMIMECompFields +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSMIMECOMPFIELDS + + nsMsgSMIMEComposeFields(); + +private: + virtual ~nsMsgSMIMEComposeFields(); + bool mSignMessage; + bool mAlwaysEncryptMessage; +}; + +typedef enum { + mime_crypto_none, /* normal unencapsulated MIME message */ + mime_crypto_clear_signed, /* multipart/signed encapsulation */ + mime_crypto_opaque_signed, /* application/x-pkcs7-mime (signedData) */ + mime_crypto_encrypted, /* application/x-pkcs7-mime */ + mime_crypto_signed_encrypted /* application/x-pkcs7-mime */ +} mimeDeliveryCryptoState; + +class nsMsgComposeSecure : public nsIMsgComposeSecure +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSESECURE + + nsMsgComposeSecure(); + + void GetOutputStream(nsIOutputStream **stream) { NS_IF_ADDREF(*stream = mStream);} + nsresult GetSMIMEBundleString(const char16_t *name, nsString &outString); + +private: + virtual ~nsMsgComposeSecure(); + typedef mozilla::mailnews::MimeEncoder MimeEncoder; + nsresult MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport *sendReport); + nsresult MimeInitEncryption(bool aSign, nsIMsgSendReport *sendReport); + nsresult MimeFinishMultipartSigned (bool aOuter, nsIMsgSendReport *sendReport); + nsresult MimeFinishEncryption (bool aSign, nsIMsgSendReport *sendReport); + nsresult MimeCryptoHackCerts(const char *aRecipients, nsIMsgSendReport *sendReport, bool aEncrypt, bool aSign, nsIMsgIdentity *aIdentity); + bool InitializeSMIMEBundle(); + nsresult SMIMEBundleFormatStringFromName(const char16_t *name, + const char16_t **params, + uint32_t numParams, + char16_t **outString); + nsresult ExtractEncryptionState(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aComposeFields, bool * aSignMessage, bool * aEncrypt); + + mimeDeliveryCryptoState mCryptoState; + nsCOMPtr<nsIOutputStream> mStream; + int16_t mHashType; + nsCOMPtr<nsICryptoHash> mDataHash; + nsAutoPtr<MimeEncoder> mSigEncoder; + char *mMultipartSignedBoundary; + nsString mSigningCertName; + nsAutoCString mSigningCertDBKey; + nsCOMPtr<nsIX509Cert> mSelfSigningCert; + nsString mEncryptionCertName; + nsAutoCString mEncryptionCertDBKey; + nsCOMPtr<nsIX509Cert> mSelfEncryptionCert; + nsCOMPtr<nsIMutableArray> mCerts; + nsCOMPtr<nsICMSMessage> mEncryptionCinfo; + nsCOMPtr<nsICMSEncoder> mEncryptionContext; + nsCOMPtr<nsIStringBundle> mSMIMEBundle; + + nsAutoPtr<MimeEncoder> mCryptoEncoder; + bool mIsDraft; + + enum {eBufferSize = 8192}; + char *mBuffer; + uint32_t mBufferedBytes; + + bool mErrorAlreadyReported; + void SetError(nsIMsgSendReport *sendReport, const char16_t *bundle_string); + void SetErrorWithParam(nsIMsgSendReport *sendReport, const char16_t *bundle_string, const char *param); +}; + +#endif diff --git a/mailnews/extensions/smime/src/nsMsgSMIMECID.h b/mailnews/extensions/smime/src/nsMsgSMIMECID.h new file mode 100644 index 000000000..0fbf2d1bf --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgSMIMECID.h @@ -0,0 +1,42 @@ +/* -*- 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 nsMsgSMIMECID_h__ +#define nsMsgSMIMECID_h__ + +#include "nsISupports.h" +#include "nsIFactory.h" +#include "nsIComponentManager.h" + +#define NS_MSGSMIMECOMPFIELDS_CONTRACTID \ + "@mozilla.org/messenger-smime/composefields;1" + +#define NS_MSGSMIMECOMPFIELDS_CID \ +{ /* 122C919C-96B7-49a0-BBC8-0ABC67EEFFE0 */ \ + 0x122c919c, 0x96b7, 0x49a0, \ + { 0xbb, 0xc8, 0xa, 0xbc, 0x67, 0xee, 0xff, 0xe0 }} + +#define NS_MSGCOMPOSESECURE_CID \ +{ /* dd753201-9a23-4e08-957f-b3616bf7e012 */ \ + 0xdd753201, 0x9a23, 0x4e08, \ + {0x95, 0x7f, 0xb3, 0x61, 0x6b, 0xf7, 0xe0, 0x12 }} + +#define NS_SMIMEJSHELPER_CONTRACTID \ + "@mozilla.org/messenger-smime/smimejshelper;1" + +#define NS_SMIMEJSJELPER_CID \ +{ /* d57d928c-60e4-4f81-999d-5c762e611205 */ \ + 0xd57d928c, 0x60e4, 0x4f81, \ + {0x99, 0x9d, 0x5c, 0x76, 0x2e, 0x61, 0x12, 0x05 }} + +#define NS_SMIMEENCRYPTURISERVICE_CONTRACTID \ + "@mozilla.org/messenger-smime/smime-encrypted-uris-service;1" + +#define NS_SMIMEENCRYPTURISERVICE_CID \ +{ /* a0134d58-018f-4d40-a099-fa079e5024a6 */ \ + 0xa0134d58, 0x018f, 0x4d40, \ + {0xa0, 0x99, 0xfa, 0x07, 0x9e, 0x50, 0x24, 0xa6 }} + +#endif // nsMsgSMIMECID_h__ diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp new file mode 100644 index 000000000..c392980b6 --- /dev/null +++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp @@ -0,0 +1,335 @@ +/* -*- 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/mailnews/MimeHeaderParser.h" +#include "nspr.h" +#include "nsSMimeJSHelper.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsStringGlue.h" +#include "nsIX509CertDB.h" +#include "nsIX509CertValidity.h" +#include "nsIServiceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsCRTGlue.h" + +using namespace mozilla::mailnews; + +NS_IMPL_ISUPPORTS(nsSMimeJSHelper, nsISMimeJSHelper) + +nsSMimeJSHelper::nsSMimeJSHelper() +{ +} + +nsSMimeJSHelper::~nsSMimeJSHelper() +{ +} + +NS_IMETHODIMP nsSMimeJSHelper::GetRecipientCertsInfo( + nsIMsgCompFields *compFields, + uint32_t *count, + char16_t ***emailAddresses, + int32_t **certVerification, + char16_t ***certIssuedInfos, + char16_t ***certExpiresInfos, + nsIX509Cert ***certs, + bool *canEncrypt) +{ + NS_ENSURE_ARG_POINTER(count); + *count = 0; + + NS_ENSURE_ARG_POINTER(emailAddresses); + NS_ENSURE_ARG_POINTER(certVerification); + NS_ENSURE_ARG_POINTER(certIssuedInfos); + NS_ENSURE_ARG_POINTER(certExpiresInfos); + NS_ENSURE_ARG_POINTER(certs); + NS_ENSURE_ARG_POINTER(canEncrypt); + + NS_ENSURE_ARG_POINTER(compFields); + + nsTArray<nsCString> mailboxes; + nsresult rv = getMailboxList(compFields, mailboxes); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t mailbox_count = mailboxes.Length(); + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + + *count = mailbox_count; + *canEncrypt = false; + rv = NS_OK; + + if (mailbox_count) + { + char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + int32_t *outCV = static_cast<int32_t *>(moz_xmalloc(mailbox_count * sizeof(int32_t))); + char16_t **outCII = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + char16_t **outCEI = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + nsIX509Cert **outCerts = static_cast<nsIX509Cert **>(moz_xmalloc(mailbox_count * sizeof(nsIX509Cert *))); + + if (!outEA || !outCV || !outCII || !outCEI || !outCerts) + { + free(outEA); + free(outCV); + free(outCII); + free(outCEI); + free(outCerts); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + { + char16_t **iEA = outEA; + int32_t *iCV = outCV; + char16_t **iCII = outCII; + char16_t **iCEI = outCEI; + nsIX509Cert **iCert = outCerts; + + bool found_blocker = false; + bool memory_failure = false; + + for (uint32_t i = 0; + i < mailbox_count; + ++i, ++iEA, ++iCV, ++iCII, ++iCEI, ++iCert) + { + *iCert = nullptr; + *iCV = 0; + *iCII = nullptr; + *iCEI = nullptr; + + if (memory_failure) { + *iEA = nullptr; + continue; + } + + nsCString &email = mailboxes[i]; + *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(email)); + if (!*iEA) { + memory_failure = true; + continue; + } + + nsCString email_lowercase; + ToLowerCase(email, email_lowercase); + + nsCOMPtr<nsIX509Cert> cert; + if (NS_SUCCEEDED(certdb->FindCertByEmailAddress( + email_lowercase.get(), getter_AddRefs(cert)))) + { + *iCert = cert; + NS_ADDREF(*iCert); + + nsCOMPtr<nsIX509CertValidity> validity; + rv = cert->GetValidity(getter_AddRefs(validity)); + + if (NS_SUCCEEDED(rv)) { + nsString id, ed; + + if (NS_SUCCEEDED(validity->GetNotBeforeLocalDay(id))) + { + *iCII = ToNewUnicode(id); + if (!*iCII) { + memory_failure = true; + continue; + } + } + + if (NS_SUCCEEDED(validity->GetNotAfterLocalDay(ed))) + { + *iCEI = ToNewUnicode(ed); + if (!*iCEI) { + memory_failure = true; + continue; + } + } + } + } + else + { + found_blocker = true; + } + } + + if (memory_failure) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outEA); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCII); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCEI); + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(mailbox_count, outCerts); + free(outCV); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + if (mailbox_count > 0 && !found_blocker) + { + *canEncrypt = true; + } + + *emailAddresses = outEA; + *certVerification = outCV; + *certIssuedInfos = outCII; + *certExpiresInfos = outCEI; + *certs = outCerts; + } + } + } + return rv; +} + +NS_IMETHODIMP nsSMimeJSHelper::GetNoCertAddresses( + nsIMsgCompFields *compFields, + uint32_t *count, + char16_t ***emailAddresses) +{ + NS_ENSURE_ARG_POINTER(count); + *count = 0; + + NS_ENSURE_ARG_POINTER(emailAddresses); + + NS_ENSURE_ARG_POINTER(compFields); + + nsTArray<nsCString> mailboxes; + nsresult rv = getMailboxList(compFields, mailboxes); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t mailbox_count = mailboxes.Length(); + + if (!mailbox_count) + { + *count = 0; + *emailAddresses = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + + uint32_t missing_count = 0; + bool *haveCert = new bool[mailbox_count]; + if (!haveCert) + { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = NS_OK; + + if (mailbox_count) + { + for (uint32_t i = 0; i < mailbox_count; ++i) + { + haveCert[i] = false; + + nsCString email_lowercase; + ToLowerCase(mailboxes[i], email_lowercase); + + nsCOMPtr<nsIX509Cert> cert; + if (NS_SUCCEEDED(certdb->FindCertByEmailAddress( + email_lowercase.get(), getter_AddRefs(cert)))) + haveCert[i] = true; + + if (!haveCert[i]) + ++missing_count; + } + } + + *count = missing_count; + + if (missing_count) + { + char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(missing_count * sizeof(char16_t *))); + if (!outEA ) + { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + { + char16_t **iEA = outEA; + + bool memory_failure = false; + + for (uint32_t i = 0; i < mailbox_count; ++i) + { + if (!haveCert[i]) + { + if (memory_failure) { + *iEA = nullptr; + } + else { + *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(mailboxes[i])); + if (!*iEA) { + memory_failure = true; + } + } + ++iEA; + } + } + + if (memory_failure) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(missing_count, outEA); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + *emailAddresses = outEA; + } + } + } + else + { + *emailAddresses = nullptr; + } + + delete [] haveCert; + return rv; +} + +nsresult nsSMimeJSHelper::getMailboxList(nsIMsgCompFields *compFields, + nsTArray<nsCString> &mailboxes) +{ + if (!compFields) + return NS_ERROR_INVALID_ARG; + + nsresult res; + nsString to, cc, bcc, ng; + + res = compFields->GetTo(to); + if (NS_FAILED(res)) + return res; + + res = compFields->GetCc(cc); + if (NS_FAILED(res)) + return res; + + res = compFields->GetBcc(bcc); + if (NS_FAILED(res)) + return res; + + res = compFields->GetNewsgroups(ng); + if (NS_FAILED(res)) + return res; + + { + nsCString all_recipients; + + if (!to.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(to)); + all_recipients.Append(','); + } + + if (!cc.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(cc)); + all_recipients.Append(','); + } + + if (!bcc.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(bcc)); + all_recipients.Append(','); + } + + if (!ng.IsEmpty()) + all_recipients.Append(NS_ConvertUTF16toUTF8(ng)); + + ExtractEmails(EncodedHeader(all_recipients), + UTF16ArrayAdapter<>(mailboxes)); + } + + return NS_OK; +} diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.h b/mailnews/extensions/smime/src/nsSMimeJSHelper.h new file mode 100644 index 000000000..403ab2098 --- /dev/null +++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.h @@ -0,0 +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/. */ + +#ifndef _nsSMimeJSHelper_H_ +#define _nsSMimeJSHelper_H_ + +#include "nsISMimeJSHelper.h" +#include "nsIX509Cert.h" +#include "nsIMsgCompFields.h" + +class nsSMimeJSHelper : public nsISMimeJSHelper +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISMIMEJSHELPER + + nsSMimeJSHelper(); + +private: + virtual ~nsSMimeJSHelper(); + nsresult getMailboxList(nsIMsgCompFields *compFields, + nsTArray<nsCString> &mailboxes); +}; + +#endif diff --git a/mailnews/extensions/smime/src/smime-service.js b/mailnews/extensions/smime/src/smime-service.js new file mode 100644 index 000000000..9fa618fd6 --- /dev/null +++ b/mailnews/extensions/smime/src/smime-service.js @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function SMIMEService() {} + +SMIMEService.prototype = { + name: "smime", + chromePackageName: "messenger", + showPanel: function(server) { + // don't show the panel for news, rss, or local accounts + return (server.type != "nntp" && server.type != "rss" && + server.type != "im" && server.type != "none"); + }, + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]), + classID: Components.ID("{f2809796-1dd1-11b2-8c1b-8f15f007c699}"), +}; + +var components = [SMIMEService]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/extensions/smime/src/smime-service.manifest b/mailnews/extensions/smime/src/smime-service.manifest new file mode 100644 index 000000000..1b799ed7b --- /dev/null +++ b/mailnews/extensions/smime/src/smime-service.manifest @@ -0,0 +1,3 @@ +component {f2809796-1dd1-11b2-8c1b-8f15f007c699} smime-service.js +contract @mozilla.org/accountmanager/extension;1?name=smime {f2809796-1dd1-11b2-8c1b-8f15f007c699} +category mailnews-accountmanager-extensions smime-account-manager-extension @mozilla.org/accountmanager/extension;1?name=smime |