diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/extensions/smime/nsCMS.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/extensions/smime/nsCMS.cpp')
-rw-r--r-- | comm/mailnews/extensions/smime/nsCMS.cpp | 1187 |
1 files changed, 1187 insertions, 0 deletions
diff --git a/comm/mailnews/extensions/smime/nsCMS.cpp b/comm/mailnews/extensions/smime/nsCMS.cpp new file mode 100644 index 0000000000..be743b9663 --- /dev/null +++ b/comm/mailnews/extensions/smime/nsCMS.cpp @@ -0,0 +1,1187 @@ +/* -*- 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 "nsCMS.h" + +#include "CertVerifier.h" +#include "CryptoTask.h" +#include "ScopedNSSTypes.h" +#include "cms.h" +#include "mozilla/Logging.h" +#include "mozilla/RefPtr.h" +#include "nsDependentSubstring.h" +#include "nsICryptoHash.h" +#include "nsISupports.h" +#include "nsIX509CertDB.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsNSSHelper.h" +#include "nsServiceManagerUtils.h" +#include "mozpkix/Result.h" +#include "mozpkix/pkixtypes.h" +#include "sechash.h" +#include "secerr.h" +#include "smime.h" +#include "mozilla/StaticMutex.h" +#include "nsIPrefBranch.h" + +using namespace mozilla; +using namespace mozilla::psm; +using namespace mozilla::pkix; + +static mozilla::LazyLogModule gCMSLog("CMS"); + +NS_IMPL_ISUPPORTS(nsCMSMessage, nsICMSMessage) + +nsCMSMessage::nsCMSMessage() { m_cmsMsg = nullptr; } +nsCMSMessage::nsCMSMessage(NSSCMSMessage* aCMSMsg) { m_cmsMsg = aCMSMsg; } + +nsCMSMessage::~nsCMSMessage() { + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + } +} + +nsresult nsCMSMessage::Init() { + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = + do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +NS_IMETHODIMP nsCMSMessage::VerifySignature(int32_t verifyFlags) { + return CommonVerifySignature(verifyFlags, {}, 0); +} + +NSSCMSSignerInfo* nsCMSMessage::GetTopLevelSignerInfo() { + if (!m_cmsMsg) return nullptr; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) return nullptr; + + NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (!cinfo) return nullptr; + + NSSCMSSignedData* sigd = + (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo); + if (!sigd) return nullptr; + + PR_ASSERT(NSS_CMSSignedData_SignerInfoCount(sigd) > 0); + return NSS_CMSSignedData_GetSignerInfo(sigd, 0); +} + +NS_IMETHODIMP nsCMSMessage::GetSignerEmailAddress(char** aEmail) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerEmailAddress")); + NS_ENSURE_ARG(aEmail); + + NSSCMSSignerInfo* si = GetTopLevelSignerInfo(); + if (!si) return NS_ERROR_FAILURE; + + *aEmail = NSS_CMSSignerInfo_GetSignerEmailAddress(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCommonName(char** aName) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCommonName")); + NS_ENSURE_ARG(aName); + + NSSCMSSignerInfo* si = GetTopLevelSignerInfo(); + if (!si) return NS_ERROR_FAILURE; + + *aName = NSS_CMSSignerInfo_GetSignerCommonName(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsEncrypted(bool* isEncrypted) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsEncrypted")); + NS_ENSURE_ARG(isEncrypted); + + if (!m_cmsMsg) return NS_ERROR_FAILURE; + + *isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsSigned(bool* isSigned) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsSigned")); + NS_ENSURE_ARG(isSigned); + + if (!m_cmsMsg) return NS_ERROR_FAILURE; + + *isSigned = NSS_CMSMessage_IsSigned(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCert(nsIX509Cert** scert) { + NSSCMSSignerInfo* si = GetTopLevelSignerInfo(); + if (!si) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIX509Cert> cert; + if (si->cert) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::GetSignerCert got signer cert")); + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + nsTArray<uint8_t> certBytes; + certBytes.AppendElements(si->cert->derCert.data, si->cert->derCert.len); + nsresult rv = certdb->ConstructX509(certBytes, getter_AddRefs(cert)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::GetSignerCert no signer cert, do we have a cert " + "list? %s", + (si->certList ? "yes" : "no"))); + + *scert = nullptr; + } + + cert.forget(scert); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetEncryptionCert(nsIX509Cert**) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCMSMessage::GetSigningTime(PRTime* aTime) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSigningTime")); + NS_ENSURE_ARG(aTime); + + NSSCMSSignerInfo* si = GetTopLevelSignerInfo(); + if (!si) { + return NS_ERROR_FAILURE; + } + + SECStatus getSigningTimeResult = NSS_CMSSignerInfo_GetSigningTime(si, aTime); + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::GetSigningTime result: %s", + (getSigningTimeResult ? "ok" : "fail"))); + + return getSigningTimeResult == SECSuccess ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCMSMessage::VerifyDetachedSignature(int32_t verifyFlags, + const nsTArray<uint8_t>& aDigestData, + int16_t aDigestType) { + if (aDigestData.IsEmpty()) return NS_ERROR_FAILURE; + + return CommonVerifySignature(verifyFlags, aDigestData, aDigestType); +} + +// This is an exact copy of NSS_CMSArray_Count from NSS' cmsarray.c, +// temporarily necessary, see below for for justification. +static int myNSS_CMSArray_Count(void** array) { + int n = 0; + + if (array == NULL) return 0; + + while (*array++ != NULL) n++; + + return n; +} + +// This is an exact copy of NSS_CMSArray_Add from NSS' cmsarray.c, +// temporarily necessary, see below for for justification. +static SECStatus myNSS_CMSArray_Add(PLArenaPool* poolp, void*** array, + void* obj) { + void** p; + int n; + void** dest; + + PORT_Assert(array != NULL); + if (array == NULL) return SECFailure; + + if (*array == NULL) { + dest = (void**)PORT_ArenaAlloc(poolp, 2 * sizeof(void*)); + n = 0; + } else { + n = 0; + p = *array; + while (*p++) n++; + dest = (void**)PORT_ArenaGrow(poolp, *array, (n + 1) * sizeof(void*), + (n + 2) * sizeof(void*)); + } + + if (dest == NULL) return SECFailure; + + dest[n] = obj; + dest[n + 1] = NULL; + *array = dest; + return SECSuccess; +} + +// This is an exact copy of NSS_CMSArray_Add from NSS' cmsarray.c, +// temporarily necessary, see below for for justification. +static SECStatus myNSS_CMSSignedData_AddTempCertificate(NSSCMSSignedData* sigd, + CERTCertificate* cert) { + CERTCertificate* c; + SECStatus rv; + + if (!sigd || !cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + c = CERT_DupCertificate(cert); + rv = myNSS_CMSArray_Add(sigd->cmsg->poolp, (void***)&(sigd->tempCerts), + (void*)c); + return rv; +} + +typedef SECStatus (*extraVerificationOnCertFn)(CERTCertificate* cert, + SECCertUsage certusage); + +static SECStatus myExtraVerificationOnCert(CERTCertificate* cert, + SECCertUsage certusage) { + RefPtr<SharedCertVerifier> certVerifier; + certVerifier = GetDefaultCertVerifier(); + if (!certVerifier) { + return SECFailure; + } + + SECCertificateUsage usageForPkix; + + switch (certusage) { + case certUsageEmailSigner: + usageForPkix = certificateUsageEmailSigner; + break; + case certUsageEmailRecipient: + usageForPkix = certificateUsageEmailRecipient; + break; + default: + return SECFailure; + } + + nsTArray<uint8_t> certBytes(cert->derCert.data, cert->derCert.len); + nsTArray<nsTArray<uint8_t>> builtChain; + // This code is used when verifying incoming certificates, including + // a signature certificate. Performing OCSP is necessary. + // Allowing OCSP in blocking mode should be fine, because all our + // callers run this code on a separate thread, using + // SMimeVerificationTask/CryptoTask. + mozilla::pkix::Result result = certVerifier->VerifyCert( + certBytes, usageForPkix, Now(), nullptr /*XXX pinarg*/, + nullptr /*hostname*/, builtChain); + if (result != mozilla::pkix::Success) { + return SECFailure; + } + + return SECSuccess; +} + +// This is a temporary copy of NSS_CMSSignedData_ImportCerts, which +// performs additional verifications prior to import. +// The copy is almost identical to the original. +// +// The ONLY DIFFERENCE is the addition of parameter extraVerifyFn, +// and the call to it - plus a non-null check. +// +// NSS should add this or a similar API in the future, +// and then these temporary functions should be removed, including +// the ones above. Request is tracked in bugzilla 1738592. +static SECStatus myNSS_CMSSignedData_ImportCerts( + NSSCMSSignedData* sigd, CERTCertDBHandle* certdb, SECCertUsage certusage, + PRBool keepcerts, extraVerificationOnCertFn extraVerifyFn) { + int certcount; + CERTCertificate** certArray = NULL; + CERTCertList* certList = NULL; + CERTCertListNode* node; + SECStatus rv; + SECItem** rawArray; + int i; + PRTime now; + + if (!sigd) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + certcount = myNSS_CMSArray_Count((void**)sigd->rawCerts); + + /* get the certs in the temp DB */ + rv = CERT_ImportCerts(certdb, certusage, certcount, sigd->rawCerts, + &certArray, PR_FALSE, PR_FALSE, NULL); + if (rv != SECSuccess) { + goto loser; + } + + /* save the certs so they don't get destroyed */ + for (i = 0; i < certcount; i++) { + CERTCertificate* cert = certArray[i]; + if (cert) myNSS_CMSSignedData_AddTempCertificate(sigd, cert); + } + + if (!keepcerts) { + goto done; + } + + /* build a CertList for filtering */ + certList = CERT_NewCertList(); + if (certList == NULL) { + rv = SECFailure; + goto loser; + } + for (i = 0; i < certcount; i++) { + CERTCertificate* cert = certArray[i]; + if (cert) cert = CERT_DupCertificate(cert); + if (cert) CERT_AddCertToListTail(certList, cert); + } + + /* filter out the certs we don't want */ + rv = CERT_FilterCertListByUsage(certList, certusage, PR_FALSE); + if (rv != SECSuccess) { + goto loser; + } + + /* go down the remaining list of certs and verify that they have + * valid chains, then import them. + */ + now = PR_Now(); + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + CERTCertificateList* certChain; + + if (!node->cert) { + continue; + } + + if (extraVerifyFn) { + if ((*extraVerifyFn)(node->cert, certusage) != SECSuccess) { + continue; + } + } + + if (CERT_VerifyCert(certdb, node->cert, PR_TRUE, certusage, now, NULL, + NULL) != SECSuccess) { + continue; + } + + certChain = CERT_CertChainFromCert(node->cert, certusage, PR_FALSE); + if (!certChain) { + continue; + } + + /* + * CertChain returns an array of SECItems, import expects an array of + * SECItem pointers. Create the SECItem Pointers from the array of + * SECItems. + */ + rawArray = (SECItem**)PORT_Alloc(certChain->len * sizeof(SECItem*)); + if (!rawArray) { + CERT_DestroyCertificateList(certChain); + continue; + } + for (i = 0; i < certChain->len; i++) { + rawArray[i] = &certChain->certs[i]; + } + (void)CERT_ImportCerts(certdb, certusage, certChain->len, rawArray, NULL, + keepcerts, PR_FALSE, NULL); + PORT_Free(rawArray); + CERT_DestroyCertificateList(certChain); + } + + rv = SECSuccess; + + /* XXX CRL handling */ + +done: + if (sigd->signerInfos != NULL) { + /* fill in all signerinfo's certs */ + for (i = 0; sigd->signerInfos[i] != NULL; i++) + (void)NSS_CMSSignerInfo_GetSigningCertificate(sigd->signerInfos[i], + certdb); + } + +loser: + /* now free everything */ + if (certArray) { + CERT_DestroyCertArray(certArray, certcount); + } + if (certList) { + CERT_DestroyCertList(certList); + } + + return rv; +} + +nsresult nsCMSMessage::CommonVerifySignature( + int32_t verifyFlags, const nsTArray<uint8_t>& aDigestData, + int16_t aDigestType) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature, content level count %d", + NSS_CMSMessage_ContentLevelCount(m_cmsMsg))); + NSSCMSContentInfo* cinfo = nullptr; + NSSCMSSignedData* sigd = nullptr; + NSSCMSSignerInfo* si; + int32_t nsigners; + nsresult rv = NS_ERROR_FAILURE; + SECOidTag sigAlgTag; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - not signed")); + return NS_ERROR_CMS_VERIFY_NOT_SIGNED; + } + + cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (cinfo) { + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_SIGNED_DATA: + sigd = reinterpret_cast<NSSCMSSignedData*>( + NSS_CMSContentInfo_GetContent(cinfo)); + break; + + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + default: { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - unexpected " + "ContentTypeTag")); + rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + goto loser; + } + } + } + + if (!sigd) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - no content info")); + rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + goto loser; + } + + if (!aDigestData.IsEmpty()) { + SECOidTag oidTag; + SECItem digest; + // NSS_CMSSignedData_SetDigestValue() takes a copy and won't mutate our + // data, so we're OK to cast away the const here. + digest.data = const_cast<uint8_t*>(aDigestData.Elements()); + digest.len = aDigestData.Length(); + + if (NSS_CMSSignedData_HasDigests(sigd)) { + SECAlgorithmID** existingAlgs = NSS_CMSSignedData_GetDigestAlgs(sigd); + if (existingAlgs) { + while (*existingAlgs) { + SECAlgorithmID* alg = *existingAlgs; + SECOidTag algOIDTag = SECOID_FindOIDTag(&alg->algorithm); + NSS_CMSSignedData_SetDigestValue(sigd, algOIDTag, NULL); + ++existingAlgs; + } + } + } + + oidTag = + HASH_GetHashOidTagByHashType(static_cast<HASH_HashType>(aDigestType)); + if (oidTag == SEC_OID_UNKNOWN) { + rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST; + goto loser; + } + + if (NSS_CMSSignedData_SetDigestValue(sigd, oidTag, &digest)) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - bad digest")); + rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST; + goto loser; + } + } + + // Import certs. Note that import failure is not a signature verification + // failure. // + if (myNSS_CMSSignedData_ImportCerts( + sigd, CERT_GetDefaultCertDB(), certUsageEmailRecipient, true, + myExtraVerificationOnCert) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - can not import certs")); + } + + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + PR_ASSERT(nsigners > 0); + NS_ENSURE_TRUE(nsigners > 0, NS_ERROR_UNEXPECTED); + si = NSS_CMSSignedData_GetSignerInfo(sigd, 0); + + NS_ENSURE_TRUE(si, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(si->cert, NS_ERROR_UNEXPECTED); + + // See bug 324474. We want to make sure the signing cert is + // still valid at the current time. + + if (myExtraVerificationOnCert(si->cert, certUsageEmailSigner) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - signing cert not trusted " + "now")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + goto loser; + } + + sigAlgTag = NSS_CMSSignerInfo_GetDigestAlgTag(si); + switch (sigAlgTag) { + case SEC_OID_SHA256: + case SEC_OID_SHA384: + case SEC_OID_SHA512: + break; + + case SEC_OID_SHA1: + if (verifyFlags & nsICMSVerifyFlags::VERIFY_ALLOW_WEAK_SHA1) { + break; + } + // else fall through to failure +#if defined(__clang__) + [[clang::fallthrough]]; +#endif + + default: + MOZ_LOG( + gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - unsupported digest algo")); + rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO; + goto loser; + }; + + // We verify the first signer info, only // + // XXX: NSS_CMSSignedData_VerifySignerInfo calls CERT_VerifyCert, which + // requires NSS's certificate verification configuration to be done in + // order to work well (e.g. honoring OCSP preferences and proxy settings + // for OCSP requests), but Gecko stopped doing that configuration. Something + // similar to what was done for Gecko bug 1028643 needs to be done here too. + if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(), + certUsageEmailSigner) != SECSuccess) { + MOZ_LOG( + gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - unable to verify signature")); + + if (NSSCMSVS_SigningCertNotFound == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - signing cert not found")); + rv = NS_ERROR_CMS_VERIFY_NOCERT; + } else if (NSSCMSVS_SigningCertNotTrusted == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - signing cert not trusted " + "at signing time")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + } else if (NSSCMSVS_Unverified == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - can not verify")); + rv = NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED; + } else if (NSSCMSVS_ProcessingError == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - processing error")); + rv = NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; + } else if (NSSCMSVS_BadSignature == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - bad signature")); + rv = NS_ERROR_CMS_VERIFY_BAD_SIGNATURE; + } else if (NSSCMSVS_DigestMismatch == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - digest mismatch")); + rv = NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH; + } else if (NSSCMSVS_SignatureAlgorithmUnknown == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - algo unknown")); + rv = NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO; + } else if (NSSCMSVS_SignatureAlgorithmUnsupported == + si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - algo not supported")); + rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO; + } else if (NSSCMSVS_MalformedSignature == si->verificationStatus) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - malformed signature")); + rv = NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE; + } + + goto loser; + } + + // Save the profile. Note that save import failure is not a signature + // verification failure. // + if (NSS_SMIMESignerInfo_SaveSMIMEProfile(si) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - unable to save smime " + "profile")); + } + + rv = NS_OK; +loser: + return rv; +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifySignature( + int32_t verifyFlags, nsISMimeVerificationListener* aListener) { + return CommonAsyncVerifySignature(verifyFlags, aListener, {}, 0); +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature( + int32_t verifyFlags, nsISMimeVerificationListener* aListener, + const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) { + if (aDigestData.IsEmpty()) return NS_ERROR_FAILURE; + + return CommonAsyncVerifySignature(verifyFlags, aListener, aDigestData, + aDigestType); +} + +class SMimeVerificationTask final : public CryptoTask { + public: + SMimeVerificationTask(nsICMSMessage* aMessage, int32_t verifyFlags, + nsISMimeVerificationListener* aListener, + const nsTArray<uint8_t>& aDigestData, + int16_t aDigestType) + : mMessage(aMessage), + mListener(aListener), + mDigestData(aDigestData.Clone()), + mDigestType(aDigestType), + mVerifyFlags(verifyFlags) { + MOZ_ASSERT(NS_IsMainThread()); + } + + private: + virtual nsresult CalculateResult() override { + MOZ_ASSERT(!NS_IsMainThread()); + + // Because the S/MIME code and related certificate processing isn't + // sufficiently threadsafe (see bug 1529003), we want this code to + // never run in parallel (see bug 1386601). + mozilla::StaticMutexAutoLock lock(sMutex); + nsresult rv; + if (mDigestData.IsEmpty()) { + rv = mMessage->VerifySignature(mVerifyFlags); + } else { + rv = mMessage->VerifyDetachedSignature(mVerifyFlags, mDigestData, + mDigestType); + } + + return rv; + } + virtual void CallCallback(nsresult rv) override { + MOZ_ASSERT(NS_IsMainThread()); + mListener->Notify(mMessage, rv); + } + + nsCOMPtr<nsICMSMessage> mMessage; + nsCOMPtr<nsISMimeVerificationListener> mListener; + nsTArray<uint8_t> mDigestData; + int16_t mDigestType; + int32_t mVerifyFlags; + + static mozilla::StaticMutex sMutex; +}; + +mozilla::StaticMutex SMimeVerificationTask::sMutex; + +nsresult nsCMSMessage::CommonAsyncVerifySignature( + int32_t verifyFlags, nsISMimeVerificationListener* aListener, + const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) { + RefPtr<CryptoTask> task = new SMimeVerificationTask( + this, verifyFlags, aListener, aDigestData, aDigestType); + return task->Dispatch(); +} + +class nsZeroTerminatedCertArray { + public: + nsZeroTerminatedCertArray() : mCerts(nullptr), mPoolp(nullptr), mSize(0) {} + + ~nsZeroTerminatedCertArray() { + if (mCerts) { + for (uint32_t i = 0; i < mSize; i++) { + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + } + } + + if (mPoolp) PORT_FreeArena(mPoolp, false); + } + + bool allocate(uint32_t count) { + // only allow allocation once + if (mPoolp) return false; + + mSize = count; + + if (!mSize) return false; + + mPoolp = PORT_NewArena(1024); + if (!mPoolp) return false; + + mCerts = (CERTCertificate**)PORT_ArenaZAlloc( + mPoolp, (count + 1) * sizeof(CERTCertificate*)); + + if (!mCerts) return false; + + // null array, including zero termination + for (uint32_t i = 0; i < count + 1; i++) { + mCerts[i] = nullptr; + } + + return true; + } + + void set(uint32_t i, CERTCertificate* c) { + if (i >= mSize) return; + + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + + mCerts[i] = CERT_DupCertificate(c); + } + + CERTCertificate* get(uint32_t i) { + if (i >= mSize) return nullptr; + + return CERT_DupCertificate(mCerts[i]); + } + + CERTCertificate** getRawArray() { return mCerts; } + + private: + CERTCertificate** mCerts; + PLArenaPool* mPoolp; + uint32_t mSize; +}; + +NS_IMETHODIMP nsCMSMessage::CreateEncrypted( + const nsTArray<RefPtr<nsIX509Cert>>& aRecipientCerts) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted")); + NSSCMSContentInfo* cinfo; + NSSCMSEnvelopedData* envd; + NSSCMSRecipientInfo* recipientInfo; + nsZeroTerminatedCertArray recipientCerts; + SECOidTag bulkAlgTag; + int keySize; + uint32_t i; + nsresult rv = NS_ERROR_FAILURE; + + // Check the recipient certificates // + uint32_t recipientCertCount = aRecipientCerts.Length(); + PR_ASSERT(recipientCertCount > 0); + + if (!recipientCerts.allocate(recipientCertCount)) { + goto loser; + } + + for (i = 0; i < recipientCertCount; i++) { + nsIX509Cert* x509cert = aRecipientCerts[i]; + + if (!x509cert) return NS_ERROR_FAILURE; + + UniqueCERTCertificate c(x509cert->GetCert()); + recipientCerts.set(i, c.get()); + } + + // Find a bulk key algorithm // + if (NSS_SMIMEUtil_FindBulkAlgForRecipients( + recipientCerts.getRawArray(), &bulkAlgTag, &keySize) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't find bulk alg for " + "recipients")); + rv = NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG; + goto loser; + } + + m_cmsMsg = NSS_CMSMessage_Create(nullptr); + if (!m_cmsMsg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't create new cms message")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + if ((envd = NSS_CMSEnvelopedData_Create(m_cmsMsg, bulkAlgTag, keySize)) == + nullptr) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't create enveloped data")); + goto loser; + } + + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_EnvelopedData(m_cmsMsg, cinfo, envd) != + SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't create content enveloped " + "data")); + goto loser; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd); + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, false) != + SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't set content data")); + goto loser; + } + + // Create and attach recipient information // + for (i = 0; i < recipientCertCount; i++) { + UniqueCERTCertificate rc(recipientCerts.get(i)); + if ((recipientInfo = NSS_CMSRecipientInfo_Create(m_cmsMsg, rc.get())) == + nullptr) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't create recipient info")); + goto loser; + } + if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientInfo) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateEncrypted - can't add recipient info")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + + return rv; +} + +bool nsCMSMessage::IsAllowedHash(const int16_t aCryptoHashInt) { + switch (aCryptoHashInt) { + case nsICryptoHash::SHA1: + case nsICryptoHash::SHA256: + case nsICryptoHash::SHA384: + case nsICryptoHash::SHA512: + return true; + default: + return false; + } +} + +NS_IMETHODIMP +nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert, + const nsTArray<uint8_t>& aDigestData, + int16_t aDigestType) { + NS_ENSURE_ARG(aSigningCert); + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned")); + NSSCMSContentInfo* cinfo; + NSSCMSSignedData* sigd; + NSSCMSSignerInfo* signerinfo; + UniqueCERTCertificate scert(aSigningCert->GetCert()); + UniqueCERTCertificate ecert; + nsresult rv = NS_ERROR_FAILURE; + + if (!scert) { + return NS_ERROR_FAILURE; + } + + if (aEncryptCert) { + ecert = UniqueCERTCertificate(aEncryptCert->GetCert()); + } + + if (!IsAllowedHash(aDigestType)) { + return NS_ERROR_INVALID_ARG; + } + + SECOidTag digestType = + HASH_GetHashOidTagByHashType(static_cast<HASH_HashType>(aDigestType)); + if (digestType == SEC_OID_UNKNOWN) { + return NS_ERROR_INVALID_ARG; + } + + /* + * create the message object + */ + m_cmsMsg = + NSS_CMSMessage_Create(nullptr); /* create a message on its own pool */ + if (!m_cmsMsg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't create new message")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + /* + * build chain of objects: message->signedData->data + */ + if ((sigd = NSS_CMSSignedData_Create(m_cmsMsg)) == nullptr) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't create signed data")); + goto loser; + } + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_SignedData(m_cmsMsg, cinfo, sigd) != + SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't set content signed data")); + goto loser; + } + + cinfo = NSS_CMSSignedData_GetContentInfo(sigd); + + /* we're always passing data in and detaching optionally */ + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, true) != + SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't set content data")); + goto loser; + } + + /* + * create & attach signer information + */ + signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType); + if (!signerinfo) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't create signer info")); + goto loser; + } + + /* we want the cert chain included for this one */ + if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, + certUsageEmailSigner) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't include signer cert chain")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add signing time")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add smime caps")); + goto loser; + } + + if (ecert) { + if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs( + signerinfo, ecert.get(), CERT_GetDefaultCertDB()) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add smime enc key prefs")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs( + signerinfo, ecert.get(), CERT_GetDefaultCertDB()) != SECSuccess) { + MOZ_LOG( + gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add MS smime enc key prefs")); + goto loser; + } + + // If signing and encryption cert are identical, don't add it twice. + bool addEncryptionCert = + (ecert && (!scert || !CERT_CompareCerts(ecert.get(), scert.get()))); + + if (addEncryptionCert && + NSS_CMSSignedData_AddCertificate(sigd, ecert.get()) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add own encryption " + "certificate")); + goto loser; + } + } + + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't add signer info")); + goto loser; + } + + // Finally, add the pre-computed digest if passed in + if (!aDigestData.IsEmpty()) { + SECItem digest; + + // NSS_CMSSignedData_SetDigestValue() takes a copy and won't mutate our + // data, so we're OK to cast away the const here. + digest.data = const_cast<uint8_t*>(aDigestData.Elements()); + digest.len = aDigestData.Length(); + if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) != + SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::CreateSigned - can't set digest value")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + return rv; +} + +NS_IMPL_ISUPPORTS(nsCMSDecoder, nsICMSDecoder) +NS_IMPL_ISUPPORTS(nsCMSDecoderJS, nsICMSDecoderJS) + +nsCMSDecoder::nsCMSDecoder() : m_dcx(nullptr) {} +nsCMSDecoderJS::nsCMSDecoderJS() : m_dcx(nullptr) {} + +nsCMSDecoder::~nsCMSDecoder() { + if (m_dcx) { + NSS_CMSDecoder_Cancel(m_dcx); + m_dcx = nullptr; + } +} + +nsCMSDecoderJS::~nsCMSDecoderJS() { + if (m_dcx) { + NSS_CMSDecoder_Cancel(m_dcx); + m_dcx = nullptr; + } +} + +nsresult nsCMSDecoder::Init() { + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = + do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +nsresult nsCMSDecoderJS::Init() { + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = + do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +/* void start (in NSSCMSContentCallback cb, in voidPtr arg); */ +NS_IMETHODIMP nsCMSDecoder::Start(NSSCMSContentCallback cb, void* arg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSDecoder::Start")); + m_ctx = new PipUIContext(); + + m_dcx = NSS_CMSDecoder_Start(0, cb, arg, 0, m_ctx, 0, 0); + if (!m_dcx) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSDecoder::Start - can't start decoder")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string bug, in long len); */ +NS_IMETHODIMP nsCMSDecoder::Update(const char* buf, int32_t len) { + NSS_CMSDecoder_Update(m_dcx, (char*)buf, len); + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSDecoder::Finish(nsICMSMessage** aCMSMsg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSDecoder::Finish")); + NSSCMSMessage* cmsMsg; + cmsMsg = NSS_CMSDecoder_Finish(m_dcx); + m_dcx = nullptr; + if (cmsMsg) { + nsCMSMessage* obj = new nsCMSMessage(cmsMsg); + // The NSS object cmsMsg still carries a reference to the context + // we gave it on construction. + // Make sure the context will live long enough. + obj->referenceContext(m_ctx); + NS_ADDREF(*aCMSMsg = obj); + } + return NS_OK; +} + +void nsCMSDecoderJS::content_callback(void* arg, const char* input, + unsigned long length) { + nsCMSDecoderJS* self = reinterpret_cast<nsCMSDecoderJS*>(arg); + self->mDecryptedData.AppendElements(input, length); +} + +NS_IMETHODIMP nsCMSDecoderJS::Decrypt(const nsTArray<uint8_t>& aInput, + nsTArray<uint8_t>& _retval) { + if (aInput.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + m_ctx = new PipUIContext(); + + m_dcx = NSS_CMSDecoder_Start(0, nsCMSDecoderJS::content_callback, this, 0, + m_ctx, 0, 0); + if (!m_dcx) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSDecoderJS::Start - can't start decoder")); + return NS_ERROR_FAILURE; + } + + NSS_CMSDecoder_Update(m_dcx, (char*)aInput.Elements(), aInput.Length()); + + NSSCMSMessage* cmsMsg; + cmsMsg = NSS_CMSDecoder_Finish(m_dcx); + m_dcx = nullptr; + if (cmsMsg) { + nsCMSMessage* obj = new nsCMSMessage(cmsMsg); + // The NSS object cmsMsg still carries a reference to the context + // we gave it on construction. + // Make sure the context will live long enough. + obj->referenceContext(m_ctx); + mCMSMessage = obj; + } + + _retval = mDecryptedData.Clone(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsCMSEncoder, nsICMSEncoder) + +nsCMSEncoder::nsCMSEncoder() : m_ecx(nullptr) {} + +nsCMSEncoder::~nsCMSEncoder() { + if (m_ecx) NSS_CMSEncoder_Cancel(m_ecx); +} + +nsresult nsCMSEncoder::Init() { + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = + do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +/* void start (); */ +NS_IMETHODIMP nsCMSEncoder::Start(nsICMSMessage* aMsg, NSSCMSContentCallback cb, + void* arg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Start")); + nsCMSMessage* cmsMsg = static_cast<nsCMSMessage*>(aMsg); + m_ctx = new PipUIContext(); + + m_ecx = NSS_CMSEncoder_Start(cmsMsg->getCMS(), cb, arg, 0, 0, 0, m_ctx, 0, 0, + 0, 0); + if (!m_ecx) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSEncoder::Start - can't start encoder")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string aBuf, in long aLen); */ +NS_IMETHODIMP nsCMSEncoder::Update(const char* aBuf, int32_t aLen) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Update")); + if (!m_ecx || NSS_CMSEncoder_Update(m_ecx, aBuf, aLen) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSEncoder::Update - can't update encoder")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSEncoder::Finish() { + nsresult rv = NS_OK; + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Finish")); + if (!m_ecx || NSS_CMSEncoder_Finish(m_ecx) != SECSuccess) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSEncoder::Finish - can't finish encoder")); + rv = NS_ERROR_FAILURE; + } + m_ecx = nullptr; + return rv; +} + +/* void encode (in nsICMSMessage aMsg); */ +NS_IMETHODIMP nsCMSEncoder::Encode(nsICMSMessage* aMsg) { + MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Encode")); + return NS_ERROR_NOT_IMPLEMENTED; +} |