diff options
Diffstat (limited to 'mailnews/mime/src/mimemcms.cpp')
-rw-r--r-- | mailnews/mime/src/mimemcms.cpp | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/mailnews/mime/src/mimemcms.cpp b/mailnews/mime/src/mimemcms.cpp new file mode 100644 index 000000000..96aa5481f --- /dev/null +++ b/mailnews/mime/src/mimemcms.cpp @@ -0,0 +1,494 @@ +/* -*- 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 "nsICMSMessage.h" +#include "nsICMSMessageErrors.h" +#include "nsICMSDecoder.h" +#include "nsICryptoHash.h" +#include "mimemcms.h" +#include "mimecryp.h" +#include "nsMimeTypes.h" +#include "nspr.h" +#include "nsMimeStringResources.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "nsIURI.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsIX509Cert.h" +#include "plstr.h" +#include "nsComponentManagerUtils.h" + +#define MIME_SUPERCLASS mimeMultipartSignedClass +MimeDefClass(MimeMultipartSignedCMS, MimeMultipartSignedCMSClass, + mimeMultipartSignedCMSClass, &MIME_SUPERCLASS); + +static int MimeMultipartSignedCMS_initialize (MimeObject *); + +static void *MimeMultCMS_init (MimeObject *); +static int MimeMultCMS_data_hash (const char *, int32_t, void *); +static int MimeMultCMS_sig_hash (const char *, int32_t, void *); +static int MimeMultCMS_data_eof (void *, bool); +static int MimeMultCMS_sig_eof (void *, bool); +static int MimeMultCMS_sig_init (void *, MimeObject *, MimeHeaders *); +static char * MimeMultCMS_generate (void *); +static void MimeMultCMS_free (void *); +static void MimeMultCMS_suppressed_child(void *crypto_closure); + +extern int SEC_ERROR_CERT_ADDR_MISMATCH; + +static int +MimeMultipartSignedCMSClassInitialize(MimeMultipartSignedCMSClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartSignedClass *sclass = (MimeMultipartSignedClass *) clazz; + + oclass->initialize = MimeMultipartSignedCMS_initialize; + + sclass->crypto_init = MimeMultCMS_init; + sclass->crypto_data_hash = MimeMultCMS_data_hash; + sclass->crypto_data_eof = MimeMultCMS_data_eof; + sclass->crypto_signature_init = MimeMultCMS_sig_init; + sclass->crypto_signature_hash = MimeMultCMS_sig_hash; + sclass->crypto_signature_eof = MimeMultCMS_sig_eof; + sclass->crypto_generate_html = MimeMultCMS_generate; + sclass->crypto_notify_suppressed_child = MimeMultCMS_suppressed_child; + sclass->crypto_free = MimeMultCMS_free; + + PR_ASSERT(!oclass->class_initialized); + return 0; +} + +static int +MimeMultipartSignedCMS_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + + +typedef struct MimeMultCMSdata +{ + int16_t hash_type; + nsCOMPtr<nsICryptoHash> data_hash_context; + nsCOMPtr<nsICMSDecoder> sig_decoder_context; + nsCOMPtr<nsICMSMessage> content_info; + char *sender_addr; + bool decoding_failed; + unsigned char* item_data; + uint32_t item_len; + MimeObject *self; + nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink; + nsCString url; + + MimeMultCMSdata() + :hash_type(0), + sender_addr(nullptr), + decoding_failed(false), + item_data(nullptr), + self(nullptr) {} + + ~MimeMultCMSdata() + { + PR_FREEIF(sender_addr); + + // Do a graceful shutdown of the nsICMSDecoder and release the nsICMSMessage // + if (sig_decoder_context) + { + nsCOMPtr<nsICMSMessage> cinfo; + sig_decoder_context->Finish(getter_AddRefs(cinfo)); + } + + delete [] item_data; + } +} MimeMultCMSdata; + +/* #### MimeEncryptedCMS and MimeMultipartSignedCMS have a sleazy, + incestuous, dysfunctional relationship. */ +extern bool MimeAnyParentCMSSigned(MimeObject *obj); +extern void MimeCMSGetFromSender(MimeObject *obj, + nsCString &from_addr, + nsCString &from_name, + nsCString &sender_addr, + nsCString &sender_name); +extern bool MimeCMSHeadersAndCertsMatch(MimeObject *obj, + nsICMSMessage *, + bool *signing_cert_without_email_address); +extern void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg, + const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + const nsCString &aMsgNeckoURL, + unsigned char* item_data, uint32_t item_len, + int16_t digest_type); +extern char *MimeCMS_MakeSAURL(MimeObject *obj); +extern char *IMAP_CreateReloadAllPartsUrl(const char *url); +extern int MIMEGetRelativeCryptoNestLevel(MimeObject *obj); + +static void * +MimeMultCMS_init (MimeObject *obj) +{ + MimeHeaders *hdrs = obj->headers; + MimeMultCMSdata *data = 0; + char *ct, *micalg; + int16_t hash_type; + nsresult rv; + + data = new MimeMultCMSdata; + if (!data) + return 0; + + data->self = obj; + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl; + nsCOMPtr<nsISupports> securityInfo; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + rv = uri->GetSpec(data->url); + + // We only want to update the UI if the current mime transaction + // is intended for display. + // If the current transaction is intended for background processing, + // we can learn that by looking at the additional header=filter + // string contained in the URI. + // + // If we find something, we do not set smimeHeaderSink, + // which will prevent us from giving UI feedback. + // + // If we do not find header=filter, we assume the result of the + // processing will be shown in the UI. + + if (!strstr(data->url.get(), "?header=filter") && + !strstr(data->url.get(), "&header=filter") && + !strstr(data->url.get(), "?header=attach") && + !strstr(data->url.get(), "&header=attach")) + { + msgurl = do_QueryInterface(uri); + if (msgurl) + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) + data->smimeHeaderSink = do_QueryInterface(securityInfo); + } + } + } // if channel + } // if msd + + if (obj->parent && MimeAnyParentCMSSigned(obj)) { + // Parent is signed. We know this part is a signature, too, because + // multipart doesn't allow encryption. + // We don't support "inner sign" with outer sign, because the + // inner encrypted part could have been produced by an attacker who + // stripped away a part containing the signature (S/MIME doesn't + // have integrity protection). + // Also we don't want to support sign-then-sign, that's misleading, + // which part would be shown as having a signature? + // TODO: should we show all contents, without any signature info? + + if (data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus( + MIMEGetRelativeCryptoNestLevel(data->self), + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } + delete data; + PR_SetError(-1, 0); + return 0; + } + + ct = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (!ct) { + delete data; + return 0; /* #### bogus message? out of memory? */ + } + micalg = MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL); + PR_Free(ct); + ct = 0; + if (!micalg) { + delete data; + return 0; /* #### bogus message? out of memory? */ + } + + if (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2)) + hash_type = nsICryptoHash::MD5; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5)) + hash_type = nsICryptoHash::SHA1; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3)) + hash_type = nsICryptoHash::SHA256; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3)) + hash_type = nsICryptoHash::SHA384; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3)) + hash_type = nsICryptoHash::SHA512; + else + hash_type = -1; + + PR_Free(micalg); + micalg = 0; + + if (hash_type == -1) { + delete data; + return 0; /* #### bogus message? */ + } + + data->hash_type = hash_type; + + data->data_hash_context = + do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + + rv = data->data_hash_context->Init(data->hash_type); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + + PR_SetError(0, 0); + + return data; +} + +static int +MimeMultCMS_data_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + PR_SetError(0, 0); + nsresult rv = data->data_hash_context->Update((unsigned char *) buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_data_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + nsAutoCString hashString; + data->data_hash_context->Finish(false, hashString); + PR_SetError(0, 0); + + data->item_len = hashString.Length(); + data->item_data = new unsigned char[data->item_len]; + if (!data->item_data) return MIME_OUT_OF_MEMORY; + + memcpy(data->item_data, hashString.get(), data->item_len); + + // Release our reference to nsICryptoHash // + data->data_hash_context = nullptr; + + /* At this point, data->item.data contains a digest for the first part. + When we process the signature, the security library will compare this + digest to what's in the signature object. */ + + return 0; +} + + +static int +MimeMultCMS_sig_init (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + char *ct; + int status = 0; + nsresult rv; + + if (!signature_hdrs) { + return -1; + } + + ct = MimeHeaders_get (signature_hdrs, HEADER_CONTENT_TYPE, true, false); + + /* Verify that the signature object is of the right type. */ + if (!ct || /* is not a signature type */ + (PL_strcasecmp(ct, APPLICATION_XPKCS7_SIGNATURE) != 0 + && PL_strcasecmp(ct, APPLICATION_PKCS7_SIGNATURE) != 0)) { + status = -1; /* #### error msg about bogus message */ + } + PR_FREEIF(ct); + if (status < 0) return status; + + data->sig_decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return 0; + + rv = data->sig_decoder_context->Start(nullptr, nullptr); + if (NS_FAILED(rv)) { + status = PR_GetError(); + if (status >= 0) status = -1; + } + return status; +} + + +static int +MimeMultCMS_sig_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + nsresult rv; + + if (!data || !data->sig_decoder_context) { + return -1; + } + + rv = data->sig_decoder_context->Update(buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_sig_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + + if (!data) { + return -1; + } + + /* Hand an EOF to the crypto library. + + We save away the value returned and will use it later to emit a + blurb about whether the signature validation was cool. + */ + + if (data->sig_decoder_context) { + data->sig_decoder_context->Finish(getter_AddRefs(data->content_info)); + + // Release our reference to nsICMSDecoder // + data->sig_decoder_context = nullptr; + } + + return 0; +} + +static void +MimeMultCMS_free (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return; + + delete data; +} + +static void MimeMultCMS_suppressed_child(void *crypto_closure) { + // I'm a multipart/signed. If one of my cryptographic child elements + // was suppressed, then I want my signature to be shown as invalid. + MimeMultCMSdata *data = (MimeMultCMSdata *)crypto_closure; + if (data && data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus( + MIMEGetRelativeCryptoNestLevel(data->self), + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } +} + +static char * +MimeMultCMS_generate (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return 0; + nsCOMPtr<nsIX509Cert> signerCert; + + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + + if (aRelativeNestLevel < 0) + return nullptr; + + int32_t maxNestLevel = 0; + if (data->smimeHeaderSink && aRelativeNestLevel >= 0) + { + data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel); + + if (aRelativeNestLevel > maxNestLevel) + return nullptr; + } + + if (data->self->options->missing_parts) + { + // We were not given all parts of the message. + // We are therefore unable to verify correctness of the signature. + + if (data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::VERIFY_NOT_YET_ATTEMPTED, + nullptr, data->url); + } + return nullptr; + } + + if (!data->content_info) + { + /* No content_info at all -- since we're inside a multipart/signed, + that means that we've either gotten a message that was truncated + before the signature part, or we ran out of memory, or something + awful has happened. + */ + return nullptr; + } + + nsCString from_addr; + nsCString from_name; + nsCString sender_addr; + nsCString sender_name; + + MimeCMSGetFromSender(data->self, + from_addr, from_name, + sender_addr, sender_name); + + MimeCMSRequestAsyncSignatureVerification(data->content_info, + from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), + data->smimeHeaderSink, aRelativeNestLevel, + data->url, + data->item_data, data->item_len, + data->hash_type); + + if (data->content_info) + { +#if 0 // XXX Fix this. What do we do here? // + if (SEC_CMSContainsCertsOrCrls(data->content_info)) + { + /* #### call libsec telling it to import the certs */ + } +#endif + } + + return nullptr; +} |