aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-pkcs7-context.c
blob: 185ae09ccc3f856a4e96607d7c831b835a891f71 (plain) (tree)






























                                                                           
                



























































































































                                                                                                                        








































                                                                                  

                                                                   





















































































                                                                                                          
                                                             






                                              




                                                                              



                                    
                              



                              
        


                                                   
        




                                                       
        

























                                                                                             
                                                                                  





                                            







                                                                                   

                                    
                                
                              
                            
                          
                        
        

































                                                                                                  
                                                                                  





                                            

 


                                                                             











                                       

























                                                                               






                                                                                  
                           










                                                                     
                                                                                       





                                             







                                                                 
















                                                   

                                                                                                               
                
                                                                                 



                                            


                                                           


                     
                                                      




                                                                                                 






























                                                                                     










                                                                                           






























































                                                                                                    



                  




                                                           





                                                               













































                                                                                


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

#include "camel-pkcs7-context.h"

#include "camel-stream-fs.h"
#include "camel-stream-mem.h"

#include <nss.h>
#include <cert.h>
#include <secpkcs7.h>

#include <gtk/gtk.h> /* for _() macro */

#define d(x)

struct _CamelPkcs7ContextPrivate {
    CERTCertDBHandle *certdb;
};


static int                  pkcs7_sign (CamelCipherContext *ctx, const char *userid, CamelCipherHash hash,
                    CamelStream *istream, CamelStream *ostream, CamelException *ex);
static int                  pkcs7_clearsign (CamelCipherContext *context, const char *userid,
                         CamelCipherHash hash, CamelStream *istream,
                         CamelStream *ostream, CamelException *ex);
static CamelCipherValidity *pkcs7_verify (CamelCipherContext *context, CamelStream *istream,
                      CamelStream *sigstream, CamelException *ex);
static int                  pkcs7_encrypt (CamelCipherContext *context, gboolean sign, const char *userid,
                       GPtrArray *recipients, CamelStream *istream, CamelStream *ostream,
                       CamelException *ex);
static int                  pkcs7_decrypt (CamelCipherContext *context, CamelStream *istream,
                       CamelStream *ostream, CamelException *ex);


static CamelCipherContextClass *parent_class;

static void
camel_pkcs7_context_init (CamelPkcs7Context *context)
{
    context->priv = g_new0 (struct _CamelPkcs7ContextPrivate, 1);
}

static void
camel_pkcs7_context_finalise (CamelObject *o)
{
    CamelPkcs7Context *context = (CamelPkcs7Context *)o;
    
    CERT_ClosePermCertDB (context->priv->certdb);
    g_free (context->priv->certdb);
    
    g_free (context->priv);
}

static void
camel_pkcs7_context_class_init (CamelPkcs7ContextClass *camel_pkcs7_context_class)
{
    CamelCipherContextClass *camel_cipher_context_class =
        CAMEL_CIPHER_CONTEXT_CLASS (camel_pkcs7_context_class);
    
    parent_class = CAMEL_CIPHER_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cipher_context_get_type ()));
    
    camel_cipher_context_class->sign = pkcs7_sign;
    camel_cipher_context_class->clearsign = pkcs7_clearsign;
    camel_cipher_context_class->verify = pkcs7_verify;
    camel_cipher_context_class->encrypt = pkcs7_encrypt;
    camel_cipher_context_class->decrypt = pkcs7_decrypt;
}

CamelType
camel_pkcs7_context_get_type (void)
{
    static CamelType type = CAMEL_INVALID_TYPE;
    
    if (type == CAMEL_INVALID_TYPE) {
        type = camel_type_register (camel_cipher_context_get_type (),
                        "CamelPkcs7Context",
                        sizeof (CamelPkcs7Context),
                        sizeof (CamelPkcs7ContextClass),
                        (CamelObjectClassInitFunc) camel_pkcs7_context_class_init,
                        NULL,
                        (CamelObjectInitFunc) camel_pkcs7_context_init,
                        (CamelObjectFinalizeFunc) camel_pkcs7_context_finalise);
    }
    
    return type;
}


/**
 * camel_pkcs7_context_new:
 * @session: CamelSession
 * @certdb: certificate db
 *
 * This creates a new CamelPkcs7Context object which is used to sign,
 * verify, encrypt and decrypt streams.
 *
 * Return value: the new CamelPkcs7Context
 **/
