/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright 2001 Ximian, Inc. (www.ximian.com)
*
* 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.
*
* 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.
*
*/
#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 <gtk/gtk.h> /* for _() macro */
#define d(x)
struct _CamelSMimeContextPrivate {
CERTCertDBHandle *certdb;
};
static CamelMimeMessage *smime_sign (CamelCMSContext *ctx, CamelMimeMessage *message,
const char *userid, gboolean signing_time,
gboolean detached, CamelException *ex);
static CamelMimeMessage *smime_certsonly (CamelCMSContext *ctx, CamelMimeMessage *message,
const char *userid, GPtrArray *recipients,
CamelException *ex);
static CamelMimeMessage *smime_encrypt (CamelCMSContext *ctx, CamelMimeMessage *message,
const char *userid, GPtrArray *recipients,
CamelException *ex);
static CamelMimeMessage *smime_envelope (CamelCMSContext *ctx, CamelMimeMessage *message,
const char *userid, GPtrArray *recipients,
CamelException *ex);
static CamelMimeMessage *smime_decode (CamelCMSContext *ctx, CamelMimeMessage *message,
CamelCMSValidityInfo **info, CamelException *ex);
static CamelCMSContextClass *parent_class;
static void
camel_smime_context_init (CamelSMimeContext *context)
{
context->priv = g_new0 (struct _CamelSMimeContextPrivate, 1);
}
static void
camel_smime_context_finalise (CamelObject *o)
{
CamelSMimeContext *context = (CamelSMimeContext *)o;
g_free (context->encryption_key);
g_free (context->priv);
}
static void
camel_smime_context_class_init (CamelSMimeContextClass *camel_smime_context_class)
{
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;
}
CamelType
camel_smime_context_get_type (void)
{
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);
}
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)
{
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;
return context;
}
struct _GetPasswdData {
CamelSession *session;
const char *userid;
CamelException *ex;
};
static char *
smime_get_password (PK11SlotInfo *info, PRBool retry, void *arg)
{
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, TRUE,
NULL, userid, ex);
g_free (prompt);
ret = PL_strdup (passwd);
g_free (passwd);
return ret;
}
static PK11SymKey *
decode_key_cb (void *arg, SECAlgorithmID *algid)
{
return (PK11SymKey *)arg;
}
static NSSCMSMessage *
signed_data (CamelSMimeContext *ctx, const char *userid, gboolean signing_time,
gboolean detached, CamelException *ex)
{
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."));
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;
}
/* 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 (TRUE) {
/* Add S/MIME Capabilities */
NSS_CMSSignerInfo_AddSMIMECaps (signerinfo);
}
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);
}
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 {
CamelMimePartEncodingType 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);
}
}
}
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 {
CamelMimePartEncodingType 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_MIME_PART_ENCODING_BASE64)
camel_mime_part_set_encoding (mime_part, CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE);
*encodings = g_slist_append (*encodings, GINT_TO_POINTER (encoding));
}
}
}
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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (stream));
return mesg;
}
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;
}
}
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]);
}
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);
return cmsg;
exception:
NSS_CMSMessage_Destroy (cmsg);
g_free (rcerts);
return NULL;
}
static CamelMimeMessage *
smime_certsonly (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 = 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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (stream));
return mesg;
}
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;
}
/* 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);
}
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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (stream));
return mesg;
}
struct _BulkKey {
PK11SymKey *bulkkey;
SECOidTag bulkalgtag;
int keysize;
};
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;
}
static struct _BulkKey *
get_bulkkey (CamelSMimeContext *ctx, const char *userid, GPtrArray *recipients, 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;
}
}
return NULL;
}
static CamelMimeMessage *
smime_encrypt (CamelCMSContext *ctx, CamelMimeMessage *message,
const char *userid, GPtrArray *recipients,
CamelException *ex)
{
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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (stream));
return NULL;
}
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 (CAMEL_OBJECT (stream));
return mesg;
}
static NSSCMSMessage *
decode_data (CamelSMimeContext *ctx, GByteArray *input, CamelStream *ostream,
CamelCMSValidityInfo **info, CamelException *ex)
{
NSSCMSDecoderContext *dcx;
struct _GetPasswdData *data;
CamelCMSValidityInfo *vinfo = NULL;
NSSCMSMessage *cmsg = NULL;
NSSCMSContentInfo *cinfo;
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);
if (cmsg == NULL) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to decode message."));
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;
}
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;
}
return cmsg;
}
for (j = 0; vinfo && j < nsigners; j++) {
if (!signers) {
signers = g_new0 (CamelCMSSigner, 1);
vinfo->signers = signers;
} else {
signers->next = g_new0 (CamelCMSSigner, 1);
signers = signers->next;
}
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)));
}
}
break;
case SEC_OID_PKCS7_ENVELOPED_DATA:
if (vinfo)
vinfo->type = CAMEL_CMS_TYPE_ENVELOPED;
envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent (cinfo);
break;
case SEC_OID_PKCS7_ENCRYPTED_DATA:
if (vinfo)
vinfo->type = CAMEL_CMS_TYPE_ENCRYPTED;
encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent (cinfo);
break;
case SEC_OID_PKCS7_DATA:
break;
default:
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);
return NULL;
}
static CamelMimeMessage *
smime_decode (CamelCMSContext *ctx, CamelMimeMessage *message,
CamelCMSValidityInfo **info, CamelException *ex)
{
CamelMimeMessage *mesg = NULL;
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 (CAMEL_OBJECT (stream));
if (!cmsg) {
camel_object_unref (CAMEL_OBJECT (ostream));
return NULL;
}
/* 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 (CAMEL_OBJECT (ostream));
return mesg;
}
#endif /* HAVE_NSS */