/* -*- 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 data_hash_context; nsCOMPtr sig_decoder_context; nsCOMPtr content_info; char *sender_addr; bool decoding_failed; unsigned char* item_data; uint32_t item_len; MimeObject *self; nsCOMPtr 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 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 uri; nsCOMPtr msgWindow; nsCOMPtr headerSink; nsCOMPtr msgurl; nsCOMPtr 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 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; }