1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
4 * Michael Zucchi <notzed@ximian.com>
6 * The Initial Developer of the Original Code is Netscape
7 * Communications Corporation. Portions created by Netscape are
8 * Copyright (C) 1994-2000 Netscape Communications Corporation. All
11 * Copyright 2003 Ximian, Inc. (www.ximian.com)
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
46 #include <camel/camel-exception.h>
47 #include <camel/camel-stream-mem.h>
48 #include <camel/camel-data-wrapper.h>
50 #include <camel/camel-mime-part.h>
51 #include <camel/camel-multipart-signed.h>
52 #include <camel/camel-stream-fs.h>
53 #include <camel/camel-stream-filter.h>
54 #include <camel/camel-mime-filter-basic.h>
55 #include <camel/camel-mime-filter-canon.h>
57 #include "camel-smime-context.h"
58 #include "camel-operation.h"
62 struct _CamelSMIMEContextPrivate {
63 CERTCertDBHandle *certdb;
66 camel_smime_sign_t sign_mode;
69 unsigned int send_encrypt_key_prefs:1;
72 static CamelCipherContextClass *parent_class = NULL;
74 /* used for decode content callback, for streaming decode */
76 sm_write_stream(void *arg, const char *buf, unsigned long len)
78 camel_stream_write((CamelStream *)arg, buf, len);
82 sm_decrypt_key(void *arg, SECAlgorithmID *algid)
84 printf("Decrypt key called\n");
85 return (PK11SymKey *)arg;
89 sm_get_passwd(PK11SlotInfo *info, PRBool retry, void *arg)
91 CamelSMIMEContext *context = arg;
92 char *pass, *nsspass = NULL;
96 ex = camel_exception_new();
98 /* we got a password, but its asking again, the password we had was wrong */
99 if (context->priv->password_tries > 0) {
100 camel_session_forget_password(((CamelCipherContext *)context)->session, NULL, PK11_GetTokenName(info), NULL);
101 context->priv->password_tries = 0;
104 prompt = g_strdup_printf(_("Enter security pass-phrase for `%s'"), PK11_GetTokenName(info));
105 pass = camel_session_get_password(((CamelCipherContext *)context)->session, prompt, FALSE, TRUE, NULL, PK11_GetTokenName(info), ex);
106 camel_exception_free(ex);
109 nsspass = PORT_Strdup(pass);
110 memset(pass, 0, strlen(pass));
112 context->priv->password_tries++;
119 * camel_smime_context_new:
122 * Creates a new sm cipher context object.
124 * Returns a new sm cipher context object.
127 camel_smime_context_new(CamelSession *session)
129 CamelCipherContext *cipher;
130 CamelSMIMEContext *ctx;
132 g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
134 ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type());
136 cipher =(CamelCipherContext *) ctx;
137 cipher->session = session;
138 camel_object_ref(session);
144 camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
146 context->priv->send_encrypt_key_prefs = use;
147 g_free(context->priv->encrypt_key);
148 context->priv->encrypt_key = g_strdup(key);
151 /* set signing mode, clearsigned multipart/signed or enveloped */
153 camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
155 context->priv->sign_mode = type;
158 /* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
160 camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
163 CamelContentType *ct;
166 ct = camel_mime_part_get_content_type(part);
168 if (camel_content_type_is(ct, "multipart", "signed")) {
169 tmp = camel_content_type_param(ct, "protocol");
171 (g_ascii_strcasecmp(tmp, ((CamelCipherContext *)context)->sign_protocol) == 0
172 || g_ascii_strcasecmp(tmp, "application/pkcs7-signature") == 0))
173 flags = CAMEL_SMIME_SIGNED;
174 } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
175 CamelStreamMem *istream;
177 NSSCMSDecoderContext *dec;
179 /* FIXME: stream this to the decoder incrementally */
180 istream = (CamelStreamMem *)camel_stream_mem_new();
181 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)part), (CamelStream *)istream);
182 camel_stream_reset((CamelStream *)istream);
184 dec = NSS_CMSDecoder_Start(NULL,
186 sm_get_passwd, context, /* password callback */
187 NULL, NULL); /* decrypt key callback */
189 NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
190 camel_object_unref(istream);
192 cmsg = NSS_CMSDecoder_Finish(dec);
194 if (NSS_CMSMessage_IsSigned(cmsg)) {
195 printf("message is signed\n");
196 flags |= CAMEL_SMIME_SIGNED;
199 if (NSS_CMSMessage_IsEncrypted(cmsg)) {
200 printf("message is encrypted\n");
201 flags |= CAMEL_SMIME_ENCRYPTED;
204 if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
205 printf("message contains certs or crls\n");
206 flags |= CAMEL_SMIME_CERTS;
209 NSS_CMSMessage_Destroy(cmsg);
211 printf("Message could not be parsed\n");
219 sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
222 case CAMEL_CIPHER_HASH_MD5:
224 case CAMEL_CIPHER_HASH_SHA1:
225 case CAMEL_CIPHER_HASH_DEFAULT:
232 static CamelCipherHash
233 sm_id_to_hash(CamelCipherContext *context, const char *id)
236 if (!strcmp(id, "md5"))
237 return CAMEL_CIPHER_HASH_MD5;
238 else if (!strcmp(id, "sha1"))
239 return CAMEL_CIPHER_HASH_SHA1;
242 return CAMEL_CIPHER_HASH_DEFAULT;
245 static NSSCMSMessage *
246 sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex)
248 struct _CamelSMIMEContextPrivate *p = context->priv;
249 NSSCMSMessage *cmsg = NULL;
250 NSSCMSContentInfo *cinfo;
251 NSSCMSSignedData *sigd;
252 NSSCMSSignerInfo *signerinfo;
253 CERTCertificate *cert= NULL, *ekpcert = NULL;
255 if ((cert = CERT_FindUserCertByUsage(p->certdb,
257 certUsageEmailSigner,
260 camel_exception_setv(ex, 1, "Can't find certificate for '%s'", nick);
264 cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
266 camel_exception_setv(ex, 1, "Can't create CMS message");
270 if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
271 camel_exception_setv(ex, 1, "Can't create CMS signedData");
275 cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
276 if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) {
277 camel_exception_setv(ex, 1, "Can't attach CMS signedData");
281 /* if !detatched, the contentinfo will alloc a data item for us */
282 cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
283 if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, detached) != SECSuccess) {
284 camel_exception_setv(ex, 1, "Can't attach CMS data");
288 signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
289 if (signerinfo == NULL) {
290 camel_exception_setv(ex, 1, "Can't create CMS SignerInfo");
294 /* we want the cert chain included for this one */
295 if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) {
296 camel_exception_setv(ex, 1, "Can't find cert chain");
300 /* SMIME RFC says signing time should always be added */
301 if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) {
302 camel_exception_setv(ex, 1, "Can't add CMS SigningTime");
307 /* this can but needn't be added. not sure what general usage is */
308 if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
309 fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
314 /* Check if we need to send along our return encrypt cert, rfc2633 2.5.3 */
315 if (p->send_encrypt_key_prefs) {
316 CERTCertificate *enccert = NULL;
318 if (p->encrypt_key) {
319 /* encrypt key has its own nick */
320 if ((ekpcert = CERT_FindUserCertByUsage(
323 certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
324 camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", p->encrypt_key);
328 } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) {
329 /* encrypt key is signing key */
332 /* encrypt key uses same nick */
333 if ((ekpcert = CERT_FindUserCertByUsage(
334 p->certdb, (char *)nick,
335 certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
336 camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", nick);
342 if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
343 camel_exception_setv(ex, 1, "can't add SMIMEEncKeyPrefs attribute");
347 if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
348 camel_exception_setv(ex, 1, "can't add MS SMIMEEncKeyPrefs attribute");
352 if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
353 camel_exception_setv(ex, 1, "can't add add encryption certificate");
358 if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
359 camel_exception_setv(ex, 1, "can't add CMS SignerInfo");
364 CERT_DestroyCertificate(ekpcert);
367 CERT_DestroyCertificate(cert);
372 CERT_DestroyCertificate(ekpcert);
375 CERT_DestroyCertificate(cert);
377 NSS_CMSMessage_Destroy(cmsg);
383 sm_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
387 CamelStream *ostream, *istream;
389 NSSCMSEncoderContext *enc;
390 CamelDataWrapper *dw;
391 CamelContentType *ct;
394 case CAMEL_CIPHER_HASH_SHA1:
395 case CAMEL_CIPHER_HASH_DEFAULT:
397 sechash = SEC_OID_SHA1;
399 case CAMEL_CIPHER_HASH_MD5:
400 sechash = SEC_OID_MD5;
404 cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)context, userid, sechash,
405 ((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
409 ostream = camel_stream_mem_new();
411 /* FIXME: stream this, we stream output at least */
412 istream = camel_stream_mem_new();
413 if (camel_cipher_canonical_to_stream(ipart,
414 CAMEL_MIME_FILTER_CANON_STRIP
415 |CAMEL_MIME_FILTER_CANON_CRLF
416 |CAMEL_MIME_FILTER_CANON_FROM, istream) == -1) {
417 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
418 _("Could not generate signing data: %s"), g_strerror(errno));
422 enc = NSS_CMSEncoder_Start(cmsg,
423 sm_write_stream, ostream, /* DER output callback */
424 NULL, NULL, /* destination storage */
425 sm_get_passwd, context, /* password callback */
426 NULL, NULL, /* decrypt key callback */
427 NULL, NULL ); /* detached digests */
429 camel_exception_setv(ex, 1, "Cannot create encoder context");
433 if (NSS_CMSEncoder_Update(enc, ((CamelStreamMem *)istream)->buffer->data, ((CamelStreamMem *)istream)->buffer->len) != SECSuccess) {
434 NSS_CMSEncoder_Cancel(enc);
435 camel_exception_setv(ex, 1, "Failed to add data to CMS encoder");
439 if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
440 camel_exception_setv(ex, 1, "Failed to encode data");
446 dw = camel_data_wrapper_new();
447 camel_stream_reset(ostream);
448 camel_data_wrapper_construct_from_stream(dw, ostream);
449 dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
451 if (((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
452 CamelMultipartSigned *mps;
453 CamelMimePart *sigpart;
455 sigpart = camel_mime_part_new();
456 ct = camel_content_type_new("application", "x-pkcs7-signature");
457 camel_content_type_set_param(ct, "name", "smime.p7s");
458 camel_data_wrapper_set_mime_type_field(dw, ct);
459 camel_content_type_unref(ct);
461 camel_medium_set_content_object((CamelMedium *)sigpart, dw);
463 camel_mime_part_set_filename(sigpart, "smime.p7s");
464 camel_mime_part_set_disposition(sigpart, "attachment");
465 camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64);
467 mps = camel_multipart_signed_new();
468 ct = camel_content_type_new("multipart", "signed");
469 camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
470 camel_content_type_set_param(ct, "protocol", context->sign_protocol);
471 camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
472 camel_content_type_unref(ct);
473 camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
475 mps->signature = sigpart;
476 mps->contentraw = istream;
477 camel_stream_reset(istream);
478 camel_object_ref(istream);
480 camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
482 ct = camel_content_type_new("application", "x-pkcs7-mime");
483 camel_content_type_set_param(ct, "name", "smime.p7m");
484 camel_content_type_set_param(ct, "smime-type", "signed-data");
485 camel_data_wrapper_set_mime_type_field(dw, ct);
486 camel_content_type_unref(ct);
488 camel_medium_set_content_object((CamelMedium *)opart, dw);
490 camel_mime_part_set_filename(opart, "smime.p7m");
491 camel_mime_part_set_description(opart, "S/MIME Signed Message");
492 camel_mime_part_set_disposition(opart, "attachment");
493 camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
496 camel_object_unref(dw);
498 camel_object_unref(ostream);
499 camel_object_unref(istream);
505 sm_status_description(NSSCMSVerificationStatus status)
507 /* could use this but then we can't control i18n? */
508 /*NSS_CMSUtil_VerificationStatusToString(status));*/
511 case NSSCMSVS_Unverified:
513 return _("Unverified");
514 case NSSCMSVS_GoodSignature:
515 return _("Good signature");
516 case NSSCMSVS_BadSignature:
517 return _("Bad signature");
518 case NSSCMSVS_DigestMismatch:
519 return _("Content tampered with or altered in transit");
520 case NSSCMSVS_SigningCertNotFound:
521 return _("Signing certificate not found");
522 case NSSCMSVS_SigningCertNotTrusted:
523 return _("Signing certificate not trusted");
524 case NSSCMSVS_SignatureAlgorithmUnknown:
525 return _("Signature algorithm unknown");
526 case NSSCMSVS_SignatureAlgorithmUnsupported:
527 return _("Siganture algorithm unsupported");
528 case NSSCMSVS_MalformedSignature:
529 return _("Malformed signature");
530 case NSSCMSVS_ProcessingError:
531 return _("Processing error");
535 static CamelCipherValidity *
536 sm_verify_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
538 struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
539 NSSCMSSignedData *sigd = NULL;
540 NSSCMSEnvelopedData *envd;
541 NSSCMSEncryptedData *encd;
542 SECAlgorithmID **digestalgs;
543 NSSCMSDigestContext *digcx;
544 int count, i, nsigners, j;
546 PLArenaPool *poolp = NULL;
548 NSSCMSVerificationStatus status;
549 CamelCipherValidity *valid;
550 GString *description;
552 description = g_string_new("");
553 valid = camel_cipher_validity_new();
554 camel_cipher_validity_set_valid(valid, TRUE);
555 status = NSSCMSVS_Unverified;
557 /* NB: this probably needs to go into a decoding routine that can be used for processing
558 enveloped data too */
559 count = NSS_CMSMessage_ContentLevelCount(cmsg);
560 for (i = 0; i < count; i++) {
561 NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg, i);
562 SECOidTag typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
565 case SEC_OID_PKCS7_SIGNED_DATA:
566 sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
568 camel_exception_setv(ex, 1, "No signedData in signature");
572 /* need to build digests of the content */
573 if (!NSS_CMSSignedData_HasDigests(sigd)) {
574 if (extstream == NULL) {
575 camel_exception_setv(ex, 1, "Digests missing from enveloped data");
579 if ((poolp = PORT_NewArena(1024)) == NULL) {
580 camel_exception_setv(ex, 1, "out of memory");
584 digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
586 digcx = NSS_CMSDigestContext_StartMultiple(digestalgs);
588 camel_exception_setv(ex, 1, "Cannot calculate digests");
592 mem = (CamelStreamMem *)camel_stream_mem_new();
593 camel_stream_write_to_stream(extstream, (CamelStream *)mem);
594 NSS_CMSDigestContext_Update(digcx, mem->buffer->data, mem->buffer->len);
595 camel_object_unref(mem);
597 if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) {
598 camel_exception_setv(ex, 1, "Cannot calculate digests");
602 if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) {
603 camel_exception_setv(ex, 1, "Cannot set message digests");
607 PORT_FreeArena(poolp, PR_FALSE);
611 /* import the certificates */
612 if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_FALSE) != SECSuccess) {
613 camel_exception_setv(ex, 1, "cert import failed");
617 /* check for certs-only message */
618 nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
620 /* ?? Should we check other usages? */
621 NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_TRUE);
622 if (NSS_CMSSignedData_VerifyCertsOnly(sigd, p->certdb, certUsageEmailSigner) != SECSuccess) {
623 g_string_printf(description, "Certficate only message, cannot verify certificates");
625 status = NSSCMSVS_GoodSignature;
626 g_string_printf(description, "Certficate only message, certificates imported and verified");
629 if (!NSS_CMSSignedData_HasDigests(sigd)) {
630 camel_exception_setv(ex, 1, "Can't find signature digests");
634 for (j = 0; j < nsigners; j++) {
635 NSSCMSSignerInfo *si;
638 si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
639 NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner);
641 status = NSS_CMSSignerInfo_GetVerificationStatus(si);
643 cn = NSS_CMSSignerInfo_GetSignerCommonName(si);
644 em = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
646 g_string_append_printf(description, _("Signer: %s <%s>: %s\n"),
647 cn?cn:"<unknown>", em?em:"<unknown>",
648 sm_status_description(status));
650 camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
657 if (status != NSSCMSVS_GoodSignature)
658 camel_cipher_validity_set_valid(valid, FALSE);
662 case SEC_OID_PKCS7_ENVELOPED_DATA:
663 envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
665 case SEC_OID_PKCS7_ENCRYPTED_DATA:
666 encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
668 case SEC_OID_PKCS7_DATA:
675 camel_cipher_validity_set_valid(valid, status == NSSCMSVS_GoodSignature);
676 camel_cipher_validity_set_description(valid, description->str);
677 g_string_free(description, TRUE);
682 camel_cipher_validity_free(valid);
683 g_string_free(description, TRUE);
688 static CamelCipherValidity *
689 sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
691 NSSCMSDecoderContext *dec;
694 CamelStream *constream;
695 CamelCipherValidity *valid = NULL;
696 CamelContentType *ct;
698 CamelMimePart *sigpart;
699 CamelDataWrapper *dw;
701 dw = camel_medium_get_content_object((CamelMedium *)ipart);
704 /* FIXME: we should stream this to the decoder */
705 mem = (CamelStreamMem *)camel_stream_mem_new();
707 if (camel_content_type_is(ct, "multipart", "signed")) {
708 CamelMultipart *mps = (CamelMultipart *)dw;
710 tmp = camel_content_type_param(ct, "protocol");
711 if (!CAMEL_IS_MULTIPART_SIGNED(mps)
713 || (g_ascii_strcasecmp(tmp, context->sign_protocol) != 0
714 && g_ascii_strcasecmp(tmp, "application/pkcs7-signature") != 0)) {
715 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
716 _("Cannot verify message signature: Incorrect message format"));
720 constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
721 if (constream == NULL)
724 sigpart = camel_multipart_get_part(mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
725 if (sigpart == NULL) {
726 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
727 _("Cannot verify message signature: Incorrect message format"));
730 } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
733 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
734 _("Cannot verify message signature: Incorrect message format"));
738 dec = NSS_CMSDecoder_Start(NULL,
739 NULL, NULL, /* content callback */
740 sm_get_passwd, context, /* password callback */
741 NULL, NULL); /* decrypt key callback */
743 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)sigpart), (CamelStream *)mem);
744 (void)NSS_CMSDecoder_Update(dec, mem->buffer->data, mem->buffer->len);
745 cmsg = NSS_CMSDecoder_Finish(dec);
747 camel_exception_setv(ex, 1, "Decoder failed");
751 valid = sm_verify_cmsg(context, cmsg, constream, ex);
753 NSS_CMSMessage_Destroy(cmsg);
755 camel_object_unref(mem);
757 camel_object_unref(constream);
763 sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
765 struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
766 /*NSSCMSRecipientInfo **recipient_infos;*/
767 CERTCertificate **recipient_certs = NULL;
768 NSSCMSContentInfo *cinfo;
769 PK11SymKey *bulkkey = NULL;
770 SECOidTag bulkalgtag;
772 CK_MECHANISM_TYPE type;
775 NSSCMSMessage *cmsg = NULL;
776 NSSCMSEnvelopedData *envd;
777 NSSCMSEncoderContext *enc = NULL;
779 CamelStream *ostream = NULL;
780 CamelDataWrapper *dw;
781 CamelContentType *ct;
783 poolp = PORT_NewArena(1024);
785 camel_exception_setv(ex, 1, "Out of memory");
789 /* Lookup all recipients certs, for later working */
790 recipient_certs = (CERTCertificate **)PORT_ArenaZAlloc(poolp, sizeof(*recipient_certs[0])*(recipients->len + 1));
791 if (recipient_certs == NULL) {
792 camel_exception_setv(ex, 1, "Out of memory");
796 for (i=0;i<recipients->len;i++) {
797 recipient_certs[i] = CERT_FindCertByNicknameOrEmailAddr(p->certdb, recipients->pdata[i]);
798 if (recipient_certs[i] == NULL) {
799 camel_exception_setv(ex, 1, "Can't find certificate for `%s'", recipients->pdata[i]);
804 /* Find a common algorithm, probably 3DES anyway ... */
805 if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) {
806 camel_exception_setv(ex, 1, "Can't find common bulk encryption algorithm");
810 /* Generate a new bulk key based on the common algorithm - expensive */
811 type = PK11_AlgtagToMechanism(bulkalgtag);
812 slot = PK11_GetBestSlot(type, context);
814 /* PORT_GetError(); ?? */
815 camel_exception_setv(ex, 1, "Can't allocate slot for encryption bulk key");
819 bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context);
822 /* Now we can start building the message */
823 /* msg->envelopedData->data */
824 cmsg = NSS_CMSMessage_Create(NULL);
826 camel_exception_setv(ex, 1, "Can't create CMS Message");
830 envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize);
832 camel_exception_setv(ex, 1, "Can't create CMS EnvelopedData");
836 cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
837 if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) {
838 camel_exception_setv(ex, 1, "Can't attach CMS EnvelopedData");
842 cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
843 if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) {
844 camel_exception_setv(ex, 1, "Can't attach CMS data object");
848 /* add recipient certs */
849 for (i=0;recipient_certs[i];i++) {
850 NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]);
853 camel_exception_setv(ex, 1, "Can't create CMS RecipientInfo");
857 if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) {
858 camel_exception_setv(ex, 1, "Can't add CMS RecipientInfo");
864 ostream = camel_stream_mem_new();
865 enc = NSS_CMSEncoder_Start(cmsg,
866 sm_write_stream, ostream,
868 sm_get_passwd, context,
869 sm_decrypt_key, bulkkey,
872 camel_exception_setv(ex, 1, "Can't create encoder context");
876 /* FIXME: Stream the input */
877 /* FIXME: Canonicalise the input? */
878 mem = (CamelStreamMem *)camel_stream_mem_new();
879 camel_data_wrapper_write_to_stream((CamelDataWrapper *)ipart, (CamelStream *)mem);
880 if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) {
881 NSS_CMSEncoder_Cancel(enc);
882 camel_object_unref(mem);
883 camel_exception_setv(ex, 1, "Failed to add data to encoder");
886 camel_object_unref(mem);
888 if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
889 camel_exception_setv(ex, 1, "Failed to encode data");
893 PK11_FreeSymKey(bulkkey);
894 NSS_CMSMessage_Destroy(cmsg);
895 for (i=0;recipient_certs[i];i++)
896 CERT_DestroyCertificate(recipient_certs[i]);
897 PORT_FreeArena(poolp, PR_FALSE);
899 dw = camel_data_wrapper_new();
900 camel_data_wrapper_construct_from_stream(dw, ostream);
901 camel_object_unref(ostream);
902 dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
904 ct = camel_content_type_new("application", "x-pkcs7-mime");
905 camel_content_type_set_param(ct, "name", "smime.p7m");
906 camel_content_type_set_param(ct, "smime-type", "enveloped-data");
907 camel_data_wrapper_set_mime_type_field(dw, ct);
908 camel_content_type_unref(ct);
910 camel_medium_set_content_object((CamelMedium *)opart, dw);
911 camel_object_unref(dw);
913 camel_mime_part_set_disposition(opart, "attachment");
914 camel_mime_part_set_filename(opart, "smime.p7m");
915 camel_mime_part_set_description(opart, "S/MIME Encrypted Message");
916 camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
922 camel_object_unref(ostream);
924 NSS_CMSMessage_Destroy(cmsg);
926 PK11_FreeSymKey(bulkkey);
928 if (recipient_certs) {
929 for (i=0;recipient_certs[i];i++)
930 CERT_DestroyCertificate(recipient_certs[i]);
933 PORT_FreeArena(poolp, PR_FALSE);
938 static CamelCipherValidity *
939 sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
941 NSSCMSDecoderContext *dec;
943 CamelStreamMem *istream;
944 CamelStream *ostream;
945 CamelCipherValidity *valid = NULL;
947 /* FIXME: This assumes the content is only encrypted. Perhaps its ok for
948 this api to do this ... */
950 ostream = camel_stream_mem_new();
952 /* FIXME: stream this to the decoder incrementally */
953 istream = (CamelStreamMem *)camel_stream_mem_new();
954 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)ipart), (CamelStream *)istream);
955 camel_stream_reset((CamelStream *)istream);
957 dec = NSS_CMSDecoder_Start(NULL,
958 sm_write_stream, ostream, /* content callback */
959 sm_get_passwd, context, /* password callback */
960 NULL, NULL); /* decrypt key callback */
962 if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) {
963 printf("decoder update failed\n");
965 camel_object_unref(istream);
967 cmsg = NSS_CMSDecoder_Finish(dec);
969 camel_exception_setv(ex, 1, "Decoder failed, error %d", PORT_GetError());
974 /* not sure if we really care about this? */
975 if (!NSS_CMSMessage_IsEncrypted(cmsg)) {
976 camel_exception_setv(ex, 1, "S/MIME Decrypt: No encrypted content found");
977 NSS_CMSMessage_Destroy(cmsg);
982 camel_stream_reset(ostream);
983 camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
985 if (NSS_CMSMessage_IsSigned(cmsg)) {
986 valid = sm_verify_cmsg(context, cmsg, NULL, ex);
988 valid = camel_cipher_validity_new();
989 valid->encrypt.description = g_strdup(_("Encrypted content"));
990 valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
993 NSS_CMSMessage_Destroy(cmsg);
995 camel_object_unref(ostream);
1001 sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex)
1003 camel_exception_setv(ex, 1, "import keys: unimplemented");
1009 sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
1011 camel_exception_setv(ex, 1, "export keys: unimplemented");
1016 /* ********************************************************************** */
1019 camel_smime_context_class_init(CamelSMIMEContextClass *klass)
1021 CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass);
1023 parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type()));
1025 cipher_class->hash_to_id = sm_hash_to_id;
1026 cipher_class->id_to_hash = sm_id_to_hash;
1027 cipher_class->sign = sm_sign;
1028 cipher_class->verify = sm_verify;
1029 cipher_class->encrypt = sm_encrypt;
1030 cipher_class->decrypt = sm_decrypt;
1031 cipher_class->import_keys = sm_import_keys;
1032 cipher_class->export_keys = sm_export_keys;
1036 camel_smime_context_init(CamelSMIMEContext *context)
1038 CamelCipherContext *cipher =(CamelCipherContext *) context;
1040 cipher->sign_protocol = "application/x-pkcs7-signature";
1041 cipher->encrypt_protocol = "application/x-pkcs7-mime";
1042 cipher->key_protocol = "application/x-pkcs7-signature";
1044 context->priv = g_malloc0(sizeof(*context->priv));
1045 context->priv->certdb = CERT_GetDefaultCertDB();
1046 context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
1047 context->priv->password_tries = 0;
1051 camel_smime_context_finalise(CamelObject *object)
1053 CamelSMIMEContext *context = (CamelSMIMEContext *)object;
1055 /* FIXME: do we have to free the certdb? */
1057 g_free(context->priv);
1061 camel_smime_context_get_type(void)
1063 static CamelType type = CAMEL_INVALID_TYPE;
1065 if (type == CAMEL_INVALID_TYPE) {
1066 type = camel_type_register(camel_cipher_context_get_type(),
1067 "CamelSMIMEContext",
1068 sizeof(CamelSMIMEContext),
1069 sizeof(CamelSMIMEContextClass),
1070 (CamelObjectClassInitFunc) camel_smime_context_class_init,
1072 (CamelObjectInitFunc) camel_smime_context_init,
1073 (CamelObjectFinalizeFunc) camel_smime_context_finalise);
1079 #endif /* HAVE_NSS */