CamelPkcs7Context *
camel_pkcs7_context_new (CamelSession *session, const char *certdb)
{
    CamelPkcs7Context *context;
    CERTCertDBHandle *handle;
    
    g_return_val_if_fail (session != NULL, NULL);
    
    context = CAMEL_PKCS7_CONTEXT (camel_object_new (CAMEL_PKCS7_CONTEXT_TYPE));
    
    camel_cipher_construct (CAMEL_CIPHER_CONTEXT (context), session);
    
    handle = g_new0 (CERTCertDBHandle, 1);
    if (certdb) {
        if (!CERT_OpenCertDBFilename (handle, certdb, FALSE)) {
            g_free (handle);
            return NULL;
        }
    } else {
        if (!CERT_OpenVolatileCertDB (handle)) {
            g_free (handle);
            return NULL;
        }
    }
    
    context->priv->certdb = handle;
    
    return context;
}

/*----------------------------------------------------------------------*
 *                     Public crypto functions
 *----------------------------------------------------------------------*/

struct _GetPasswdData {
    CamelSession *session;
    CamelException *ex;
    const char *userid;
};

static SECItem *
get_zero_len_passwd (SECKEYKeyDBHandle *handle)
{
    SECItem *pwitem;
    SECStatus rv;
    
    /* hash the empty string as a password */
    pwitem = SECKEY_DeriveKeyDBPassword (handle, "");
    if (pwitem == NULL)
        return NULL;
    
    /* check to see if this is the right password */
    rv = SECKEY_CheckKeyDBPassword (handle, pwitem);
    if (rv == SECFailure)
        return NULL;
    
    return pwitem;
}

static SECItem *
get_password (void *arg, SECKEYKeyDBHandle *handle)
{
    CamelSession *session = ((struct _GetPasswdData *) arg)->session;
    CamelException *ex = ((struct _GetPasswdData *) arg)->ex;
    const char *userid = ((struct _GetPasswdData *) arg)->userid;
    char *prompt, *passwd = NULL;
    SECItem *pwitem;
    SECStatus rv;
    
    /* Check to see if zero length password or not */
    pwitem = get_zero_len_passwd (handle);
    if (pwitem)
        return pwitem;
    
    prompt = g_strdup_printf (_("Please enter your password for %s"), userid);
    passwd = camel_session_get_password (session, prompt, TRUE,
                         NULL, userid, NULL);
    g_free (prompt);
    
    /* hash the password */
    pwitem = SECKEY_DeriveKeyDBPassword (handle, passwd ? passwd : "");
    
    /* clear out the password strings */
    if (passwd) {
        memset (passwd, 0, strlen (passwd));
        g_free (passwd);
    }
    
    if (pwitem == NULL) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Error hashing password."));
        
        return NULL;
    }
    
    /* confirm the password */
    rv = SECKEY_CheckKeyDBPassword (handle, pwitem);
    if (rv) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Invalid password."));
        
        SECITEM_ZfreeItem (pwitem, PR_TRUE);
        
        return NULL;
    }
    
    return pwitem;
}

static HASH_HashType
camel_cipher_hash_to_nss (CamelCipherHash hash)
{
    switch (hash) {
    case CAMEL_CIPHER_HASH_DEFAULT:
        return HASH_AlgSHA1;
    case CAMEL_CIPHER_HASH_MD2:
        return HASH_AlgMD2;
    case CAMEL_CIPHER_HASH_MD5:
        return HASH_AlgMD5;
    case CAMEL_CIPHER_HASH_SHA1:
        return HASH_AlgSHA1;
    }
    
    return HASH_AlgNULL;
}

static SECOidTag
nss_hash_to_sec_oid (HASH_HashType hash)
{
    switch (hash) {
    case HASH_AlgMD2:
        return SEC_OID_MD2;
    case HASH_AlgMD5:
        return SEC_OID_MD5;
    case Hash_AlgSHA1:
        return SEC_OID_SHA1;
    default:
        g_assert_not_reached ();
        return 0;
    }
}

static int
pkcs7_digest (SECItem *data, char *digestdata, unsigned int *len, unsigned int maxlen, HASH_HashType hash)
{
    SECHashObject *hashObj;
    void *hashcx;
    
    hashObj = &SECHashObjects[hash];
    
    hashcx = (* hashObj->create)();
    if (hashcx == NULL)
        return -1;
    
    (* hashObj->begin)(hashcx);
    (* hashObj->update)(hashcx, data->data, data->len);
    (* hashObj->end)(hashcx, (unsigned char *)digestdata, len, maxlen);
    (* hashObj->destroy)(hashcx, PR_TRUE);
    
    return 0;
}

