/* -*- 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
#ifdef HAVE_NSS
#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 <errno.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-multipart-signed.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;
/* 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;
printf("get passwd called '%s'\n", PK11_GetTokenName(info));
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;
}
/**
* 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;
}
/* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
guint32
camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
{
guint32 flags = 0;
CamelContentType *ct;
const char *tmp;
ct = camel_mime_part_get_content_type(part);
if (camel_content_type_is(ct, "multipart", "signed")) {
tmp = camel_content_type_param(ct, "protocol");
if (tmp &&
(g_ascii_strcasecmp(tmp, ((CamelCipherContext *)context)->sign_protocol) == 0
|| g_ascii_strcasecmp(tmp, "application/pkcs7-signature") == 0))
flags = CAMEL_SMIME_SIGNED;
} else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
CamelStreamMem *istream;
NSSCMSMessage *cmsg;
NSSCMSDecoderContext *dec;
/* 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 *)part), (CamelStream *)istream);
camel_stream_reset((CamelStream *)istream);
dec = NSS_CMSDecoder_Start(NULL,
NULL, NULL,
sm_get_passwd, context, /* password callback */
NULL, NULL); /* decrypt key callback */
NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
camel_object_unref(istream);
cmsg = NSS_CMSDecoder_Finish(dec);
if (cmsg) {
if (NSS_CMSMessage_IsSigned(cmsg)) {
printf("message is signed\n");
flags |= CAMEL_SMIME_SIGNED;
}
if (NSS_CMSMessage_IsEncrypted(cmsg)) {
printf("message is encrypted\n");
flags |= CAMEL_SMIME_ENCRYPTED;
}
#if 0
if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
printf("message contains certs or crls\n");
flags |= CAMEL_SMIME_CERTS;
}
#endif
NSS_CMSMessage_Destroy(cmsg);
} else {
printf("Message could not be parsed\n");
}
}
return flags;
}
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;
}
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;
if ((cert = CERT_FindUserCertByUsage(p->certdb,
(char *)nick,
certUsageEmailSigner,
PR_FALSE,
NULL)) == NULL) {
camel_exception_setv(ex, 1, "Can'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_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
{
int res = -1;
NSSCMSMessage *cmsg;
CamelStream *ostream, *istream;
SECOidTag sechash;
NSSCMSEncoderContext *enc;
CamelDataWrapper *dw;
CamelContentType *ct;
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 *)context, userid, sechash,
((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
if (cmsg == NULL)
return -1;
ostream = camel_stream_mem_new();
/* FIXME: stream this, we stream output at least */
istream = camel_stream_mem_new();
if (camel_cipher_canonical_to_stream(ipart,
CAMEL_MIME_FILTER_CANON_STRIP
|CAMEL_MIME_FILTER_CANON_CRLF
|CAMEL_MIME_FILTER_CANON_FROM, istream) == -1) {
camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
_("Could not generate signing data: %s"), g_strerror(errno));
goto fail;
}
enc = NSS_CMSEncoder_Start(cmsg,
sm_write_stream, ostream, /* 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;
}
if (NSS_CMSEncoder_Update(enc, ((CamelStreamMem *)istream)->buffer->data, ((CamelStreamMem *)istream)->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;
}
res = 0;
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 *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
CamelMultipartSigned *mps;
CamelMimePart *sigpart;
sigpart = camel_mime_part_new();
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_medium_set_content_object((CamelMedium *)sigpart, dw);
camel_mime_part_set_filename(sigpart, "smime.p7s");
camel_mime_part_set_disposition(sigpart, "attachment");
camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64);
mps = camel_multipart_signed_new();
ct = camel_content_type_new("multipart", "signed");
camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
camel_content_type_set_param(ct, "protocol", context->sign_protocol);
camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
camel_content_type_unref(ct);
camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
mps->signature = sigpart;
mps->contentraw = istream;
camel_stream_reset(istream);
camel_object_ref(istream);
camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
} 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_medium_set_content_object((CamelMedium *)opart, dw);
camel_mime_part_set_filename(opart, "smime.p7m");
camel_mime_part_set_description(opart, "S/MIME Signed Message");
camel_mime_part_set_disposition(opart, "attachment");
camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
}
camel_object_unref(dw);
fail:
camel_object_unref(ostream);
camel_object_unref(istream);
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_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
{
struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
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;
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 (extstream == NULL) {
camel_exception_setv(ex, 1, "Digests missing from enveloped data");
goto fail;
}
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(extstream, (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, "Cannot 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));
camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
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);
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);
return valid;
fail:
camel_cipher_validity_free(valid);
g_string_free(description, TRUE);
return NULL;
}
static CamelCipherValidity *
sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
{
NSSCMSDecoderContext *dec;
NSSCMSMessage *cmsg;
CamelStreamMem *mem;
CamelStream *constream;
CamelCipherValidity *valid = NULL;
CamelContentType *ct;
const char *tmp;
CamelMimePart *sigpart;
CamelDataWrapper *dw;
dw = camel_medium_get_content_object((CamelMedium *)ipart);
ct = dw->mime_type;
/* FIXME: we should stream this to the decoder */
mem = (CamelStreamMem *)camel_stream_mem_new();
if (camel_content_type_is(ct, "multipart", "signed")) {
CamelMultipart *mps = (CamelMultipart *)dw;
tmp = camel_content_type_param(ct, "protocol");
if (!CAMEL_IS_MULTIPART_SIGNED(mps)
|| tmp == NULL
|| (g_ascii_strcasecmp(tmp, context->sign_protocol) != 0
&& g_ascii_strcasecmp(tmp, "application/pkcs7-signature") != 0)) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
_("Cannot verify message signature: Incorrect message format"));
goto fail;
}
constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
if (constream == NULL)
goto fail;
sigpart = camel_multipart_get_part(mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
if (sigpart == NULL) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
_("Cannot verify message signature: Incorrect message format"));
goto fail;
}
} else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
sigpart = ipart;
} else {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
_("Cannot verify message signature: Incorrect message format"));
goto fail;
}
dec = NSS_CMSDecoder_Start(NULL,
NULL, NULL, /* content callback */
sm_get_passwd, context, /* password callback */
NULL, NULL); /* decrypt key callback */
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);
cmsg = NSS_CMSDecoder_Finish(dec);
if (cmsg == NULL) {
camel_exception_setv(ex, 1, "Decoder failed");
goto fail;
}
valid = sm_verify_cmsg(context, cmsg, constream, ex);
NSS_CMSMessage_Destroy(cmsg);
fail:
camel_object_unref(mem);
if (constream)
camel_object_unref(constream);
return valid;
}
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 CamelCipherValidity *
sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
{
NSSCMSDecoderContext *dec;
NSSCMSMessage *cmsg;
CamelStreamMem *istream;
CamelStream *ostream;
CamelCipherValidity *valid = 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
camel_stream_reset(ostream);
camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
if (NSS_CMSMessage_IsSigned(cmsg)) {
valid = sm_verify_cmsg(context, cmsg, NULL, ex);
} else {
valid = camel_cipher_validity_new();
valid->encrypt.description = g_strdup(_("Encrypted content"));
valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
}
NSS_CMSMessage_Destroy(cmsg);
fail:
camel_object_unref(ostream);
return valid;
}
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;
}
#endif /* HAVE_NSS */