aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-smime-context.c
blob: 9234b189886ac0962b3d1bc9ebb8c5495f0389a2 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                                                           


                                                                   
  



                                                                    
  



                                                               






                    
               

                                


                                   

                             

                            
 
                
                



                   









                                        













                                                                                          
                                                              
 

                                                                                           
 
                                          











                                                                     
                                         





                                                                                  

                                                                    
        
                                                                                                                  
        




                                                             







                                                   
                                                                          















                                                                                                      
                                                                                          






                                                                     
                                                                           

                                   
                                 

                                                     
                                                                
        



                                          

                                                                                    
                                                                           
        

                                                            



                       


                              
                           
                           

  

                                                                
 



                                                                        
        



                                                                                  
        

                                 
        






                                                

 



                                                                               
 










                                                                                                       
        
                                                                                            










                                                                                                
        

                                                                      
        













                                                                         

         


                                                            

         








                                                                                                         
                
                                                                                               
                




                                                                                            

         
                                                           
        






                                      

 
           
                                                                 
 
                                  
        


                                                                             
        







                                                                                                      
                                                        



                                                   







                                                                                  
         


           
                                                                 
 
















                                                                                                      












                                                                                                                   
         

 




                                                            
 





                                      
                            
                                        
                        
        











                                                                                           

                                         


                                                                                  

                                                              

                                 
                                                
        

                                                         

                                                   
                      
        



                                                             
        
                                      
        




                                                                                     
        




                      
                                                                                                      




                                   
                  


                                                                
                                                                                            
                         
                                                                 

                                                                                  


                               









                                                                                          
         
                             
        






                                                                            
                                                                 

         

                                                                     
        





                                                                         
        

           
                                      
        
                        
        
                    

 



                                                                 
 
                                      
                                    



                                     
                            
                        
        


                                                                                  
        







                                                                         
        


                                                                                  
        




                                                         
        



                                                             
        
                                      
        




                                                                                     
        
                    

 


                                                                                                      
 









                                                                   
                                                                                            



                                                                                  


                               
                                               





                                                                                          

                                       
         

























                                                                                      
        
















                                                                
                                    











                                                                                  
                                                
                                     

                              


                                                                         
        


                                                                                  
        

                                                         
        

                                                   
        
                                                
                                         



                                                             
        



                                                                                     
                                                   
        




                    

                             












                                                                                
        

                                     
        

                                            
        
                                                                                 
        

                                                                        
        

                                                                         
        

                                                                                 
        
                                                             
        
                                    
        
                                                              
        



                                                 

 

                                                                                                   
 






































                                                                                             

 



                                                               
 



                                        

                        


                                                                                  
        
                                  
                                         

























                                                                                     

                                                                             








                                           



                                    
                      

                                                
                                                         


                            



                                                             
                                          
        
                                                             
        
                                           
                      




                                                                     
        

































































                                                                                                     
                                                                                






















                                                                                                       

         

                                                            
        
                    


           























                                                                                  
                                                                               










                                                                                      
        
                    
 

                     
/* -*- 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 version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Place - Suite 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 */