static void
sec_output_cb (void *arg, const char *buf, unsigned long len)
{
    CamelStream *stream;
    
    stream = CAMEL_STREAM (arg);
    camel_stream_write (stream, buf, len);
}

static int
pkcs7_sign (CamelCipherContext *ctx, const char *userid, CamelCipherHash hash,
        CamelStream *istream, CamelStream *ostream, CamelException *ex)
{
    CamelPkcs7Context *context = CAMEL_PKCS7_CONTEXT (ctx);
    struct _GetPasswdData *data;
    SEC_PKCS7ContentInfo *cinfo;
    SECItem data2sign, digest;
    HASH_HashType hash_type;
    CERTCertificate *cert;
    guchar digestdata[32];
    CamelStream *stream;
    GByteArray *buf;
    guint len;
    
    g_return_val_if_fail (userid != NULL, -1);
    g_return_val_if_fail (istream != NULL, -1);
    g_return_val_if_fail (ostream != NULL, -1);
    
    stream = camel_stream_mem_new ();
    camel_stream_write_to_stream (istream, stream);
    buf = CAMEL_STREAM_MEM (stream)->buffer;
    data2sign.data = buf->data;
    data2sign.len = buf->len;
    
    hash_type = camel_cipher_hash_to_nss (hash);
    pkcs7_digest (&data2sign, digestdata, &len, 32, hash_type);
    digest.data = (unsigned char *)digestdata;
    digest.len = len;
    
    camel_object_unref (CAMEL_OBJECT (stream));
    
    cert = CERT_FindCertByNickname (context->priv->certdb, userid);
    if (!cert) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not sign: certificate not found for \"%s\"."),
                      userid);
        return -1;
    }
    
    cinfo = SEC_PKCS7CreateSignedData (cert, certUsageEmailSigner, NULL,
                       nss_hash_to_sec_oid (hash_type),
                       &digest, NULL, NULL);
    
    SEC_PKCS7IncludeCertChain (cinfo, NULL);
    
    data = g_new (struct _GetPasswdData, 1);
    data->session = ctx->session;
    data->userid = userid;
    data->ex = ex;
    
    SEC_PKCS7Encode (cinfo, sec_output_cb, ostream, NULL, get_password, data);
    
    g_free (data);
    
    SEC_PKCS7DestroyContentInfo (cinfo);
    
    return 0;
}


static int
pkcs7_clearsign (CamelCipherContext *ctx, const char *userid, CamelCipherHash hash,
         CamelStream *istream, CamelStream *ostream, CamelException *ex)
{
    CamelPkcs7Context *context = CAMEL_PKCS7_CONTEXT (ctx);
    struct _GetPasswdData *data;
    SEC_PKCS7ContentInfo *cinfo;
    HASH_HashType hash_type;
    CERTCertificate *cert;
    CamelStream *stream;
    SECItem data2sign;
    GByteArray *buf;
    
    g_return_val_if_fail (userid != NULL, -1);
    g_return_val_if_fail (istream != NULL, -1);
    g_return_val_if_fail (ostream != NULL, -1);
    
    hash_type = camel_cipher_hash_to_nss (hash);
    
    cert = CERT_FindCertByNickname (context->priv->certdb, userid);
    if (!cert) {
        camel_object_unref (CAMEL_OBJECT (stream));
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not clearsign: certificate not found for \"%s\"."),
                      userid);
        return -1;
    }
    
    cinfo = SEC_PKCS7CreateSignedData (cert, certUsageEmailSigner, NULL,
                       nss_hash_to_sec_oid (hash_type),
                       NULL, NULL, NULL);
    
    stream = camel_stream_mem_new ();
    camel_stream_write_to_stream (istream, stream);
    buf = CAMEL_STREAM_MEM (stream)->buffer;
    data2sign.data = buf->data;
    data2sign.len = buf->len;
    SEC_PKCS7SetContent (cinfo, (char *)data2sign.data, data2sign.len);
    camel_object_unref (CAMEL_OBJECT (stream));
    
    SEC_PKCS7IncludeCertChain (cinfo, NULL);
    
    data = g_new (struct _GetPasswdData, 1);
    data->session = ctx->session;
    data->userid = userid;
    data->ex = ex;
    
    SEC_PKCS7Encode (cinfo, sec_output_cb, ostream, NULL, get_password, data);
    
    g_free (data);
    
    SEC_PKCS7DestroyContentInfo (cinfo);
    
    return 0;
}

