aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-smime-context.c
blob: 9fad5a1f113eaa5294bfcd4c284877923a88279b (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>
 *           Michael Zucchi <notzed@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.
 *
 *  Copyright 2003 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

#include "nss.h"
#include <cms.h>
#include <cert.h>
#include <certdb.h>
#include <pkcs11.h>
#include <smime.h>
#include <pkcs11t.h>
#include <pk11func.h>

#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>

#include "camel-smime-context.h"
#include "camel-operation.h"

#define d(x)

struct _CamelSMIMEContextPrivate {
    CERTCertDBHandle *certdb;

    char *encrypt_key;
    camel_smime_sign_t sign_mode;

    unsigned int send_encrypt_key_prefs:1;
};

static CamelCipherContextClass *parent_class = NULL;

/**
 * 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)
{
    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;
}

void
camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
{
    context->priv->send_encrypt_key_prefs = use;
    g_free(context->priv->encrypt_key);
    context->priv->encrypt_key = g_strdup(key);
}

/* set signing mode, clearsigned multipart/signed or enveloped */
void
camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
{
    context->priv->sign_mode = type;
}

static const char *
sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
{
    switch(hash) {
    case CAMEL_CIPHER_HASH_MD5:
        return "md5";
    case CAMEL_CIPHER_HASH_SHA1:
    case CAMEL_CIPHER_HASH_DEFAULT:
        return "sha1";
    default:
        return NULL;
    }
}

static CamelCipherHash
sm_id_to_hash(CamelCipherContext *context, const char *id)
{
    if (id) {
        if (!strcmp(id, "md5"))
            return CAMEL_CIPHER_HASH_MD5;
        else if (!strcmp(id, "sha1"))
            return CAMEL_CIPHER_HASH_SHA1;
    }
    
    return CAMEL_CIPHER_HASH_DEFAULT;
}

/* used for decode content callback, for streaming decode */
static void
sm_write_stream(void *arg, const char *buf, unsigned long len)
{
    camel_stream_write((CamelStream *)arg, buf, len);
}

static PK11SymKey *
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)
{
    CamelSMIMEContext *context = arg;
    char *pass, *nsspass = NULL;
    char *prompt;
    CamelException *ex;

    ex = camel_exception_new();
    prompt = g_strdup_printf(_("Enter security pass-phrase for `%s'"), PK11_GetTokenName(info));
    pass = camel_session_get_password(((CamelCipherContext *)context)->session, prompt, FALSE, TRUE, NULL, PK11_GetTokenName(info), ex);
    camel_exception_free(ex);
    g_free(prompt);
    if (pass) {
        nsspass = PORT_Strdup(pass);
        g_free(pass);
    }
    
    return nsspass;
}

static NSSCMSMessage *
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= 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;
    }

    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;
    }

    if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
        camel_exception_setv(ex, 1, "Can't create CMS signedData");
        goto fail;
    }

    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 !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;
    }

    signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
    if (signerinfo == NULL) {
        camel_exception_setv(ex, 1, "Can't create CMS SignerInfo");
        goto fail;
    }

    /* 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;
    }

#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;
        }

        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;
        }
    }

    if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
        camel_exception_setv(ex, 1, "can't add CMS SignerInfo");
        goto fail;
    }

    if (ekpcert)
        CERT_DestroyCertificate(ekpcert);

    if (cert)
        CERT_DestroyCertificate(cert);

    return cmsg;
fail:
    if (ekpcert)
        CERT_DestroyCertificate(ekpcert);

    if (cert)
        CERT_DestroyCertificate(cert);

    NSS_CMSMessage_Destroy(cmsg);

    return NULL;
}

static int
sm_encode_cmsmessage(CamelSMIMEContext *context, NSSCMSMessage *cmsg, CamelStream *instream, CamelStream *out, CamelException *ex)
{
    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 */

    /* 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;
    }

    if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
        camel_exception_setv(ex, 1, "Failed to encode data");
        goto fail;
    }

    camel_object_unref(mem);

    return 0;

fail:
    if (mem)
        camel_object_unref(mem);

    return -1;
}

static int
sm_sign(CamelCipherContext *ctx, const char *userid, CamelCipherHash hash, CamelStream *istream, CamelMimePart *sigpart, CamelException *ex)
{
    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);
    }


    camel_object_unref(ostream);

    return res;
}

static const char *
sm_status_description(NSSCMSVerificationStatus status)
{
    /* 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");
    }
}

static CamelCipherValidity *
sm_verify(CamelCipherContext *context, CamelCipherHash hash, CamelStream *istream, CamelMimePart *sigpart, CamelException *ex)
{
    struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
    NSSCMSDecoderContext *dec;
    NSSCMSMessage *cmsg;
    NSSCMSSignedData *sigd = NULL;
    NSSCMSEnvelopedData *envd;
    NSSCMSEncryptedData *encd;
    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_setv(ex, 1, "Decoder failed");
        return NULL;
    }

    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:
            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);
                
                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;
            }

            /* 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 {
                    status = NSSCMSVS_GoodSignature;
                    g_string_printf(description, "Certficate only message, certificates imported and verified");
                }
            } 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:
            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:
            encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
            break;
        case SEC_OID_PKCS7_DATA:
            break;
        default:
            break;
        }
    }

    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 int
sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
{
    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;
    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);
    
    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

    NSS_CMSMessage_Destroy(cmsg);

    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 int
sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
{
    camel_exception_setv(ex, 1, "export keys: unimplemented");

    return -1;
}

/* ********************************************************************** */

static void
camel_smime_context_class_init(CamelSMIMEContextClass *klass)
{
    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;
}

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";

    context->priv = g_malloc0(sizeof(*context->priv));
    context->priv->certdb = CERT_GetDefaultCertDB();
    context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
}

static void
camel_smime_context_finalise(CamelObject *object)
{
    CamelSMIMEContext *context = (CamelSMIMEContext *)object;

    /* FIXME: do we have to free the certdb? */

    g_free(context->priv);
}

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;
}