diff options
Diffstat (limited to 'camel/camel-smime-context.c')
-rw-r--r-- | camel/camel-smime-context.c | 1613 |
1 files changed, 765 insertions, 848 deletions
diff --git a/camel/camel-smime-context.c b/camel/camel-smime-context.c index 3c87c83f64..ea6a73760d 100644 --- a/camel/camel-smime-context.c +++ b/camel/camel-smime-context.c @@ -1,900 +1,587 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> * - * Copyright 2001 Ximian, Inc. (www.ximian.com) + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public - * License as published by the Free Software Foundation. + * Copyright 2003 Ximian, Inc. (www.ximian.com) * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ -/* Note: much of the NSS code was copied from Mozilla's cmsutil.c program */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif -#ifdef HAVE_NSS -#include "camel-smime-context.h" - -#include "camel-mime-filter-from.h" -#include "camel-mime-filter-crlf.h" -#include "camel-stream-filter.h" -#include "camel-stream-fs.h" -#include "camel-stream-mem.h" -#include "camel-mime-part.h" -#include "camel-multipart.h" - #include "nss.h" #include <cms.h> #include <cert.h> #include <certdb.h> #include <pkcs11.h> #include <smime.h> +#include <pkcs11t.h> +#include <pk11func.h> -#define d(x) - -struct _CamelSMimeContextPrivate { - CERTCertDBHandle *certdb; -}; +#include <camel/camel-exception.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-data-wrapper.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-stream-fs.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter-basic.h> +#include <camel/camel-mime-filter-canon.h> -static CamelMimeMessage *smime_sign (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, gboolean signing_time, - gboolean detached, CamelException *ex); +#include "camel-smime-context.h" +#include "camel-operation.h" -static CamelMimeMessage *smime_certsonly (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex); +#define d(x) -static CamelMimeMessage *smime_encrypt (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex); +struct _CamelSMIMEContextPrivate { + CERTCertDBHandle *certdb; -static CamelMimeMessage *smime_envelope (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex); + char *encrypt_key; + camel_smime_sign_t sign_mode; -static CamelMimeMessage *smime_decode (CamelCMSContext *ctx, CamelMimeMessage *message, - CamelCMSValidityInfo **info, CamelException *ex); + unsigned int send_encrypt_key_prefs:1; +}; -static CamelCMSContextClass *parent_class; +static CamelCipherContextClass *parent_class = NULL; -static void -camel_smime_context_init (CamelSMimeContext *context) +/** + * camel_smime_context_new: + * @session: session + * + * Creates a new sm cipher context object. + * + * Returns a new sm cipher context object. + **/ +CamelCipherContext * +camel_smime_context_new(CamelSession *session) { - context->priv = g_new0 (struct _CamelSMimeContextPrivate, 1); + CamelCipherContext *cipher; + CamelSMIMEContext *ctx; + + g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL); + + ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type()); + + cipher =(CamelCipherContext *) ctx; + cipher->session = session; + camel_object_ref(session); + + return cipher; } -static void -camel_smime_context_finalise (CamelObject *o) +void +camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key) { - CamelSMimeContext *context = (CamelSMimeContext *)o; - - g_free (context->encryption_key); - g_free (context->priv); + context->priv->send_encrypt_key_prefs = use; + g_free(context->priv->encrypt_key); + context->priv->encrypt_key = g_strdup(key); } -static void -camel_smime_context_class_init (CamelSMimeContextClass *camel_smime_context_class) +/* set signing mode, clearsigned multipart/signed or enveloped */ +void +camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type) { - CamelCMSContextClass *camel_cms_context_class = - CAMEL_CMS_CONTEXT_CLASS (camel_smime_context_class); - - parent_class = CAMEL_CMS_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cms_context_get_type ())); - - camel_cms_context_class->sign = smime_sign; - camel_cms_context_class->certsonly = smime_certsonly; - camel_cms_context_class->encrypt = smime_encrypt; - camel_cms_context_class->envelope = smime_envelope; - camel_cms_context_class->decode = smime_decode; + context->priv->sign_mode = type; } -CamelType -camel_smime_context_get_type (void) +static const char * +sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash) { - static CamelType type = CAMEL_INVALID_TYPE; - - if (type == CAMEL_INVALID_TYPE) { - type = camel_type_register (camel_cms_context_get_type (), - "CamelSMimeContext", - sizeof (CamelSMimeContext), - sizeof (CamelSMimeContextClass), - (CamelObjectClassInitFunc) camel_smime_context_class_init, - NULL, - (CamelObjectInitFunc) camel_smime_context_init, - (CamelObjectFinalizeFunc) camel_smime_context_finalise); + switch(hash) { + case CAMEL_CIPHER_HASH_MD5: + return "md5"; + case CAMEL_CIPHER_HASH_SHA1: + case CAMEL_CIPHER_HASH_DEFAULT: + return "sha1"; + default: + return NULL; } - - return type; } - -/** - * camel_smime_context_new: - * @session: CamelSession - * @encryption_key: preferred encryption key (used when attaching cert chains to messages) - * - * This creates a new CamelSMimeContext object which is used to sign, - * verify, encrypt and decrypt streams. - * - * Return value: the new CamelSMimeContext - **/ -CamelSMimeContext * -camel_smime_context_new (CamelSession *session, const char *encryption_key) +static CamelCipherHash +sm_id_to_hash(CamelCipherContext *context, const char *id) { - CamelSMimeContext *context; - CERTCertDBHandle *certdb; - - g_return_val_if_fail (session != NULL, NULL); - g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); - - certdb = CERT_GetDefaultCertDB (); - if (!certdb) - return NULL; - - context = CAMEL_SMIME_CONTEXT (camel_object_new (CAMEL_SMIME_CONTEXT_TYPE)); - - camel_cms_context_construct (CAMEL_CMS_CONTEXT (context), session); - - context->encryption_key = g_strdup (encryption_key); - context->priv->certdb = certdb; + if (id) { + if (!strcmp(id, "md5")) + return CAMEL_CIPHER_HASH_MD5; + else if (!strcmp(id, "sha1")) + return CAMEL_CIPHER_HASH_SHA1; + } - return context; + return CAMEL_CIPHER_HASH_DEFAULT; } - -struct _GetPasswdData { - CamelSession *session; - const char *userid; - CamelException *ex; -}; - -static char * -smime_get_password (PK11SlotInfo *info, PRBool retry, void *arg) +/* used for decode content callback, for streaming decode */ +static void +sm_write_stream(void *arg, const char *buf, unsigned long len) { - CamelSession *session = ((struct _GetPasswdData *)arg)->session; - const char *userid = ((struct _GetPasswdData *)arg)->userid; - CamelException *ex = ((struct _GetPasswdData *)arg)->ex; - char *prompt, *passwd, *ret; - - prompt = g_strdup_printf (_("Please enter your password for %s"), userid); - passwd = camel_session_get_password (session, prompt, FALSE, TRUE, - NULL, userid, ex); - g_free (prompt); - - ret = PL_strdup (passwd); - g_free (passwd); - - return ret; + camel_stream_write((CamelStream *)arg, buf, len); } static PK11SymKey * -decode_key_cb (void *arg, SECAlgorithmID *algid) +sm_decrypt_key(void *arg, SECAlgorithmID *algid) { + printf("Decrypt key called\n"); return (PK11SymKey *)arg; } +static char * +sm_get_passwd(PK11SlotInfo *info, PRBool retry, void *arg) +{ + printf("Password requested for '%s'\n", PK11_GetTokenName(info)); + + return NULL; +} static NSSCMSMessage * -signed_data (CamelSMimeContext *ctx, const char *userid, gboolean signing_time, - gboolean detached, CamelException *ex) +sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex) { + struct _CamelSMIMEContextPrivate *p = context->priv; NSSCMSMessage *cmsg = NULL; NSSCMSContentInfo *cinfo; NSSCMSSignedData *sigd; NSSCMSSignerInfo *signerinfo; - CERTCertificate *cert, *ekpcert; - - if (!userid) { - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, - _("Please indicate the nickname of a certificate to sign with.")); + CERTCertificate *cert= NULL, *ekpcert = NULL; + + /* TODO: usage should be hardcoded to usageSigner? */ + + if ((cert = CERT_FindUserCertByUsage(p->certdb, + (char *)nick, + certUsageEmailSigner, + PR_FALSE, + NULL)) == NULL) { + camel_exception_setv(ex, 1, "Cann't find certificate for '%s'", nick); return NULL; } - - if ((cert = CERT_FindCertByNickname (ctx->priv->certdb, (char *) userid)) == NULL) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("The signature certificate for \"%s\" does not exist."), - userid); - return NULL; + + cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */ + if (cmsg == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS message"); + goto fail; } - - /* create the cms message object */ - cmsg = NSS_CMSMessage_Create (NULL); - - /* build chain of objects: message->signedData->data */ - sigd = NSS_CMSSignedData_Create (cmsg); - - cinfo = NSS_CMSMessage_GetContentInfo (cmsg); - NSS_CMSContentInfo_SetContent_SignedData (cmsg, cinfo, sigd); - - cinfo = NSS_CMSSignedData_GetContentInfo (sigd); - - /* speciffy whether we want detached signatures or not */ - NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, detached); - - /* create & attach signer information */ - signerinfo = NSS_CMSSignerInfo_Create (cmsg, cert, SEC_OID_SHA1); - - /* include the cert chain */ - NSS_CMSSignerInfo_IncludeCerts (signerinfo, NSSCMSCM_CertChain, - certUsageEmailSigner); - - if (signing_time) { - NSS_CMSSignerInfo_AddSigningTime (signerinfo, PR_Now ()); + + if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS signedData"); + goto fail; } - - if (TRUE) { - /* Add S/MIME Capabilities */ - NSS_CMSSignerInfo_AddSMIMECaps (signerinfo); + + cinfo = NSS_CMSMessage_GetContentInfo(cmsg); + if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't attach CMS signedData"); + goto fail; } - - if (ctx->encryption_key) { - /* get the cert, add it to the message */ - ekpcert = CERT_FindCertByNickname (ctx->priv->certdb, ctx->encryption_key); - if (!ekpcert) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("The encryption certificate for \"%s\" does not exist."), - ctx->encryption_key); - goto exception; - } - - NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs (signerinfo, ekpcert, ctx->priv->certdb); - - NSS_CMSSignedData_AddCertificate (sigd, ekpcert); - } else { - /* check signing cert for fitness as encryption cert */ - /* if yes, add signing cert as EncryptionKeyPreference */ - NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs (signerinfo, cert, ctx->priv->certdb); + + /* if !detatched, the contentinfo will alloc a data item for us */ + cinfo = NSS_CMSSignedData_GetContentInfo(sigd); + if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, detached) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't attach CMS data"); + goto fail; } - - NSS_CMSSignedData_AddSignerInfo (sigd, signerinfo); - - return cmsg; - - exception: - - NSS_CMSMessage_Destroy (cmsg); - - return NULL; -} -static void -smime_sign_restore (CamelMimePart *mime_part, GSList **encodings) -{ - CamelDataWrapper *wrapper; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); - if (!wrapper) - return; - - if (CAMEL_IS_MULTIPART (wrapper)) { - int parts, i; - - parts = camel_multipart_get_number (CAMEL_MULTIPART (wrapper)); - for (i = 0; i < parts; i++) { - CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (wrapper), i); - - smime_sign_restore (part, encodings); - *encodings = (*encodings)->next; - } - } else { - CamelTransferEncoding encoding; - - if (CAMEL_IS_MIME_MESSAGE (wrapper)) { - /* restore the message parts' subparts */ - smime_sign_restore (CAMEL_MIME_PART (wrapper), encodings); - } else { - encoding = GPOINTER_TO_INT ((*encodings)->data); - - camel_mime_part_set_encoding (mime_part, encoding); - } + signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash); + if (signerinfo == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS SignerInfo"); + goto fail; } -} -static void -smime_sign_prepare (CamelMimePart *mime_part, GSList **encodings) -{ - CamelDataWrapper *wrapper; - int parts, i; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); - if (!wrapper) - return; - - if (CAMEL_IS_MULTIPART (wrapper)) { - parts = camel_multipart_get_number (CAMEL_MULTIPART (wrapper)); - for (i = 0; i < parts; i++) { - CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (wrapper), i); - - smime_sign_prepare (part, encodings); - } - } else { - CamelTransferEncoding encoding; - - if (CAMEL_IS_MIME_MESSAGE (wrapper)) { - /* prepare the message parts' subparts */ - smime_sign_prepare (CAMEL_MIME_PART (wrapper), encodings); - } else { - encoding = camel_mime_part_get_encoding (mime_part); - - /* FIXME: find the best encoding for this part and use that instead?? */ - /* the encoding should really be QP or Base64 */ - if (encoding != CAMEL_TRANSFER_ENCODING_BASE64) - camel_mime_part_set_encoding (mime_part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE); - - *encodings = g_slist_append (*encodings, GINT_TO_POINTER (encoding)); - } + /* we want the cert chain included for this one */ + if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't find cert chain"); + goto fail; } -} + /* SMIME RFC says signing time should always be added */ + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't add CMS SigningTime"); + goto fail; + } -static CamelMimeMessage * -smime_sign (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, gboolean signing_time, - gboolean detached, CamelException *ex) -{ - CamelMimeMessage *mesg = NULL; - NSSCMSMessage *cmsg = NULL; - struct _GetPasswdData *data; - PLArenaPool *arena; - NSSCMSEncoderContext *ecx; - SECItem output = { 0, 0, 0 }; - CamelStream *stream; - GSList *list, *encodings = NULL; - GByteArray *buf; - - cmsg = signed_data (CAMEL_SMIME_CONTEXT (ctx), userid, signing_time, detached, ex); - if (!cmsg) - return NULL; - - arena = PORT_NewArena (1024); - data = g_new (struct _GetPasswdData, 1); - data->session = ctx->session; - data->userid = userid; - data->ex = ex; - ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena, - smime_get_password, data, NULL, NULL, - NULL, NULL); - - stream = camel_stream_mem_new (); - - smime_sign_prepare (CAMEL_MIME_PART (message), &encodings); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - list = encodings; - smime_sign_restore (CAMEL_MIME_PART (message), &list); - g_slist_free (encodings); - - buf = CAMEL_STREAM_MEM (stream)->buffer; - - NSS_CMSEncoder_Update (ecx, buf->data, buf->len); - NSS_CMSEncoder_Finish (ecx); - - camel_object_unref (stream); - g_free (data); - - /* write the result to a camel stream */ - stream = camel_stream_mem_new (); - camel_stream_write (stream, output.data, output.len); - PORT_FreeArena (arena, PR_FALSE); - - NSS_CMSMessage_Destroy (cmsg); - - /* parse the stream into a new CamelMimeMessage */ - mesg = camel_mime_message_new (); - camel_stream_reset (stream); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream); - camel_object_unref (stream); - - return mesg; -} +#if 0 + /* this can but needn't be added. not sure what general usage is */ + if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) { + fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n"); + goto loser; + } +#endif + /* Check if we need to send along our return encrypt cert, rfc2633 2.5.3 */ + if (p->send_encrypt_key_prefs) { + CERTCertificate *enccert = NULL; + + if (p->encrypt_key) { + /* encrypt key has its own nick */ + if ((ekpcert = CERT_FindUserCertByUsage( + p->certdb, + p->encrypt_key, + certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) { + camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", p->encrypt_key); + goto fail; + } + enccert = ekpcert; + } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) { + /* encrypt key is signing key */ + enccert = cert; + } else { + /* encrypt key uses same nick */ + if ((ekpcert = CERT_FindUserCertByUsage( + p->certdb, (char *)nick, + certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) { + camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", nick); + goto fail; + } + enccert = ekpcert; + } -static NSSCMSMessage * -certsonly_data (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex) -{ - NSSCMSMessage *cmsg = NULL; - NSSCMSContentInfo *cinfo; - NSSCMSSignedData *sigd; - CERTCertificate **rcerts; - int i = 0; - - /* find the signer's and the recipients' certs */ - rcerts = g_new (CERTCertificate *, recipients->len + 2); - rcerts[0] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, (char *) userid); - if (!rcerts[0]) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to find certificate for \"%s\"."), - recipients->pdata[i]); - goto exception; - } - - for (i = 0; i < recipients->len; i++) { - rcerts[i + 1] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, - recipients->pdata[i]); - - if (!rcerts[i + 1]) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to find certificate for \"%s\"."), - recipients->pdata[i]); - goto exception; + if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) { + camel_exception_setv(ex, 1, "can't add SMIMEEncKeyPrefs attribute"); + goto fail; + } + + if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) { + camel_exception_setv(ex, 1, "can't add MS SMIMEEncKeyPrefs attribute"); + goto fail; + } + + if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) { + camel_exception_setv(ex, 1, "can't add add encryption certificate"); + goto fail; } } - rcerts[i + 1] = NULL; - - /* create the cms message object */ - cmsg = NSS_CMSMessage_Create (NULL); - - sigd = NSS_CMSSignedData_CreateCertsOnly (cmsg, rcerts[0], PR_TRUE); - - /* add the recipient cert chain */ - for (i = 0; i < recipients->len; i++) { - NSS_CMSSignedData_AddCertChain (sigd, rcerts[i]); + + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) { + camel_exception_setv(ex, 1, "can't add CMS SignerInfo"); + goto fail; } - - cinfo = NSS_CMSMessage_GetContentInfo (cmsg); - NSS_CMSContentInfo_SetContent_SignedData (cmsg, cinfo, sigd); - - cinfo = NSS_CMSSignedData_GetContentInfo (sigd); - NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE); - - g_free (rcerts); - + + if (ekpcert) + CERT_DestroyCertificate(ekpcert); + + if (cert) + CERT_DestroyCertificate(cert); + return cmsg; - - exception: - - NSS_CMSMessage_Destroy (cmsg); - - g_free (rcerts); - +fail: + if (ekpcert) + CERT_DestroyCertificate(ekpcert); + + if (cert) + CERT_DestroyCertificate(cert); + + NSS_CMSMessage_Destroy(cmsg); + return NULL; } -static CamelMimeMessage * -smime_certsonly (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex) +static int +sm_encode_cmsmessage(CamelSMIMEContext *context, NSSCMSMessage *cmsg, CamelStream *instream, CamelStream *out, CamelException *ex) { - CamelMimeMessage *mesg = NULL; - struct _GetPasswdData *data; - NSSCMSMessage *cmsg = NULL; - PLArenaPool *arena; - NSSCMSEncoderContext *ecx; - SECItem output = { 0, 0, 0 }; - CamelStream *stream; - GByteArray *buf; - - cmsg = certsonly_data (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex); - if (!cmsg) - return NULL; - - arena = PORT_NewArena (1024); - data = g_new (struct _GetPasswdData, 1); - data->session = ctx->session; - data->userid = userid; - data->ex = ex; - ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena, - smime_get_password, data, NULL, NULL, - NULL, NULL); - - stream = camel_stream_mem_new (); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - buf = CAMEL_STREAM_MEM (stream)->buffer; - - NSS_CMSEncoder_Update (ecx, buf->data, buf->len); - NSS_CMSEncoder_Finish (ecx); - - camel_object_unref (stream); - g_free (data); - - /* write the result to a camel stream */ - stream = camel_stream_mem_new (); - camel_stream_write (stream, output.data, output.len); - PORT_FreeArena (arena, PR_FALSE); - - NSS_CMSMessage_Destroy (cmsg); - - /* parse the stream into a new CamelMimeMessage */ - mesg = camel_mime_message_new (); - camel_stream_reset (stream); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream); - camel_object_unref (stream); - - return mesg; -} + NSSCMSEncoderContext *enc; + CamelStreamMem *mem = NULL; + + enc = NSS_CMSEncoder_Start(cmsg, + sm_write_stream, out, /* DER output callback */ + NULL, NULL, /* destination storage */ + sm_get_passwd, context, /* password callback */ + NULL, NULL, /* decrypt key callback */ + NULL, NULL ); /* detached digests */ + if (!enc) { + camel_exception_setv(ex, 1, "Cannot create encoder context"); + goto fail; + } + /* Note: see rfc2015 or rfc3156, section 5 */ -static NSSCMSMessage * -enveloped_data (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex) -{ - NSSCMSMessage *cmsg = NULL; - NSSCMSContentInfo *cinfo; - NSSCMSEnvelopedData *envd; - NSSCMSRecipientInfo *rinfo; - CERTCertificate **rcerts; - SECOidTag bulkalgtag; - int keysize, i; - - /* find the recipient certs by email address or nickname */ - rcerts = g_new (CERTCertificate *, recipients->len + 2); - rcerts[0] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, (char *) userid); - if (!rcerts[0]) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to find certificate for \"%s\"."), - userid); - goto exception; - } - - for (i = 0; i < recipients->len; i++) { - rcerts[i + 1] = CERT_FindCertByNicknameOrEmailAddr (ctx->priv->certdb, - recipients->pdata[i]); - if (!rcerts[i + 1]) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to find certificate for \"%s\"."), - recipients->pdata[i]); - goto exception; - } - } - rcerts[i + 1] = NULL; - - /* find a nice bulk algorithm */ - if (!NSS_SMIMEUtil_FindBulkAlgForRecipients (rcerts, &bulkalgtag, &keysize)) { - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to find a common bulk algorithm.")); - goto exception; + /* FIXME: stream this, we stream output at least */ + mem = (CamelStreamMem *)camel_stream_mem_new(); + camel_stream_write_to_stream(instream, (CamelStream *)mem); + + if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) { + NSS_CMSEncoder_Cancel(enc); + camel_exception_setv(ex, 1, "Failed to add data to CMS encoder"); + goto fail; } - - /* create a cms message object */ - cmsg = NSS_CMSMessage_Create (NULL); - - envd = NSS_CMSEnvelopedData_Create (cmsg, bulkalgtag, keysize); - cinfo = NSS_CMSMessage_GetContentInfo (cmsg); - NSS_CMSContentInfo_SetContent_EnvelopedData (cmsg, cinfo, envd); - - cinfo = NSS_CMSEnvelopedData_GetContentInfo (envd); - NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE); - - /* create & attach recipient information */ - for (i = 0; rcerts[i] != NULL; i++) { - rinfo = NSS_CMSRecipientInfo_Create (cmsg, rcerts[i]); - NSS_CMSEnvelopedData_AddRecipient (envd, rinfo); + + if (NSS_CMSEncoder_Finish(enc) != SECSuccess) { + camel_exception_setv(ex, 1, "Failed to encode data"); + goto fail; } - - g_free (rcerts); - - return cmsg; - - exception: - - NSS_CMSMessage_Destroy (cmsg); - - g_free (rcerts); - - return NULL; -} -static CamelMimeMessage * -smime_envelope (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex) -{ - CamelMimeMessage *mesg = NULL; - struct _GetPasswdData *data; - NSSCMSMessage *cmsg = NULL; - PLArenaPool *arena; - NSSCMSEncoderContext *ecx; - SECItem output = { 0, 0, 0 }; - CamelStream *stream; - GByteArray *buf; - - cmsg = enveloped_data (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex); - if (!cmsg) - return NULL; - - arena = PORT_NewArena (1024); - data = g_new (struct _GetPasswdData, 1); - data->session = ctx->session; - data->userid = userid; - data->ex = ex; - ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena, - smime_get_password, data, NULL, NULL, - NULL, NULL); - - stream = camel_stream_mem_new (); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - buf = CAMEL_STREAM_MEM (stream)->buffer; - - NSS_CMSEncoder_Update (ecx, buf->data, buf->len); - NSS_CMSEncoder_Finish (ecx); - - camel_object_unref (stream); - g_free (data); - - /* write the result to a camel stream */ - stream = camel_stream_mem_new (); - camel_stream_write (stream, output.data, output.len); - PORT_FreeArena (arena, PR_FALSE); - - NSS_CMSMessage_Destroy (cmsg); - - /* parse the stream into a new CamelMimeMessage */ - mesg = camel_mime_message_new (); - camel_stream_reset (stream); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream); - camel_object_unref (stream); - - return mesg; -} + camel_object_unref(mem); + return 0; -struct _BulkKey { - PK11SymKey *bulkkey; - SECOidTag bulkalgtag; - int keysize; -}; +fail: + if (mem) + camel_object_unref(mem); -static NSSCMSMessage * -encrypted_data (CamelSMimeContext *ctx, GByteArray *input, struct _BulkKey *key, - CamelStream *ostream, CamelException *ex) -{ - NSSCMSMessage *cmsg = NULL; - NSSCMSContentInfo *cinfo; - NSSCMSEncryptedData *encd; - NSSCMSEncoderContext *ecx = NULL; - PLArenaPool *arena = NULL; - SECItem output = { 0, 0, 0 }; - - /* arena for output */ - arena = PORT_NewArena (1024); - - /* create cms message object */ - cmsg = NSS_CMSMessage_Create (NULL); - - encd = NSS_CMSEncryptedData_Create (cmsg, key->bulkalgtag, key->keysize); - - cinfo = NSS_CMSMessage_GetContentInfo (cmsg); - NSS_CMSContentInfo_SetContent_EncryptedData (cmsg, cinfo, encd); - - cinfo = NSS_CMSEncryptedData_GetContentInfo (encd); - NSS_CMSContentInfo_SetContent_Data (cmsg, cinfo, NULL, PR_FALSE); - - ecx = NSS_CMSEncoder_Start (cmsg, NULL, NULL, &output, arena, NULL, NULL, - decode_key_cb, key->bulkkey, NULL, NULL); - - NSS_CMSEncoder_Update (ecx, input->data, input->len); - - NSS_CMSEncoder_Finish (ecx); - - camel_stream_write (ostream, output.data, output.len); - - if (arena) - PORT_FreeArena (arena, PR_FALSE); - - return cmsg; + return -1; } -static struct _BulkKey * -get_bulkkey (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, CamelException *ex) +static int +sm_sign(CamelCipherContext *ctx, const char *userid, CamelCipherHash hash, CamelStream *istream, CamelMimePart *sigpart, CamelException *ex) { - struct _BulkKey *bulkkey = NULL; - NSSCMSMessage *env_cmsg; - NSSCMSContentInfo *cinfo; - SECItem dummyOut = { 0, 0, 0 }; - SECItem dummyIn = { 0, 0, 0 }; - char str[] = "You are not a beautiful and unique snowflake."; - PLArenaPool *arena; - int i, nlevels; - - /* construct an enveloped data message to obtain bulk keys */ - arena = PORT_NewArena (1024); - dummyIn.data = (unsigned char *)str; - dummyIn.len = strlen (str); - - env_cmsg = enveloped_data (ctx, userid, recipients, ex); - NSS_CMSDEREncode (env_cmsg, &dummyIn, &dummyOut, arena); - /*camel_stream_write (envstream, dummyOut.data, dummyOut.len);*/ - PORT_FreeArena (arena, PR_FALSE); - - /* get the content info for the enveloped data */ - nlevels = NSS_CMSMessage_ContentLevelCount (env_cmsg); - for (i = 0; i < nlevels; i++) { - SECOidTag typetag; - - cinfo = NSS_CMSMessage_ContentLevel (env_cmsg, i); - typetag = NSS_CMSContentInfo_GetContentTypeTag (cinfo); - if (typetag == SEC_OID_PKCS7_DATA) { - bulkkey = g_new (struct _BulkKey, 1); - - /* get the symmertic key */ - bulkkey->bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag (cinfo); - bulkkey->keysize = NSS_CMSContentInfo_GetBulkKeySize (cinfo); - bulkkey->bulkkey = NSS_CMSContentInfo_GetBulkKey (cinfo); - - return bulkkey; + int res; + NSSCMSMessage *cmsg; + CamelStream *ostream; + SECOidTag sechash; + + switch (hash) { + case CAMEL_CIPHER_HASH_SHA1: + case CAMEL_CIPHER_HASH_DEFAULT: + default: + sechash = SEC_OID_SHA1; + break; + case CAMEL_CIPHER_HASH_MD5: + sechash = SEC_OID_MD5; + break; + } + + cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)ctx, userid, sechash, + ((CamelSMIMEContext *)ctx)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex); + if (cmsg == NULL) + return -1; + + ostream = camel_stream_mem_new(); + res = sm_encode_cmsmessage((CamelSMIMEContext *)ctx, cmsg, istream, ostream, ex); + NSS_CMSMessage_Destroy(cmsg); + + if (res == 0) { + CamelDataWrapper *dw; + CamelContentType *ct; + + dw = camel_data_wrapper_new(); + camel_stream_reset(ostream); + camel_data_wrapper_construct_from_stream(dw, ostream); + dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY; + + if (((CamelSMIMEContext *)ctx)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) { + ct = camel_content_type_new("application", "x-pkcs7-signature"); + camel_content_type_set_param(ct, "name", "smime.p7s"); + camel_data_wrapper_set_mime_type_field(dw, ct); + camel_content_type_unref(ct); + + camel_mime_part_set_filename(sigpart, "smime.p7s"); + } else { + ct = camel_content_type_new("application", "x-pkcs7-mime"); + camel_content_type_set_param(ct, "name", "smime.p7m"); + camel_content_type_set_param(ct, "smime-type", "signed-data"); + camel_data_wrapper_set_mime_type_field(dw, ct); + camel_content_type_unref(ct); + + camel_mime_part_set_filename(sigpart, "smime.p7m"); + camel_mime_part_set_description(sigpart, "S/MIME Signed Message"); } + + camel_mime_part_set_disposition(sigpart, "attachment"); + camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64); + + camel_medium_set_content_object((CamelMedium *)sigpart, dw); + camel_object_unref(dw); } - - return NULL; + + + camel_object_unref(ostream); + + return res; } -static CamelMimeMessage * -smime_encrypt (CamelCMSContext *ctx, CamelMimeMessage *message, - const char *userid, GPtrArray *recipients, - CamelException *ex) +static const char * +sm_status_description(NSSCMSVerificationStatus status) { - struct _BulkKey *bulkkey = NULL; - CamelMimeMessage *mesg = NULL; - NSSCMSMessage *cmsg = NULL; - CamelStream *stream; - GByteArray *buf; - - bulkkey = get_bulkkey (CAMEL_SMIME_CONTEXT (ctx), userid, recipients, ex); - if (!bulkkey) - return NULL; - - buf = g_byte_array_new (); - stream = camel_stream_mem_new (); - camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buf); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - camel_object_unref (stream); - - stream = camel_stream_mem_new (); - cmsg = encrypted_data (CAMEL_SMIME_CONTEXT (ctx), buf, bulkkey, stream, ex); - g_byte_array_free (buf, TRUE); - g_free (bulkkey); - if (!cmsg) { - camel_object_unref (stream); - return NULL; + /* could use this but then we can't control i18n? */ + /*NSS_CMSUtil_VerificationStatusToString(status));*/ + + switch(status) { + case NSSCMSVS_Unverified: + default: + return _("Unverified"); + case NSSCMSVS_GoodSignature: + return _("Good signature"); + case NSSCMSVS_BadSignature: + return _("Bad signature"); + case NSSCMSVS_DigestMismatch: + return _("Content tampered with or altered in transit"); + case NSSCMSVS_SigningCertNotFound: + return _("Signing certificate not found"); + case NSSCMSVS_SigningCertNotTrusted: + return _("Signing certificate not trusted"); + case NSSCMSVS_SignatureAlgorithmUnknown: + return _("Signature algorithm unknown"); + case NSSCMSVS_SignatureAlgorithmUnsupported: + return _("Siganture algorithm unsupported"); + case NSSCMSVS_MalformedSignature: + return _("Malformed signature"); + case NSSCMSVS_ProcessingError: + return _("Processing error"); } - - NSS_CMSMessage_Destroy (cmsg); - - /* parse the stream into a new CamelMimeMessage */ - mesg = camel_mime_message_new (); - camel_stream_reset (stream); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), stream); - camel_object_unref (stream); - - return mesg; } - -static NSSCMSMessage * -decode_data (CamelSMimeContext *ctx, GByteArray *input, CamelStream *ostream, - CamelCMSValidityInfo **info, CamelException *ex) +static CamelCipherValidity * +sm_verify(CamelCipherContext *context, CamelCipherHash hash, CamelStream *istream, CamelMimePart *sigpart, CamelException *ex) { - NSSCMSDecoderContext *dcx; - struct _GetPasswdData *data; - CamelCMSValidityInfo *vinfo = NULL; - NSSCMSMessage *cmsg = NULL; - NSSCMSContentInfo *cinfo; + struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv; + NSSCMSDecoderContext *dec; + NSSCMSMessage *cmsg; NSSCMSSignedData *sigd = NULL; NSSCMSEnvelopedData *envd; NSSCMSEncryptedData *encd; - int nlevels, i, nsigners, j; - char *signercn; - NSSCMSSignerInfo *si; - SECOidTag typetag; - SECItem *item; - - data = g_new (struct _GetPasswdData, 1); - data->session = CAMEL_CMS_CONTEXT (ctx)->session; - data->userid = NULL; - data->ex = ex; - - dcx = NSS_CMSDecoder_Start (NULL, - NULL, NULL, - smime_get_password, data, - decode_key_cb, - NULL); - - NSS_CMSDecoder_Update (dcx, input->data, input->len); - - cmsg = NSS_CMSDecoder_Finish (dcx); - g_free (data); + SECAlgorithmID **digestalgs; + NSSCMSDigestContext *digcx; + int count, i, nsigners, j; + SECItem **digests; + PLArenaPool *poolp = NULL; + CamelStreamMem *mem; + NSSCMSVerificationStatus status; + CamelCipherValidity *valid; + GString *description; + + dec = NSS_CMSDecoder_Start(NULL, + NULL, NULL, /* content callback */ + sm_get_passwd, context, /* password callback */ + NULL, NULL); /* decrypt key callback */ + + /* FIXME: Stream? not worth it? sigs are small */ + mem = (CamelStreamMem *)camel_stream_mem_new(); + camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)sigpart), (CamelStream *)mem); + (void)NSS_CMSDecoder_Update(dec, mem->buffer->data, mem->buffer->len); + camel_object_unref(mem); + cmsg = NSS_CMSDecoder_Finish(dec); if (cmsg == NULL) { - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to decode message.")); + camel_exception_setv(ex, 1, "Decoder failed"); return NULL; } - - nlevels = NSS_CMSMessage_ContentLevelCount (cmsg); - for (i = 0; i < nlevels; i++) { - CamelCMSSigner *signers = NULL; - - cinfo = NSS_CMSMessage_ContentLevel (cmsg, i); - typetag = NSS_CMSContentInfo_GetContentTypeTag (cinfo); - - if (info && !vinfo) { - vinfo = g_new0 (CamelCMSValidityInfo, 1); - *info = vinfo; - } else if (vinfo) { - vinfo->next = g_new0 (CamelCMSValidityInfo, 1); - vinfo = vinfo->next; - } - + + description = g_string_new(""); + valid = camel_cipher_validity_new(); + camel_cipher_validity_set_valid(valid, TRUE); + status = NSSCMSVS_Unverified; + + /* NB: this probably needs to go into a decoding routine that can be used for processing + enveloped data too */ + count = NSS_CMSMessage_ContentLevelCount(cmsg); + for (i = 0; i < count; i++) { + NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg, i); + SECOidTag typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + switch (typetag) { case SEC_OID_PKCS7_SIGNED_DATA: - if (vinfo) - vinfo->type = CAMEL_CMS_TYPE_SIGNED; - - sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent (cinfo); - - /* import the certificates */ - NSS_CMSSignedData_ImportCerts (sigd, ctx->priv->certdb, - certUsageEmailSigner, PR_FALSE); - - /* find out about signers */ - nsigners = NSS_CMSSignedData_SignerInfoCount (sigd); - - if (nsigners == 0) { - /* must be a cert transport message */ - SECStatus retval; - - /* XXX workaround for bug #54014 */ - NSS_CMSSignedData_ImportCerts (sigd, ctx->priv->certdb, - certUsageEmailSigner, PR_TRUE); - - retval = NSS_CMSSignedData_VerifyCertsOnly (sigd, ctx->priv->certdb, - certUsageEmailSigner); - if (retval != SECSuccess) { - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to verify certificates.")); - goto exception; + sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (sigd == NULL) { + camel_exception_setv(ex, 1, "No signedData in signature"); + goto fail; + } + + /* need to build digests of the content */ + if (!NSS_CMSSignedData_HasDigests(sigd)) { + if ((poolp = PORT_NewArena(1024)) == NULL) { + camel_exception_setv(ex, 1, "out of memory"); + goto fail; } + + digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd); - return cmsg; + digcx = NSS_CMSDigestContext_StartMultiple(digestalgs); + if (digcx == NULL) { + camel_exception_setv(ex, 1, "Cannot calculate digests"); + goto fail; + } + + mem = (CamelStreamMem *)camel_stream_mem_new(); + camel_stream_write_to_stream(istream, (CamelStream *)mem); + NSS_CMSDigestContext_Update(digcx, mem->buffer->data, mem->buffer->len); + camel_object_unref(mem); + + if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) { + camel_exception_setv(ex, 1, "Can not calculate digests"); + goto fail; + } + + if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) { + camel_exception_setv(ex, 1, "Cannot set message digests"); + goto fail; + } + + PORT_FreeArena(poolp, PR_FALSE); + poolp = NULL; + } + + /* import the certificates */ + if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_FALSE) != SECSuccess) { + camel_exception_setv(ex, 1, "cert import failed"); + goto fail; } - - for (j = 0; vinfo && j < nsigners; j++) { - if (!signers) { - signers = g_new0 (CamelCMSSigner, 1); - vinfo->signers = signers; + + /* check for certs-only message */ + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + if (nsigners == 0) { + /* ?? Should we check other usages? */ + NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_TRUE); + if (NSS_CMSSignedData_VerifyCertsOnly(sigd, p->certdb, certUsageEmailSigner) != SECSuccess) { + g_string_printf(description, "Certficate only message, cannot verify certificates"); } else { - signers->next = g_new0 (CamelCMSSigner, 1); - signers = signers->next; + status = NSSCMSVS_GoodSignature; + g_string_printf(description, "Certficate only message, certificates imported and verified"); } - - si = NSS_CMSSignedData_GetSignerInfo (sigd, j); - signercn = NSS_CMSSignerInfo_GetSignerCommonName (si); - if (signercn == NULL) - signercn = ""; - - NSS_CMSSignedData_VerifySignerInfo (sigd, j, ctx->priv->certdb, - certUsageEmailSigner); - - if (signers) { - signers->signercn = g_strdup (signercn); - signers->status = g_strdup ( - NSS_CMSUtil_VerificationStatusToString ( - NSS_CMSSignerInfo_GetVerificationStatus (si))); + } else { + + if (!NSS_CMSSignedData_HasDigests(sigd)) { + camel_exception_setv(ex, 1, "Can't find signature digests"); + goto fail; + } + + for (j = 0; j < nsigners; j++) { + NSSCMSSignerInfo *si; + char *cn, *em; + + si = NSS_CMSSignedData_GetSignerInfo(sigd, j); + NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner); + + status = NSS_CMSSignerInfo_GetVerificationStatus(si); + + cn = NSS_CMSSignerInfo_GetSignerCommonName(si); + em = NSS_CMSSignerInfo_GetSignerEmailAddress(si); + + g_string_append_printf(description, _("Signer: %s <%s>: %s\n"), + cn?cn:"<unknown>", em?em:"<unknown>", + sm_status_description(status)); + + if (cn) + PORT_Free(cn); + if (em) + PORT_Free(em); + + if (status != NSSCMSVS_GoodSignature) + camel_cipher_validity_set_valid(valid, FALSE); } } break; case SEC_OID_PKCS7_ENVELOPED_DATA: - if (vinfo) - vinfo->type = CAMEL_CMS_TYPE_ENVELOPED; - - envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent (cinfo); + envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo); + /* do we need to look into the enveloped data for signatures too?? */ break; case SEC_OID_PKCS7_ENCRYPTED_DATA: - if (vinfo) - vinfo->type = CAMEL_CMS_TYPE_ENCRYPTED; - - encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent (cinfo); + encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo); break; case SEC_OID_PKCS7_DATA: break; @@ -902,100 +589,330 @@ decode_data (CamelSMimeContext *ctx, GByteArray *input, CamelStream *ostream, break; } } - - item = NSS_CMSMessage_GetContent (cmsg); - camel_stream_write (ostream, item->data, item->len); - - return cmsg; - - exception: - - if (info) - camel_cms_validity_info_free (*info); - - if (cmsg) - NSS_CMSMessage_Destroy (cmsg); - + + camel_cipher_validity_set_valid(valid, status == NSSCMSVS_GoodSignature); + camel_cipher_validity_set_description(valid, description->str); + g_string_free(description, TRUE); + + NSS_CMSMessage_Destroy(cmsg); + return valid; + +fail: + NSS_CMSMessage_Destroy(cmsg); + camel_cipher_validity_free(valid); + g_string_free(description, TRUE); + + if (poolp) + PORT_FreeArena(poolp, PR_FALSE); + return NULL; } - -static CamelMimeMessage * -smime_decode (CamelCMSContext *ctx, CamelMimeMessage *message, - CamelCMSValidityInfo **info, CamelException *ex) +static int +sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex) { - CamelMimeMessage *mesg = NULL; + struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv; + /*NSSCMSRecipientInfo **recipient_infos;*/ + CERTCertificate **recipient_certs = NULL; + NSSCMSContentInfo *cinfo; + PK11SymKey *bulkkey = NULL; + SECOidTag bulkalgtag; + int bulkkeysize, i; + CK_MECHANISM_TYPE type; + PK11SlotInfo *slot; + PLArenaPool *poolp; NSSCMSMessage *cmsg = NULL; - CamelStream *stream, *ostream; - GByteArray *buf; - - stream = camel_stream_mem_new (); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - buf = CAMEL_STREAM_MEM (stream)->buffer; - - ostream = camel_stream_mem_new (); - cmsg = decode_data (CAMEL_SMIME_CONTEXT (ctx), buf, ostream, info, ex); - camel_object_unref (stream); - if (!cmsg) { - camel_object_unref (ostream); - return NULL; + NSSCMSEnvelopedData *envd; + NSSCMSEncoderContext *enc = NULL; + CamelStreamMem *mem; + CamelStream *ostream = NULL; + CamelDataWrapper *dw; + CamelContentType *ct; + + poolp = PORT_NewArena(1024); + if (poolp == NULL) { + camel_exception_setv(ex, 1, "Out of memory"); + return -1; } + + /* Lookup all recipients certs, for later working */ + recipient_certs = (CERTCertificate **)PORT_ArenaZAlloc(poolp, sizeof(*recipient_certs[0])*(recipients->len + 1)); + if (recipient_certs == NULL) { + camel_exception_setv(ex, 1, "Out of memory"); + goto fail; + } + + for (i=0;i<recipients->len;i++) { + recipient_certs[i] = CERT_FindCertByNicknameOrEmailAddr(p->certdb, recipients->pdata[i]); + if (recipient_certs[i] == NULL) { + camel_exception_setv(ex, 1, "Can't find certificate for `%s'", recipients->pdata[i]); + goto fail; + } + } + + /* Find a common algorithm, probably 3DES anyway ... */ + if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't find common bulk encryption algorithm"); + goto fail; + } + + /* Generate a new bulk key based on the common algorithm - expensive */ + type = PK11_AlgtagToMechanism(bulkalgtag); + slot = PK11_GetBestSlot(type, context); + if (slot == NULL) { + /* PORT_GetError(); ?? */ + camel_exception_setv(ex, 1, "Can't allocate slot for encryption bulk key"); + goto fail; + } + + bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context); + PK11_FreeSlot(slot); + + /* Now we can start building the message */ + /* msg->envelopedData->data */ + cmsg = NSS_CMSMessage_Create(NULL); + if (cmsg == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS Message"); + goto fail; + } + + envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize); + if (envd == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS EnvelopedData"); + goto fail; + } + + cinfo = NSS_CMSMessage_GetContentInfo(cmsg); + if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't attach CMS EnvelopedData"); + goto fail; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd); + if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't attach CMS data object"); + goto fail; + } + + /* add recipient certs */ + for (i=0;recipient_certs[i];i++) { + NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]); + + if (ri == NULL) { + camel_exception_setv(ex, 1, "Can't create CMS RecipientInfo"); + goto fail; + } + + if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) { + camel_exception_setv(ex, 1, "Can't add CMS RecipientInfo"); + goto fail; + } + } + + /* dump it out */ + ostream = camel_stream_mem_new(); + enc = NSS_CMSEncoder_Start(cmsg, + sm_write_stream, ostream, + NULL, NULL, + sm_get_passwd, context, + sm_decrypt_key, bulkkey, + NULL, NULL); + if (enc == NULL) { + camel_exception_setv(ex, 1, "Can't create encoder context"); + goto fail; + } + + /* FIXME: Stream the input */ + /* FIXME: Canonicalise the input? */ + mem = (CamelStreamMem *)camel_stream_mem_new(); + camel_data_wrapper_write_to_stream((CamelDataWrapper *)ipart, (CamelStream *)mem); + if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) { + NSS_CMSEncoder_Cancel(enc); + camel_object_unref(mem); + camel_exception_setv(ex, 1, "Failed to add data to encoder"); + goto fail; + } + camel_object_unref(mem); + + if (NSS_CMSEncoder_Finish(enc) != SECSuccess) { + camel_exception_setv(ex, 1, "Failed to encode data"); + goto fail; + } + + PK11_FreeSymKey(bulkkey); + NSS_CMSMessage_Destroy(cmsg); + for (i=0;recipient_certs[i];i++) + CERT_DestroyCertificate(recipient_certs[i]); + PORT_FreeArena(poolp, PR_FALSE); - /* construct a new mime message from the stream */ - mesg = camel_mime_message_new (); - camel_stream_reset (ostream); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (mesg), ostream); - camel_object_unref (ostream); - - return mesg; + dw = camel_data_wrapper_new(); + camel_data_wrapper_construct_from_stream(dw, ostream); + camel_object_unref(ostream); + dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY; + + ct = camel_content_type_new("application", "x-pkcs7-mime"); + camel_content_type_set_param(ct, "name", "smime.p7m"); + camel_content_type_set_param(ct, "smime-type", "enveloped-data"); + camel_data_wrapper_set_mime_type_field(dw, ct); + camel_content_type_unref(ct); + + camel_medium_set_content_object((CamelMedium *)opart, dw); + camel_object_unref(dw); + + camel_mime_part_set_disposition(opart, "attachment"); + camel_mime_part_set_filename(opart, "smime.p7m"); + camel_mime_part_set_description(opart, "S/MIME Encrypted Message"); + camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64); + + return 0; + +fail: + if (ostream) + camel_object_unref(ostream); + if (cmsg) + NSS_CMSMessage_Destroy(cmsg); + if (bulkkey) + PK11_FreeSymKey(bulkkey); + + if (recipient_certs) { + for (i=0;recipient_certs[i];i++) + CERT_DestroyCertificate(recipient_certs[i]); + } + + PORT_FreeArena(poolp, PR_FALSE); + + return -1; } +static CamelMimePart * +sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex) +{ + NSSCMSDecoderContext *dec; + NSSCMSMessage *cmsg; + CamelStreamMem *istream; + CamelStream *ostream; + CamelMimePart *opart = NULL; + + /* FIXME: This assumes the content is only encrypted. Perhaps its ok for + this api to do this ... */ + + ostream = camel_stream_mem_new(); + + /* FIXME: stream this to the decoder incrementally */ + istream = (CamelStreamMem *)camel_stream_mem_new(); + camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)ipart), (CamelStream *)istream); + camel_stream_reset((CamelStream *)istream); + + dec = NSS_CMSDecoder_Start(NULL, + sm_write_stream, ostream, /* content callback */ + sm_get_passwd, context, /* password callback */ + NULL, NULL); /* decrypt key callback */ + + if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) { + printf("decoder update failed\n"); + } + camel_object_unref(istream); + + cmsg = NSS_CMSDecoder_Finish(dec); + if (cmsg == NULL) { + camel_exception_setv(ex, 1, "Decoder failed, error %d", PORT_GetError()); + goto fail; + } + #if 0 + /* not sure if we really care about this? */ + if (!NSS_CMSMessage_IsEncrypted(cmsg)) { + camel_exception_setv(ex, 1, "S/MIME Decrypt: No encrypted content found"); + NSS_CMSMessage_Destroy(cmsg); + goto fail; + } +#endif -/* Ugh, so smime context inherets from cms context, not cipher context - this needs to be fixed ... */ + NSS_CMSMessage_Destroy(cmsg); -/* this has a 1:1 relationship to CamelCipherHash */ -static char **name_table[] = { - "sha1", /* we use sha1 as the 'default' */ - NULL, - "md5", - "sha1", - NULL, -}; + opart = camel_mime_part_new(); + camel_stream_reset(ostream); + camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream); +fail: + camel_object_unref(ostream); + + return opart; +} + +static int +sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex) +{ + camel_exception_setv(ex, 1, "import keys: unimplemented"); + + return -1; +} -static const char *smime_hash_to_id(CamelCipherContext *context, CamelCipherHash hash) +static int +sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex) { - /* if we dont know, just use default? */ - if (hash > sizeof(name_table)/sizeof(name_table[0]) - || name_table[hash] == NULL; - hash = CAMEL_CIPHER_HASH_DEFAULT; + camel_exception_setv(ex, 1, "export keys: unimplemented"); - return name_table[hash]; + return -1; } -static CamelCipherHash smime_id_to_hash(CamelCipherContext *context, const char *id) +/* ********************************************************************** */ + +static void +camel_smime_context_class_init(CamelSMIMEContextClass *klass) { - int i; - unsigned char *tmpid, *o, *in; - unsigned char c; + CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass); + + parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type())); + + cipher_class->hash_to_id = sm_hash_to_id; + cipher_class->id_to_hash = sm_id_to_hash; + cipher_class->sign = sm_sign; + cipher_class->verify = sm_verify; + cipher_class->encrypt = sm_encrypt; + cipher_class->decrypt = sm_decrypt; + cipher_class->import_keys = sm_import_keys; + cipher_class->export_keys = sm_export_keys; +} - if (id == NULL) - return CAMEL_CIPHER_HASH_DEFAULT; +static void +camel_smime_context_init(CamelSMIMEContext *context) +{ + CamelCipherContext *cipher =(CamelCipherContext *) context; + + cipher->sign_protocol = "application/x-pkcs7-signature"; + cipher->encrypt_protocol = "application/x-pkcs7-mime"; + cipher->key_protocol = "application/x-pkcs7-signature"; - tmpid = alloca(strlen(id)+1); - in = id; - o = tmpid; - while ((c = *in++)) - *o++ = tolower(c); + context->priv = g_malloc0(sizeof(*context->priv)); + context->priv->certdb = CERT_GetDefaultCertDB(); + context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN; +} - for (i=1;i<sizeof(name_table)/sizeof(name_table[0]);i++) { - if (!strcmp(name_table[i], tmpid)) - return i; - } +static void +camel_smime_context_finalise(CamelObject *object) +{ + CamelSMIMEContext *context = (CamelSMIMEContext *)object; - return CAMEL_CIPHER_HASH_DEFAULT; + /* FIXME: do we have to free the certdb? */ + + g_free(context->priv); } -#endif -#endif /* HAVE_NSS */ +CamelType +camel_smime_context_get_type(void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register(camel_cipher_context_get_type(), + "CamelSMIMEContext", + sizeof(CamelSMIMEContext), + sizeof(CamelSMIMEContextClass), + (CamelObjectClassInitFunc) camel_smime_context_class_init, + NULL, + (CamelObjectInitFunc) camel_smime_context_init, + (CamelObjectFinalizeFunc) camel_smime_context_finalise); + } + + return type; +} |