#if 0
/* this is just meant as a reference so I can see what the valid enums are */
typedef enum {
    certUsageSSLClient,
    certUsageSSLServer,
    certUsageSSLServerWithStepUp,
    certUsageSSLCA,
    certUsageEmailSigner,
    certUsageEmailRecipient,
    certUsageObjectSigner,
    certUsageUserCertImport,
    certUsageVerifyCA,
    certUsageProtectedObjectSigner,
    certUsageStatusResponder,
    certUsageAnyCA
} SECCertUsage;
#endif

#if 0
static HASH_HashType
AlgorithmToHashType (SECAlgorithmID *digestAlgorithms)
{
    SECOidTag tag;
    
    tag = SECOID_GetAlgorithmTag (digestAlgorithms);
    
    switch (tag) {
    case SEC_OID_MD2:
        return HASH_AlgMD2;
    case SEC_OID_MD5:
        return HASH_AlgMD5;
    case SEC_OID_SHA1:
        return HASH_AlgSHA1;
    default:
        g_assert_not_reached ();
        return HASH_AlgNULL;
    }
}
#endif

/* FIXME: god knows if this code works, NSS "docs" are so not helpful at all */
static CamelCipherValidity *
pkcs7_verify (CamelCipherContext *ctx, CamelCipherHash hash, CamelStream *istream,
          CamelStream *sigstream, CamelException *ex)
{
    CamelPkcs7Context *context = CAMEL_PKCS7_CONTEXT (ctx);
    CamelCipherValidity *valid = NULL;
    SEC_PKCS7ContentInfo *cinfo;
    SECCertUsage usage;
    GByteArray *plaintext;
    CamelStream *stream;
    
    /* create our ContentInfo object */
    stream = camel_stream_mem_new ();
    camel_stream_write_to_stream (istream, stream);
    plaintext = CAMEL_STREAM_MEM (stream)->buffer;
    cinfo = SEC_PKCS7CreateData ();
    SEC_PKCS7SetContent (cinfo, plaintext->data, plaintext->len);
    camel_object_unref (CAMEL_OBJECT (stream));
    
    usage = certUsageEmailSigner;  /* just a guess. or maybe certUsageVerifyCA?? */
    
    valid = camel_cipher_validity_new ();
    
    if (sigstream) {
        HASH_HashType digest_type;
        GByteArray *signature;
        SECItem digest;
        
        /* create our digest object */
        stream = camel_stream_mem_new ();
        camel_stream_write_to_stream (sigstream, stream);
        signature = CAMEL_STREAM_MEM (stream)->buffer;
        digest.data = signature->data;
        digest.len = signature->len;
        
        switch (hash) {
        default:
        case CAMEL_CIPHER_HASH_DEFAULT:
            digest_type = HASH_AlgNULL;
            break;
        case CAMEL_CIPHER_HASH_MD2:
            digest_type = HASH_AlgMD2;
            break;
        case CAMEL_CIPHER_HASH_MD5:
            digest_type = HASH_AlgMD5;
            break;
        case CAMEL_CIPHER_HASH_SHA1:
            digest_type = HASH_AlgSHA1;
            break;
        }
        
        valid->valid = SEC_PKCS7VerifyDetachedSignature (cinfo, usage, &digest, digest_type, PR_FALSE);
        camel_object_unref (CAMEL_OBJECT (stream));
    } else {
        valid->valid = SEC_PKCS7VerifySignature (cinfo, usage, PR_FALSE);
    }
    
    SEC_PKCS7DestroyContentInfo (cinfo);
    
    /* FIXME: set a meaningful description...in UTF8 */
    camel_cipher_validity_set_description (valid, "");
    
    return valid;
}

