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,
106 CAMEL_SESSION_PASSWORD_SECRET|CAMEL_SESSION_PASSWORD_STATIC, NULL, PK11_GetTokenName(info), ex);
107 camel_exception_free(ex);
110 nsspass = PORT_Strdup(pass);
111 memset(pass, 0, strlen(pass));
113 context->priv->password_tries++;
120 * camel_smime_context_new:
123 * Creates a new sm cipher context object.
125 * Returns a new sm cipher context object.
128 camel_smime_context_new(CamelSession *session)
130 CamelCipherContext *cipher;
131 CamelSMIMEContext *ctx;
133 g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
135 ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type());
137 cipher =(CamelCipherContext *) ctx;
138 cipher->session = session;
139 camel_object_ref(session);
145 camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
147 context->priv->send_encrypt_key_prefs = use;
148 g_free(context->priv->encrypt_key);
149 context->priv->encrypt_key = g_strdup(key);
152 /* set signing mode, clearsigned multipart/signed or enveloped */
154 camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
156 context->priv->sign_mode = type;
159 /* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
161 camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
164 CamelContentType *ct;
167 ct = camel_mime_part_get_content_type(part);
169 if (camel_content_type_is(ct, "multipart", "signed")) {
170 tmp = camel_content_type_param(ct, "protocol");
172 (g_ascii_strcasecmp(tmp, ((CamelCipherContext *)context)->sign_protocol) == 0
173 || g_ascii_strcasecmp(tmp, "application/pkcs7-signature") == 0))
174 flags = CAMEL_SMIME_SIGNED;
175 } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
176 CamelStreamMem *istream;
178 NSSCMSDecoderContext *dec;
180 /* FIXME: stream this to the decoder incrementally */
181 istream = (CamelStreamMem *)camel_stream_mem_new();
182 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)part), (CamelStream *)istream);
183 camel_stream_reset((CamelStream *)istream);
185 dec = NSS_CMSDecoder_Start(NULL,
187 sm_get_passwd, context, /* password callback */
188 NULL, NULL); /* decrypt key callback */
190 NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
191 camel_object_unref(istream);
193 cmsg = NSS_CMSDecoder_Finish(dec);
195 if (NSS_CMSMessage_IsSigned(cmsg)) {
196 printf("message is signed\n");
197 flags |= CAMEL_SMIME_SIGNED;
200 if (NSS_CMSMessage_IsEncrypted(cmsg)) {
201 printf("message is encrypted\n");
202 flags |= CAMEL_SMIME_ENCRYPTED;
205 if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
206 printf("message contains certs or crls\n");
207 flags |= CAMEL_SMIME_CERTS;
210 NSS_CMSMessage_Destroy(cmsg);
212 printf("Message could not be parsed\n");
220 sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
223 case CAMEL_CIPHER_HASH_MD5:
225 case CAMEL_CIPHER_HASH_SHA1:
226 case CAMEL_CIPHER_HASH_DEFAULT:
233 static CamelCipherHash
234 sm_id_to_hash(CamelCipherContext *context, const char *id)
237 if (!strcmp(id, "md5"))
238 return CAMEL_CIPHER_HASH_MD5;
239 else if (!strcmp(id, "sha1"))
240 return CAMEL_CIPHER_HASH_SHA1;
243 return CAMEL_CIPHER_HASH_DEFAULT;
246 static NSSCMSMessage *
247 sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex)
249 struct _CamelSMIMEContextPrivate *p = context->priv;
250 NSSCMSMessage *cmsg = NULL;
251 NSSCMSContentInfo *cinfo;
252 NSSCMSSignedData *sigd;
253 NSSCMSSignerInfo *signerinfo;
254 CERTCertificate *cert= NULL, *ekpcert = NULL;
256 if ((cert = CERT_FindUserCertByUsage(p->certdb,
258 certUsageEmailSigner,
261 camel_exception_setv(ex, 1, "Can't find certificate for '%s'", nick);
265 cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
267 camel_exception_setv(ex, 1, "Can't create CMS message");
271 if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
272 camel_exception_setv(ex, 1, "Can't create CMS signedData");
276 cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
277 if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) {
278 camel_exception_setv(ex, 1, "Can't attach CMS signedData");
282 /* if !detatched, the contentinfo will alloc a data item for us */
283 cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
284 if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, detached) != SECSuccess) {
285 camel_exception_setv(ex, 1, "Can't attach CMS data");
289 signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
290 if (signerinfo == NULL) {
291 camel_exception_setv(ex, 1, "Can't create CMS SignerInfo");
295 /* we want the cert chain included for this one */
296 if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) {
297 camel_exception_setv(ex, 1, "Can't find cert chain");
301 /* SMIME RFC says signing time should always be added */
302 if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) {
303 camel_exception_setv(ex, 1, "Can't add CMS SigningTime");
308 /* this can but needn't be added. not sure what general usage is */
309 if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
310 fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
315 /* Check if we need to send along our return encrypt cert, rfc2633 2.5.3 */
316 if (p->send_encrypt_key_prefs) {
317 CERTCertificate *enccert = NULL;
319 if (p->encrypt_key) {
320 /* encrypt key has its own nick */
321 if ((ekpcert = CERT_FindUserCertByUsage(
324 certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
325 camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", p->encrypt_key);
329 } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) {
330 /* encrypt key is signing key */
333 /* encrypt key uses same nick */
334 if ((ekpcert = CERT_FindUserCertByUsage(
335 p->certdb, (char *)nick,
336 certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
337 camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", nick);
343 if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
344 camel_exception_setv(ex, 1, "can't add SMIMEEncKeyPrefs attribute");
348 if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
349 camel_exception_setv(ex, 1, "can't add MS SMIMEEncKeyPrefs attribute");
353 if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
354 camel_exception_setv(ex, 1, "can't add add encryption certificate");
359 if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
360 camel_exception_setv(ex, 1, "can't add CMS SignerInfo");
365 CERT_DestroyCertificate(ekpcert);
368 CERT_DestroyCertificate(cert);
373 CERT_DestroyCertificate(ekpcert);
376 CERT_DestroyCertificate(cert);
378 NSS_CMSMessage_Destroy(cmsg);
384 sm_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
388 CamelStream *ostream, *istream;
390 NSSCMSEncoderContext *enc;
391 CamelDataWrapper *dw;
392 CamelContentType *ct;
395 case CAMEL_CIPHER_HASH_SHA1:
396 case CAMEL_CIPHER_HASH_DEFAULT:
398 sechash = SEC_OID_SHA1;
400 case CAMEL_CIPHER_HASH_MD5:
401 sechash = SEC_OID_MD5;
405 cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)context, userid, sechash,
406 ((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
410 ostream = camel_stream_mem_new();
412 /* FIXME: stream this, we stream output at least */
413 istream = camel_stream_mem_new();
414 if (camel_cipher_canonical_to_stream(ipart,
415 CAMEL_MIME_FILTER_CANON_STRIP
416 |CAMEL_MIME_FILTER_CANON_CRLF
417 |CAMEL_MIME_FILTER_CANON_FROM, istream) == -1) {
418 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
419 _("Could not generate signing data: %s"), g_strerror(errno));
423 enc = NSS_CMSEncoder_Start(cmsg,
424 sm_write_stream, ostream, /* DER output callback */
425 NULL, NULL, /* destination storage */
426 sm_get_passwd, context, /* password callback */
427 NULL, NULL, /* decrypt key callback */
428 NULL, NULL ); /* detached digests */
430 camel_exception_setv(ex, 1, "Cannot create encoder context");
434 if (NSS_CMSEncoder_Update(enc, ((CamelStreamMem *)istream)->buffer->data, ((CamelStreamMem *)istream)->buffer->len) != SECSuccess) {
435 NSS_CMSEncoder_Cancel(enc);
436 camel_exception_setv(ex, 1, "Failed to add data to CMS encoder");
440 if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
441 camel_exception_setv(ex, 1, "Failed to encode data");
447 dw = camel_data_wrapper_new();
448 camel_stream_reset(ostream);
449 camel_data_wrapper_construct_from_stream(dw, ostream);
450 dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
452 if (((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
453 CamelMultipartSigned *mps;
454 CamelMimePart *sigpart;
456 sigpart = camel_mime_part_new();
457 ct = camel_content_type_new("application", "x-pkcs7-signature");
458 camel_content_type_set_param(ct, "name", "smime.p7s");
459 camel_data_wrapper_set_mime_type_field(dw, ct);
460 camel_content_type_unref(ct);
462 camel_medium_set_content_object((CamelMedium *)sigpart, dw);
464 camel_mime_part_set_filename(sigpart, "smime.p7s");
465 camel_mime_part_set_disposition(sigpart, "attachment");
466 camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64);
468 mps = camel_multipart_signed_new();
469 ct = camel_content_type_new("multipart", "signed");
470 camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
471 camel_content_type_set_param(ct, "protocol", context->sign_protocol);
472 camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
473 camel_content_type_unref(ct);
474 camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
476 mps->signature = sigpart;
477 mps->contentraw = istream;
478 camel_stream_reset(istream);
479 camel_object_ref(istream);
481 camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
483 ct = camel_content_type_new("application", "x-pkcs7-mime");
484 camel_content_type_set_param(ct, "name", "smime.p7m");
485 camel_content_type_set_param(ct, "smime-type", "signed-data");
486 camel_data_wrapper_set_mime_type_field(dw, ct);
487 camel_content_type_unref(ct);
489 camel_medium_set_content_object((CamelMedium *)opart, dw);
491 camel_mime_part_set_filename(opart, "smime.p7m");
492 camel_mime_part_set_description(opart, "S/MIME Signed Message");
493 camel_mime_part_set_disposition(opart, "attachment");
494 camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
497 camel_object_unref(dw);
499 camel_object_unref(ostream);
500 camel_object_unref(istream);
506 sm_status_description(NSSCMSVerificationStatus status)
508 /* could use this but then we can't control i18n? */
509 /*NSS_CMSUtil_VerificationStatusToString(status));*/
512 case NSSCMSVS_Unverified:
514 return _("Unverified");
515 case NSSCMSVS_GoodSignature:
516 return _("Good signature");
517 case NSSCMSVS_BadSignature:
518 return _("Bad signature");
519 case NSSCMSVS_DigestMismatch:
520 return _("Content tampered with or altered in transit");
521 case NSSCMSVS_SigningCertNotFound:
522 return _("Signing certificate not found");
523 case NSSCMSVS_SigningCertNotTrusted:
524 return _("Signing certificate not trusted");
525 case NSSCMSVS_SignatureAlgorithmUnknown:
526 return _("Signature algorithm unknown");
527 case NSSCMSVS_SignatureAlgorithmUnsupported:
528 return _("Siganture algorithm unsupported");
529 case NSSCMSVS_MalformedSignature:
530 return _("Malformed signature");
531 case NSSCMSVS_ProcessingError:
532 return _("Processing error");
536 static CamelCipherValidity *
537 sm_verify_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
539 struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
540 NSSCMSSignedData *sigd = NULL;
541 NSSCMSEnvelopedData *envd;
542 NSSCMSEncryptedData *encd;
543 SECAlgorithmID **digestalgs;
544 NSSCMSDigestContext *digcx;
545 int count, i, nsigners, j;
547 PLArenaPool *poolp = NULL;
549 NSSCMSVerificationStatus status;
550 CamelCipherValidity *valid;
551 GString *description;
553 description = g_string_new("");
554 valid = camel_cipher_validity_new();
555 camel_cipher_validity_set_valid(valid, TRUE);
556 status = NSSCMSVS_Unverified;
558 /* NB: this probably needs to go into a decoding routine that can be used for processing
559 enveloped data too */
560 count = NSS_CMSMessage_ContentLevelCount(cmsg);
561 for (i = 0; i < count; i++) {
562 NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg, i);
563 SECOidTag typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
566 case SEC_OID_PKCS7_SIGNED_DATA:
567 sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
569 camel_exception_setv(ex, 1, "No signedData in signature");
573 /* need to build digests of the content */
574 if (!NSS_CMSSignedData_HasDigests(sigd)) {
575 if (extstream == NULL) {
576 camel_exception_setv(ex, 1, "Digests missing from enveloped data");
580 if ((poolp = PORT_NewArena(1024)) == NULL) {
581 camel_exception_setv(ex, 1, "out of memory");
585 digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
587 digcx = NSS_CMSDigestContext_StartMultiple(digestalgs);
589 camel_exception_setv(ex, 1, "Cannot calculate digests");
593 mem = (CamelStreamMem *)camel_stream_mem_new();
594 camel_stream_write_to_stream(extstream, (CamelStream *)mem);
595 NSS_CMSDigestContext_Update(digcx, mem->buffer->data, mem->buffer->len);
596 camel_object_unref(mem);
598 if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) {
599 camel_exception_setv(ex, 1, "Cannot calculate digests");
603 if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) {
604 camel_exception_setv(ex, 1, "Cannot set message digests");
608 PORT_FreeArena(poolp, PR_FALSE);
612 /* import the certificates */
613 if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_FALSE) != SECSuccess) {
614 camel_exception_setv(ex, 1, "cert import failed");
618 /* check for certs-only message */
619 nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
621 /* ?? Should we check other usages? */
622 NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_TRUE);
623 if (NSS_CMSSignedData_VerifyCertsOnly(sigd, p->certdb, certUsageEmailSigner) != SECSuccess) {
624 g_string_printf(description, "Certficate only message, cannot verify certificates");
626 status = NSSCMSVS_GoodSignature;
627 g_string_printf(description, "Certficate only message, certificates imported and verified");
630 if (!NSS_CMSSignedData_HasDigests(sigd)) {
631 camel_exception_setv(ex, 1, "Can't find signature digests");
635 for (j = 0; j < nsigners; j++) {
636 NSSCMSSignerInfo *si;
639 si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
640 NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner);
642 status = NSS_CMSSignerInfo_GetVerificationStatus(si);
644 cn = NSS_CMSSignerInfo_GetSignerCommonName(si);
645 em = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
647 g_string_append_printf(description, _("Signer: %s <%s>: %s\n"),
648 cn?cn:"<unknown>", em?em:"<unknown>",
649 sm_status_description(status));
651 camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
658 if (status != NSSCMSVS_GoodSignature)
659 camel_cipher_validity_set_valid(valid, FALSE);
663 case SEC_OID_PKCS7_ENVELOPED_DATA:
664 envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
666 case SEC_OID_PKCS7_ENCRYPTED_DATA:
667 encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
669 case SEC_OID_PKCS7_DATA:
676 camel_cipher_validity_set_valid(valid, status == NSSCMSVS_GoodSignature);
677 camel_cipher_validity_set_description(valid, description->str);
678 g_string_free(description, TRUE);
683 camel_cipher_validity_free(valid);
684 g_string_free(description, TRUE);
689 static CamelCipherValidity *
690 sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
692 NSSCMSDecoderContext *dec;
695 CamelStream *constream;
696 CamelCipherValidity *valid = NULL;
697 CamelContentType *ct;
699 CamelMimePart *sigpart;
700 CamelDataWrapper *dw;
702 dw = camel_medium_get_content_object((CamelMedium *)ipart);
705 /* FIXME: we should stream this to the decoder */
706 mem = (CamelStreamMem *)camel_stream_mem_new();
708 if (camel_content_type_is(ct, "multipart", "signed")) {
709 CamelMultipart *mps = (CamelMultipart *)dw;
711 tmp = camel_content_type_param(ct, "protocol");
712 if (!CAMEL_IS_MULTIPART_SIGNED(mps)
714 || (g_ascii_strcasecmp(tmp, context->sign_protocol) != 0
715 && g_ascii_strcasecmp(tmp, "application/pkcs7-signature") != 0)) {
716 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
717 _("Cannot verify message signature: Incorrect message format"));
721 constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
722 if (constream == NULL)
725 sigpart = camel_multipart_get_part(mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
726 if (sigpart == NULL) {
727 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
728 _("Cannot verify message signature: Incorrect message format"));
731 } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
734 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
735 _("Cannot verify message signature: Incorrect message format"));
739 dec = NSS_CMSDecoder_Start(NULL,
740 NULL, NULL, /* content callback */
741 sm_get_passwd, context, /* password callback */
742 NULL, NULL); /* decrypt key callback */
744 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)sigpart), (CamelStream *)mem);
745 (void)NSS_CMSDecoder_Update(dec, mem->buffer->data, mem->buffer->len);
746 cmsg = NSS_CMSDecoder_Finish(dec);
748 camel_exception_setv(ex, 1, "Decoder failed");
752 valid = sm_verify_cmsg(context, cmsg, constream, ex);
754 NSS_CMSMessage_Destroy(cmsg);
756 camel_object_unref(mem);
758 camel_object_unref(constream);
764 sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
766 struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
767 /*NSSCMSRecipientInfo **recipient_infos;*/
768 CERTCertificate **recipient_certs = NULL;
769 NSSCMSContentInfo *cinfo;
770 PK11SymKey *bulkkey = NULL;
771 SECOidTag bulkalgtag;
773 CK_MECHANISM_TYPE type;
776 NSSCMSMessage *cmsg = NULL;
777 NSSCMSEnvelopedData *envd;
778 NSSCMSEncoderContext *enc = NULL;
780 CamelStream *ostream = NULL;
781 CamelDataWrapper *dw;
782 CamelContentType *ct;
784 poolp = PORT_NewArena(1024);
786 camel_exception_setv(ex, 1, "Out of memory");
790 /* Lookup all recipients certs, for later working */
791 recipient_certs = (CERTCertificate **)PORT_ArenaZAlloc(poolp, sizeof(*recipient_certs[0])*(recipients->len + 1));
792 if (recipient_certs == NULL) {
793 camel_exception_setv(ex, 1, "Out of memory");
797 for (i=0;i<recipients->len;i++) {
798 recipient_certs[i] = CERT_FindCertByNicknameOrEmailAddr(p->certdb, recipients->pdata[i]);
799 if (recipient_certs[i] == NULL) {
800 camel_exception_setv(ex, 1, "Can't find certificate for `%s'", recipients->pdata[i]);
805 /* Find a common algorithm, probably 3DES anyway ... */
806 if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) {
807 camel_exception_setv(ex, 1, "Can't find common bulk encryption algorithm");
811 /* Generate a new bulk key based on the common algorithm - expensive */
812 type = PK11_AlgtagToMechanism(bulkalgtag);
813 slot = PK11_GetBestSlot(type, context);
815 /* PORT_GetError(); ?? */
816 camel_exception_setv(ex, 1, "Can't allocate slot for encryption bulk key");
820 bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context);
823 /* Now we can start building the message */
824 /* msg->envelopedData->data */
825 cmsg = NSS_CMSMessage_Create(NULL);
827 camel_exception_setv(ex, 1, "Can't create CMS Message");
831 envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize);
833 camel_exception_setv(ex, 1, "Can't create CMS EnvelopedData");
837 cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
838 if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) {
839 camel_exception_setv(ex, 1, "Can't attach CMS EnvelopedData");
843 cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
844 if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) {
845 camel_exception_setv(ex, 1, "Can't attach CMS data object");
849 /* add recipient certs */
850 for (i=0;recipient_certs[i];i++) {
851 NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]);
854 camel_exception_setv(ex, 1, "Can't create CMS RecipientInfo");
858 if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) {
859 camel_exception_setv(ex, 1, "Can't add CMS RecipientInfo");
865 ostream = camel_stream_mem_new();
866 enc = NSS_CMSEncoder_Start(cmsg,
867 sm_write_stream, ostream,
869 sm_get_passwd, context,
870 sm_decrypt_key, bulkkey,
873 camel_exception_setv(ex, 1, "Can't create encoder context");
877 /* FIXME: Stream the input */
878 /* FIXME: Canonicalise the input? */
879 mem = (CamelStreamMem *)camel_stream_mem_new();
880 camel_data_wrapper_write_to_stream((CamelDataWrapper *)ipart, (CamelStream *)mem);
881 if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) {
882 NSS_CMSEncoder_Cancel(enc);
883 camel_object_unref(mem);
884 camel_exception_setv(ex, 1, "Failed to add data to encoder");
887 camel_object_unref(mem);
889 if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
890 camel_exception_setv(ex, 1, "Failed to encode data");
894 PK11_FreeSymKey(bulkkey);
895 NSS_CMSMessage_Destroy(cmsg);
896 for (i=0;recipient_certs[i];i++)
897 CERT_DestroyCertificate(recipient_certs[i]);
898 PORT_FreeArena(poolp, PR_FALSE);
900 dw = camel_data_wrapper_new();
901 camel_data_wrapper_construct_from_stream(dw, ostream);
902 camel_object_unref(ostream);
903 dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
905 ct = camel_content_type_new("application", "x-pkcs7-mime");
906 camel_content_type_set_param(ct, "name", "smime.p7m");
907 camel_content_type_set_param(ct, "smime-type", "enveloped-data");
908 camel_data_wrapper_set_mime_type_field(dw, ct);
909 camel_content_type_unref(ct);
911 camel_medium_set_content_object((CamelMedium *)opart, dw);
912 camel_object_unref(dw);
914 camel_mime_part_set_disposition(opart, "attachment");
915 camel_mime_part_set_filename(opart, "smime.p7m");
916 camel_mime_part_set_description(opart, "S/MIME Encrypted Message");
917 camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
923 camel_object_unref(ostream);
925 NSS_CMSMessage_Destroy(cmsg);
927 PK11_FreeSymKey(bulkkey);
929 if (recipient_certs) {
930 for (i=0;recipient_certs[i];i++)
931 CERT_DestroyCertificate(recipient_certs[i]);
934 PORT_FreeArena(poolp, PR_FALSE);
939 static CamelCipherValidity *
940 sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
942 NSSCMSDecoderContext *dec;
944 CamelStreamMem *istream;
945 CamelStream *ostream;
946 CamelCipherValidity *valid = NULL;
948 /* FIXME: This assumes the content is only encrypted. Perhaps its ok for
949 this api to do this ... */
951 ostream = camel_stream_mem_new();
953 /* FIXME: stream this to the decoder incrementally */
954 istream = (CamelStreamMem *)camel_stream_mem_new();
955 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)ipart), (CamelStream *)istream);
956 camel_stream_reset((CamelStream *)istream);
958 dec = NSS_CMSDecoder_Start(NULL,
959 sm_write_stream, ostream, /* content callback */
960 sm_get_passwd, context, /* password callback */
961 NULL, NULL); /* decrypt key callback */
963 if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) {
964 printf("decoder update failed\n");
966 camel_object_unref(istream);
968 cmsg = NSS_CMSDecoder_Finish(dec);
970 camel_exception_setv(ex, 1, "Decoder failed, error %d", PORT_GetError());
975 /* not sure if we really care about this? */
976 if (!NSS_CMSMessage_IsEncrypted(cmsg)) {
977 camel_exception_setv(ex, 1, "S/MIME Decrypt: No encrypted content found");
978 NSS_CMSMessage_Destroy(cmsg);
983 camel_stream_reset(ostream);
984 camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
986 if (NSS_CMSMessage_IsSigned(cmsg)) {
987 valid = sm_verify_cmsg(context, cmsg, NULL, ex);
989 valid = camel_cipher_validity_new();
990 valid->encrypt.description = g_strdup(_("Encrypted content"));
991 valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
994 NSS_CMSMessage_Destroy(cmsg);
996 camel_object_unref(ostream);
1002 sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex)
1004 camel_exception_setv(ex, 1, "import keys: unimplemented");
1010 sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
1012 camel_exception_setv(ex, 1, "export keys: unimplemented");
1017 /* ********************************************************************** */
1020 camel_smime_context_class_init(CamelSMIMEContextClass *klass)
1022 CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass);
1024 parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type()));
1026 cipher_class->hash_to_id = sm_hash_to_id;
1027 cipher_class->id_to_hash = sm_id_to_hash;
1028 cipher_class->sign = sm_sign;
1029 cipher_class->verify = sm_verify;
1030 cipher_class->encrypt = sm_encrypt;
1031 cipher_class->decrypt = sm_decrypt;
1032 cipher_class->import_keys = sm_import_keys;
1033 cipher_class->export_keys = sm_export_keys;
1037 camel_smime_context_init(CamelSMIMEContext *context)
1039 CamelCipherContext *cipher =(CamelCipherContext *) context;
1041 cipher->sign_protocol = "application/x-pkcs7-signature";
1042 cipher->encrypt_protocol = "application/x-pkcs7-mime";
1043 cipher->key_protocol = "application/x-pkcs7-signature";
1045 context->priv = g_malloc0(sizeof(*context->priv));
1046 context->priv->certdb = CERT_GetDefaultCertDB();
1047 context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
1048 context->priv->password_tries = 0;
1052 camel_smime_context_finalise(CamelObject *object)
1054 CamelSMIMEContext *context = (CamelSMIMEContext *)object;
1056 /* FIXME: do we have to free the certdb? */
1058 g_free(context->priv);
1062 camel_smime_context_get_type(void)
1064 static CamelType type = CAMEL_INVALID_TYPE;
1066 if (type == CAMEL_INVALID_TYPE) {
1067 type = camel_type_register(camel_cipher_context_get_type(),
1068 "CamelSMIMEContext",
1069 sizeof(CamelSMIMEContext),
1070 sizeof(CamelSMIMEContextClass),
1071 (CamelObjectClassInitFunc) camel_smime_context_class_init,
1073 (CamelObjectInitFunc) camel_smime_context_init,
1074 (CamelObjectFinalizeFunc) camel_smime_context_finalise);
1080 #endif /* HAVE_NSS */