Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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"
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::GetContentIsEncrypted(bool* isEncrypted) {
MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetContentIsEncrypted"));
NS_ENSURE_ARG(isEncrypted);
if (!m_cmsMsg) return NS_ERROR_FAILURE;
*isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg);
return NS_OK;
}
NS_IMETHODIMP nsCMSMessage::GetContentIsSigned(bool* isSigned) {
MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetContentIsSigned"));
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__) || defined(__GNUC__)
[[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;
}