/* FIXME: we need to respect the 'sign' argument... */
static int
pkcs7_encrypt (CamelCipherContext *ctx, gboolean sign, const char *userid, GPtrArray *recipients,
           CamelStream *istream, CamelStream *ostream, CamelException *ex)
{
    CamelPkcs7Context *context = CAMEL_PKCS7_CONTEXT (ctx);
    const char *invalid_userkey = NULL;
    SEC_PKCS7ContentInfo *cinfo = NULL;
    CERTCertificate *cert, *usercert;
    SEC_PKCS7EncoderContext *ecx;
    struct _GetPasswdData *data;
    CamelStream *stream = NULL;
    SECItem secdata;
    GByteArray *buf;
    int i = 0;
    
    g_return_val_if_fail (userid != NULL, -1);
    g_return_val_if_fail (recipients != NULL, -1);
    g_return_val_if_fail (recipients->len != 0, -1);
    g_return_val_if_fail (istream != NULL, -1);
    g_return_val_if_fail (ostream != NULL, -1);

#if 0
    /* this isn't needed until we respect the 'sign' argument... */
    usercert = CERT_FindCertByNickname (context->priv->certdb, userid);
    if (!usercert) {
        invalid_userkey = userid;
        goto exception;
    }
#endif
    
    cert = CERT_FindCertByNickname (context->priv->certdb, recipients->pdata[i]);
    if (!cert) {
        invalid_userkey = recipients->pdata[i];
        goto exception;
    }
    
    data = g_new (struct _GetPasswdData, 1);
    data->session = session;
    data->userid = userid;
    data->ex = ex;
    
    /* FIXME: extend CamelCipherContext to allow selecting an encryption algorithm?? */
    cinfo = SEC_PKCS7CreateEncryptedData (SEC_OID_DES_EDE3_CBC, 0, 
                          get_password, data);
    
    g_free (data);
    
    if (!cinfo) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not encrypt: failed to create enveloped data."));
        goto exception;
    }
    
    for (i++; i < recipients->len; i++) {
        SECStatus retval;
        
        cert = CERT_FindCertByNickname (context->priv->certdb, recipients->pdata[i]);
        if (!cert) {
            invalid_userkey = recipients->pdata[i];
            goto exception;
        }
        
        retval = SEC_PKCS7AddRecipient (cinfo, cert, certUsageEmailRecipient, NULL);
        if (retval != SECSuccess) {
            invalid_userkey = recipients->pdata[i];
            goto exception;
        }
    }
    
    ecx = SEC_PKCS7EncoderStart (cinfo, sec_output_cb, ostream, NULL);
    if (ecx == NULL) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not encrypt: failed to create encryption context."));
        goto exception;
    }
    
    stream = camel_stream_mem_new ();
    camel_stream_write_to_stream (istream, stream);
    buf = CAMEL_STREAM_MEM (stream)->buffer;
    if (SEC_PKCS7EncoderUpdate (ecx, buf->data, buf->len) != SECSuccess)
        goto exception;
    
    camel_object_unref (CAMEL_OBJECT (stream));
    stream = NULL;
    
    if (SEC_PKCS7EncoderFinish (ecx, NULL, NULL) != SECSuccess)
        goto exception;
    
    SEC_PKCS7DestroyContentInfo (cinfo);
    
    return 0;
    
 exception:
    
    if (stream)
        camel_object_unref (CAMEL_OBJECT (stream));
    
    if (cinfo)
        SEC_PKCS7DestroyContentInfo (cinfo);
    
    if (invalid_userkey) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not encrypt data: invalid user key: \"%s\"."),
                      invalid_userkey);
    }
    
    if (!camel_exception_is_set (ex)) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not encrypt: encoding failed."));
    }
    
    return -1;
}

static PRBool
decryption_allowed (SECAlgorithmID *algid, PK11SymKey *key)
{
    return PR_TRUE;
}

static int
pkcs7_decrypt (CamelCipherContext *ctx, CamelStream *istream,
           CamelStream *ostream, CamelException *ex)
{
    CamelPkcs7Context *context = CAMEL_PKCS7_CONTEXT (ctx);
    struct _GetPasswdData *data;
    SEC_PKCS7DecoderContext *dcx;
    SEC_PKCS7ContentInfo *cinfo;
    CamelStream *stream = NULL;
    SECItem secdata;
    GByteArray *buf;
    
    g_return_val_if_fail (istream != NULL, -1);
    g_return_val_if_fail (ostream != NULL, -1);
    
    stream = camel_stream_mem_new ();
    camel_stream_write_to_stream (istream, stream);
    buf = CAMEL_STREAM_MEM (stream)->buffer;
    secdata.data = buf->data;
    secdata.len = buf->len;
    
    data = g_new (struct _GetPasswdData, 1);
    data->session = ctx->session;
    data->userid = NULL;
    data->ex = ex;
    
    dcx = SEC_PKCS7DecoderStart (sec_output_cb, ostream, get_password, data,
                     NULL, NULL, decryption_allowed);
    if (dcx == NULL)
        goto exception;
    
    SEC_PKCS7DecoderUpdate (dcx, secdata.data, secdata.len);
    cinfo = SEC_PKCS7DecoderFinish (dcx);
    
    camel_object_unref (CAMEL_OBJECT (stream));
    g_free (data);
    
    if (cinfo == NULL) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Failed to decrypt: Unknown"));
        return -1;
    }
    
    SEC_PKCS7DestroyContentInfo (cinfo);
    
    return 0;
    
 exception:
    
    if (stream)
        camel_object_unref (CAMEL_OBJECT (stream));
    
    return -1;
}