Bug 1018259 - Thunderbird should stop using SHA-1 when signing email messages: select most appropriate hash algorithm based on key type and size. r=jcranmer, r=mkmelin
--- a/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp
+++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp
@@ -1,17 +1,20 @@
/* -*- 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 "cert.h"
+#include "keyhi.h"
#include "msgCore.h"
+#include "nsICryptoHash.h"
#include "nsIMsgCompFields.h"
#include "nsIMsgIdentity.h"
#include "nsIX509CertDB.h"
#include "nsMimeTypes.h"
#include "nsMsgMimeCID.h"
#include "nspr.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
@@ -34,24 +37,25 @@ using namespace mozilla;
// 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);
+ 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);
+ 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;
@@ -131,16 +135,17 @@ NS_IMETHODIMP nsMsgSMIMEComposeFields::G
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);
@@ -304,16 +309,97 @@ nsresult nsMsgComposeSecure::ExtractEncr
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;
+ }
+
+ mozilla::ScopedSECKEYPublicKey scertPublicKey(CERT_ExtractPublicKey(scert));
+ if (!scertPublicKey) {
+ return mozilla::MapSECStatus(SECFailure);
+ }
+ KeyType subjectPublicKeyType = SECKEY_GetPublicKeyType(scertPublicKey);
+
+ // Get the length of the signature in bits.
+ unsigned siglen = SECKEY_SignatureLen(scertPublicKey) * 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)
{
@@ -341,16 +427,21 @@ NS_IMETHODIMP nsMsgComposeSecure::BeginC
aIdentity->GetUnicharAttribute("signing_cert_name", mSigningCertName);
aIdentity->GetUnicharAttribute("encryption_cert_name", mEncryptionCertName);
rv = MimeCryptoHackCerts(aRecipients, sendReport, encryptMessages, signMessage);
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;
@@ -409,17 +500,18 @@ nsresult nsMsgComposeSecure::MimeInitMul
{
/* 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);
+ &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);
@@ -434,18 +526,16 @@ nsresult nsMsgComposeSecure::MimeInitMul
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.
*/
- mHashType = nsICryptoHash::SHA1;
-
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);
@@ -634,22 +724,23 @@ nsresult nsMsgComposeSecure::MimeFinishM
rv = MimeCryptoWriteBlock (header, L);
}
PR_Free(header);
/* Create the signature...
*/
- PR_ASSERT(mHashType == nsICryptoHash::SHA1);
+ 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());
+ rv = cinfo->CreateSigned(mSelfSigningCert, mSelfEncryptionCert,
+ (unsigned char*)hashString.get(), hashString.Length(), mHashType);
if (NS_FAILED(rv)) {
SetError(sendReport, MOZ_UTF16("ErrorCanNotSignMail"));
goto FAIL;
}
// Initialize the base64 encoder for the signature data.
MOZ_ASSERT(!mSigEncoder, "Shouldn't already have a mSigEncoder");
mSigEncoder = MimeEncoder::GetBase64Encoder(
@@ -941,34 +1032,53 @@ NS_IMETHODIMP nsMsgComposeSecure::MimeCr
/* 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)
+ 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=" PARAM_MICALG_SHA1 "; "
+ "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;
--- a/mailnews/mime/public/nsICMSMessage.idl
+++ b/mailnews/mime/public/nsICMSMessage.idl
@@ -13,23 +13,27 @@
interface nsIX509Cert;
interface nsIArray;
/**
* nsICMSMessage
* Interface to a CMS Message
*/
-[uuid(cfd6af3d-8ac6-401a-afc8-3f94275c1c11)]
+[uuid(c6d51c22-73e9-4dad-86b9-bde584e33c63)]
interface nsICMSMessage : nsISupports
{
void contentIsSigned(out boolean aSigned);
void contentIsEncrypted(out boolean aEncrypted);
void getSignerCommonName(out string aName);
void getSignerEmailAddress(out string aEmail);
void getSignerCert(out nsIX509Cert scert);
void getEncryptionCert(out nsIX509Cert ecert);
void verifySignature();
void verifyDetachedSignature(in UnsignedCharPtr aDigestData, in unsigned long aDigestDataLen);
void CreateEncrypted(in nsIArray aRecipientCerts);
- void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert, in UnsignedCharPtr aDigestData, in unsigned long aDigestDataLen);
+
+ /* The parameter aDigestType must be one of the values in nsICryptoHash */
+ void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert,
+ in UnsignedCharPtr aDigestData,
+ in unsigned long aDigestDataLen, in int16_t aDigestType);
};
--- a/mailnews/mime/src/nsCMS.cpp
+++ b/mailnews/mime/src/nsCMS.cpp
@@ -1,33 +1,33 @@
/* -*- 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 "cms.h"
+#include "CryptoTask.h"
+#include "nsArrayUtils.h"
+#include "nsCertVerificationThread.h"
+#include "nsIArray.h"
+#include "nsICMSMessageErrors.h"
+#include "nsICryptoHash.h"
+#include "nsISupports.h"
#include "nsIX509CertDB.h"
-#include "CertVerifier.h"
-#include "CryptoTask.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsServiceManagerUtils.h"
#include "mozilla/RefPtr.h"
#include "pkix/pkixtypes.h"
-#include "nsISupports.h"
-#include "nsNSSHelper.h"
-#include "nsNSSCertificate.h"
#include "ScopedNSSTypes.h"
#include "smime.h"
-#include "cms.h"
-#include "nsNSSComponent.h"
-#include "nsICMSMessageErrors.h"
-#include "nsIArray.h"
-#include "nsArrayUtils.h"
-#include "nsCertVerificationThread.h"
-#include "nsServiceManagerUtils.h"
-#include "mozilla/RefPtr.h"
#include "prlog.h"
using namespace mozilla;
using namespace mozilla::psm;
using namespace mozilla::pkix;
#ifdef PR_LOGGING
@@ -614,17 +614,20 @@ loser:
if (m_cmsMsg) {
NSS_CMSMessage_Destroy(m_cmsMsg);
m_cmsMsg = nullptr;
}
return rv;
}
-NS_IMETHODIMP nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert, unsigned char* aDigestData, uint32_t aDigestDataLen)
+NS_IMETHODIMP
+nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType)
{
NS_ENSURE_ARG(aSigningCert);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown())
return NS_ERROR_NOT_AVAILABLE;
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CreateSigned\n"));
NSSCMSContentInfo *cinfo;
@@ -637,16 +640,34 @@ NS_IMETHODIMP nsCMSMessage::CreateSigned
if (!scert) {
return NS_ERROR_FAILURE;
}
if (aEncryptCert) {
ecert = aEncryptCert->GetCert();
}
+ SECOidTag digestType;
+ switch (aDigestType) {
+ case nsICryptoHash::SHA1:
+ digestType = SEC_OID_SHA1;
+ break;
+ case nsICryptoHash::SHA256:
+ digestType = SEC_OID_SHA256;
+ break;
+ case nsICryptoHash::SHA384:
+ digestType = SEC_OID_SHA384;
+ break;
+ case nsICryptoHash::SHA512:
+ digestType = SEC_OID_SHA512;
+ break;
+ default:
+ 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) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CreateSigned - can't create new message\n"));
rv = NS_ERROR_OUT_OF_MEMORY;
goto loser;
@@ -673,18 +694,18 @@ NS_IMETHODIMP nsCMSMessage::CreateSigned
!= SECSuccess) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CreateSigned - can't set content data\n"));
goto loser;
}
/*
* create & attach signer information
*/
- if ((signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), SEC_OID_SHA1))
- == nullptr) {
+ signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType);
+ if (!signerinfo) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CreateSigned - can't create signer info\n"));
goto loser;
}
/* we want the cert chain included for this one */
if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain,
certUsageEmailSigner)
!= SECSuccess) {
@@ -736,17 +757,17 @@ NS_IMETHODIMP nsCMSMessage::CreateSigned
// Finally, add the pre-computed digest if passed in
if (aDigestData) {
SECItem digest;
digest.data = aDigestData;
digest.len = aDigestDataLen;
- if (NSS_CMSSignedData_SetDigestValue(sigd, SEC_OID_SHA1, &digest)) {
+ if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) != SECSuccess) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsCMSMessage::CreateSigned - can't set digest value\n"));
goto loser;
}
}
return NS_OK;
loser:
if (m_cmsMsg) {