8b132b696825d6a19dc9486e22d4f1f7e1541bfd
[platform/upstream/evolution-data-server.git] / camel / camel-smime-context.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *           Michael Zucchi <notzed@ximian.com>
5  *
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
9  *  Rights Reserved.
10  *
11  *  Copyright 2003 Ximian, Inc. (www.ximian.com)
12  *
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.
17  *
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.
22  *
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.
26  *
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32
33 #ifdef HAVE_NSS
34
35 #include "nss.h"
36 #include <cms.h>
37 #include <cert.h>
38 #include <certdb.h>
39 #include <pkcs11.h>
40 #include <smime.h>
41 #include <pkcs11t.h>
42 #include <pk11func.h>
43
44 #include <errno.h>
45
46 #include <camel/camel-exception.h>
47 #include <camel/camel-stream-mem.h>
48 #include <camel/camel-data-wrapper.h>
49
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>
56
57 #include "camel-smime-context.h"
58 #include "camel-operation.h"
59
60 #define d(x)
61
62 struct _CamelSMIMEContextPrivate {
63         CERTCertDBHandle *certdb;
64
65         char *encrypt_key;
66         camel_smime_sign_t sign_mode;
67
68         int password_tries;
69         unsigned int send_encrypt_key_prefs:1;
70 };
71
72 static CamelCipherContextClass *parent_class = NULL;
73
74 /* used for decode content callback, for streaming decode */
75 static void
76 sm_write_stream(void *arg, const char *buf, unsigned long len)
77 {
78         camel_stream_write((CamelStream *)arg, buf, len);
79 }
80
81 static PK11SymKey *
82 sm_decrypt_key(void *arg, SECAlgorithmID *algid)
83 {
84         printf("Decrypt key called\n");
85         return (PK11SymKey *)arg;
86 }
87
88 static char *
89 sm_get_passwd(PK11SlotInfo *info, PRBool retry, void *arg)
90 {
91         CamelSMIMEContext *context = arg;
92         char *pass, *nsspass = NULL;
93         char *prompt;
94         CamelException *ex;
95
96         ex = camel_exception_new();
97
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;
102         }
103
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);
107         g_free(prompt);
108         if (pass) {
109                 nsspass = PORT_Strdup(pass);
110                 memset(pass, 0, strlen(pass));
111                 g_free(pass);
112                 context->priv->password_tries++;
113         }
114         
115         return nsspass;
116 }
117
118 /**
119  * camel_smime_context_new:
120  * @session: session
121  *
122  * Creates a new sm cipher context object.
123  *
124  * Returns a new sm cipher context object.
125  **/
126 CamelCipherContext *
127 camel_smime_context_new(CamelSession *session)
128 {
129         CamelCipherContext *cipher;
130         CamelSMIMEContext *ctx;
131         
132         g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
133         
134         ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type());
135         
136         cipher =(CamelCipherContext *) ctx;
137         cipher->session = session;
138         camel_object_ref(session);
139         
140         return cipher;
141 }
142
143 void
144 camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
145 {
146         context->priv->send_encrypt_key_prefs = use;
147         g_free(context->priv->encrypt_key);
148         context->priv->encrypt_key = g_strdup(key);
149 }
150
151 /* set signing mode, clearsigned multipart/signed or enveloped */
152 void
153 camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
154 {
155         context->priv->sign_mode = type;
156 }
157
158 /* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
159 guint32
160 camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
161 {
162         guint32 flags = 0;
163         CamelContentType *ct;
164         const char *tmp;
165
166         ct = camel_mime_part_get_content_type(part);
167
168         if (camel_content_type_is(ct, "multipart", "signed")) {
169                 tmp = camel_content_type_param(ct, "protocol");
170                 if (tmp &&
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;
176                 NSSCMSMessage *cmsg;
177                 NSSCMSDecoderContext *dec;
178
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);
183
184                 dec = NSS_CMSDecoder_Start(NULL, 
185                                            NULL, NULL,
186                                            sm_get_passwd, context,      /* password callback    */
187                                            NULL, NULL); /* decrypt key callback */
188                 
189                 NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
190                 camel_object_unref(istream);
191                 
192                 cmsg = NSS_CMSDecoder_Finish(dec);
193                 if (cmsg) {
194                         if (NSS_CMSMessage_IsSigned(cmsg)) {
195                                 printf("message is signed\n");
196                                 flags |= CAMEL_SMIME_SIGNED;
197                         }
198                         
199                         if (NSS_CMSMessage_IsEncrypted(cmsg)) {
200                                 printf("message is encrypted\n");
201                                 flags |= CAMEL_SMIME_ENCRYPTED;
202                         }
203 #if 0
204                         if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
205                                 printf("message contains certs or crls\n");
206                                 flags |= CAMEL_SMIME_CERTS;
207                         }
208 #endif
209                         NSS_CMSMessage_Destroy(cmsg);
210                 } else {
211                         printf("Message could not be parsed\n");
212                 }
213         }
214
215         return flags;
216 }
217
218 static const char *
219 sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
220 {
221         switch(hash) {
222         case CAMEL_CIPHER_HASH_MD5:
223                 return "md5";
224         case CAMEL_CIPHER_HASH_SHA1:
225         case CAMEL_CIPHER_HASH_DEFAULT:
226                 return "sha1";
227         default:
228                 return NULL;
229         }
230 }
231
232 static CamelCipherHash
233 sm_id_to_hash(CamelCipherContext *context, const char *id)
234 {
235         if (id) {
236                 if (!strcmp(id, "md5"))
237                         return CAMEL_CIPHER_HASH_MD5;
238                 else if (!strcmp(id, "sha1"))
239                         return CAMEL_CIPHER_HASH_SHA1;
240         }
241         
242         return CAMEL_CIPHER_HASH_DEFAULT;
243 }
244
245 static NSSCMSMessage *
246 sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex)
247 {
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;
254
255         if ((cert = CERT_FindUserCertByUsage(p->certdb,
256                                              (char *)nick,
257                                              certUsageEmailSigner,
258                                              PR_FALSE,
259                                              NULL)) == NULL) {
260                 camel_exception_setv(ex, 1, "Can't find certificate for '%s'", nick);
261                 return NULL;
262         }
263
264         cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
265         if (cmsg == NULL) {
266                 camel_exception_setv(ex, 1, "Can't create CMS message");
267                 goto fail;
268         }
269
270         if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
271                 camel_exception_setv(ex, 1, "Can't create CMS signedData");
272                 goto fail;
273         }
274
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");
278                 goto fail;
279         }
280
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");
285                 goto fail;
286         }
287
288         signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
289         if (signerinfo == NULL) {
290                 camel_exception_setv(ex, 1, "Can't create CMS SignerInfo");
291                 goto fail;
292         }
293
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");
297                 goto fail;
298         }
299
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");
303                 goto fail;
304         }
305
306 #if 0
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");
310                 goto loser;
311         }
312 #endif
313
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;
317
318                 if (p->encrypt_key) {
319                         /* encrypt key has its own nick */
320                         if ((ekpcert = CERT_FindUserCertByUsage(
321                                      p->certdb,
322                                      p->encrypt_key,
323                                      certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
324                                 camel_exception_setv(ex, 1, "encryption cert for '%s' does not exist", p->encrypt_key);
325                                 goto fail;
326                         }
327                         enccert = ekpcert;
328                 } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) {
329                         /* encrypt key is signing key */
330                         enccert = cert;
331                 } else {
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);
337                                 goto fail;
338                         }
339                         enccert = ekpcert;
340                 }
341
342                 if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
343                         camel_exception_setv(ex, 1, "can't add SMIMEEncKeyPrefs attribute");
344                         goto fail;
345                 }
346
347                 if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
348                         camel_exception_setv(ex, 1, "can't add MS SMIMEEncKeyPrefs attribute");
349                         goto fail;
350                 }
351
352                 if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
353                         camel_exception_setv(ex, 1, "can't add add encryption certificate");
354                         goto fail;
355                 }
356         }
357
358         if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
359                 camel_exception_setv(ex, 1, "can't add CMS SignerInfo");
360                 goto fail;
361         }
362
363         if (ekpcert)
364                 CERT_DestroyCertificate(ekpcert);
365
366         if (cert)
367                 CERT_DestroyCertificate(cert);
368
369         return cmsg;
370 fail:
371         if (ekpcert)
372                 CERT_DestroyCertificate(ekpcert);
373
374         if (cert)
375                 CERT_DestroyCertificate(cert);
376
377         NSS_CMSMessage_Destroy(cmsg);
378
379         return NULL;
380 }
381
382 static int
383 sm_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
384 {
385         int res = -1;
386         NSSCMSMessage *cmsg;
387         CamelStream *ostream, *istream;
388         SECOidTag sechash;
389         NSSCMSEncoderContext *enc;
390         CamelDataWrapper *dw;
391         CamelContentType *ct;
392
393         switch (hash) {
394         case CAMEL_CIPHER_HASH_SHA1:
395         case CAMEL_CIPHER_HASH_DEFAULT:
396         default:
397                 sechash = SEC_OID_SHA1;
398                 break;
399         case CAMEL_CIPHER_HASH_MD5:
400                 sechash = SEC_OID_MD5;
401                 break;
402         }
403
404         cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)context, userid, sechash,
405                                      ((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
406         if (cmsg == NULL)
407                 return -1;
408
409         ostream = camel_stream_mem_new();
410
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));
419                 goto fail;
420         }
421
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    */
428         if (!enc) {
429                 camel_exception_setv(ex, 1, "Cannot create encoder context");
430                 goto fail;
431         }
432
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");
436                 goto fail;
437         }
438
439         if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
440                 camel_exception_setv(ex, 1, "Failed to encode data");
441                 goto fail;
442         }
443
444         res = 0;
445
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;
450
451         if (((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
452                 CamelMultipartSigned *mps;
453                 CamelMimePart *sigpart;
454
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);
460
461                 camel_medium_set_content_object((CamelMedium *)sigpart, dw);
462
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);
466
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);
474
475                 mps->signature = sigpart;
476                 mps->contentraw = istream;
477                 camel_stream_reset(istream);
478                 camel_object_ref(istream);
479                 
480                 camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
481         } else {
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);
487                 
488                 camel_medium_set_content_object((CamelMedium *)opart, dw);
489
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);
494         }
495         
496         camel_object_unref(dw);
497 fail:   
498         camel_object_unref(ostream);
499         camel_object_unref(istream);
500
501         return res;
502 }
503
504 static const char *
505 sm_status_description(NSSCMSVerificationStatus status)
506 {
507         /* could use this but then we can't control i18n? */
508         /*NSS_CMSUtil_VerificationStatusToString(status));*/
509
510         switch(status) {
511         case NSSCMSVS_Unverified:
512         default:
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");
532         }
533 }
534
535 static CamelCipherValidity *
536 sm_verify_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
537 {
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;
545         SECItem **digests;
546         PLArenaPool *poolp = NULL;
547         CamelStreamMem *mem;
548         NSSCMSVerificationStatus status;
549         CamelCipherValidity *valid;
550         GString *description;
551
552         description = g_string_new("");
553         valid = camel_cipher_validity_new();
554         camel_cipher_validity_set_valid(valid, TRUE);
555         status = NSSCMSVS_Unverified;
556
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);
563
564                 switch (typetag) {
565                 case SEC_OID_PKCS7_SIGNED_DATA:
566                         sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
567                         if (sigd == NULL) {
568                                 camel_exception_setv(ex, 1, "No signedData in signature");
569                                 goto fail;
570                         }
571
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");
576                                         goto fail;
577                                 }
578
579                                 if ((poolp = PORT_NewArena(1024)) == NULL) {
580                                         camel_exception_setv(ex, 1, "out of memory");
581                                         goto fail;
582                                 }
583
584                                 digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
585                                 
586                                 digcx = NSS_CMSDigestContext_StartMultiple(digestalgs);
587                                 if (digcx == NULL) {
588                                         camel_exception_setv(ex, 1, "Cannot calculate digests");
589                                         goto fail;
590                                 }
591
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);
596
597                                 if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) {
598                                         camel_exception_setv(ex, 1, "Cannot calculate digests");
599                                         goto fail;
600                                 }
601
602                                 if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) {
603                                         camel_exception_setv(ex, 1, "Cannot set message digests");
604                                         goto fail;
605                                 }
606
607                                 PORT_FreeArena(poolp, PR_FALSE);
608                                 poolp = NULL;
609                         }
610
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");
614                                 goto fail;
615                         }
616
617                         /* check for certs-only message */
618                         nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
619                         if (nsigners == 0) {
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");
624                                 } else {
625                                         status = NSSCMSVS_GoodSignature;
626                                         g_string_printf(description, "Certficate only message, certificates imported and verified");
627                                 }
628                         } else {
629                                 if (!NSS_CMSSignedData_HasDigests(sigd)) {
630                                         camel_exception_setv(ex, 1, "Can't find signature digests");
631                                         goto fail;
632                                 }
633
634                                 for (j = 0; j < nsigners; j++) {
635                                         NSSCMSSignerInfo *si;
636                                         char *cn, *em;
637                                         
638                                         si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
639                                         NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner);
640                                         
641                                         status = NSS_CMSSignerInfo_GetVerificationStatus(si);
642                                         
643                                         cn = NSS_CMSSignerInfo_GetSignerCommonName(si);
644                                         em = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
645                                         
646                                         g_string_append_printf(description, _("Signer: %s <%s>: %s\n"),
647                                                                cn?cn:"<unknown>", em?em:"<unknown>",
648                                                                sm_status_description(status));
649                                         
650                                         camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
651
652                                         if (cn)
653                                                 PORT_Free(cn);
654                                         if (em)
655                                                 PORT_Free(em);
656                                         
657                                         if (status != NSSCMSVS_GoodSignature)
658                                                 camel_cipher_validity_set_valid(valid, FALSE);
659                                 }
660                         }
661                         break;
662                 case SEC_OID_PKCS7_ENVELOPED_DATA:
663                         envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
664                         break;
665                 case SEC_OID_PKCS7_ENCRYPTED_DATA:
666                         encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
667                         break;
668                 case SEC_OID_PKCS7_DATA:
669                         break;
670                 default:
671                         break;
672                 }
673         }
674
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);
678
679         return valid;
680
681 fail:
682         camel_cipher_validity_free(valid);
683         g_string_free(description, TRUE);
684
685         return NULL;
686 }
687
688 static CamelCipherValidity *
689 sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
690 {
691         NSSCMSDecoderContext *dec;
692         NSSCMSMessage *cmsg;
693         CamelStreamMem *mem;
694         CamelStream *constream;
695         CamelCipherValidity *valid = NULL;
696         CamelContentType *ct;
697         const char *tmp;
698         CamelMimePart *sigpart;
699         CamelDataWrapper *dw;
700
701         dw = camel_medium_get_content_object((CamelMedium *)ipart);
702         ct = dw->mime_type;
703
704         /* FIXME: we should stream this to the decoder */
705         mem = (CamelStreamMem *)camel_stream_mem_new();
706         
707         if (camel_content_type_is(ct, "multipart", "signed")) {
708                 CamelMultipart *mps = (CamelMultipart *)dw;
709
710                 tmp = camel_content_type_param(ct, "protocol");
711                 if (!CAMEL_IS_MULTIPART_SIGNED(mps)
712                     || tmp == NULL
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"));
717                         goto fail;
718                 }
719
720                 constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
721                 if (constream == NULL)
722                         goto fail;
723
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"));
728                         goto fail;
729                 }
730         } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
731                 sigpart = ipart;
732         } else {
733                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
734                                       _("Cannot verify message signature: Incorrect message format"));
735                 goto fail;
736         }
737
738         dec = NSS_CMSDecoder_Start(NULL, 
739                                    NULL, NULL, /* content callback     */
740                                    sm_get_passwd, context,      /* password callback    */
741                                    NULL, NULL); /* decrypt key callback */
742
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);
746         if (cmsg == NULL) {
747                 camel_exception_setv(ex, 1, "Decoder failed");
748                 goto fail;
749         }
750         
751         valid = sm_verify_cmsg(context, cmsg, constream, ex);
752         
753         NSS_CMSMessage_Destroy(cmsg);
754 fail:
755         camel_object_unref(mem);
756         if (constream)
757                 camel_object_unref(constream);
758
759         return valid;
760 }
761
762 static int
763 sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
764 {
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;
771         int bulkkeysize, i;
772         CK_MECHANISM_TYPE type;
773         PK11SlotInfo *slot;
774         PLArenaPool *poolp;
775         NSSCMSMessage *cmsg = NULL;
776         NSSCMSEnvelopedData *envd;
777         NSSCMSEncoderContext *enc = NULL;
778         CamelStreamMem *mem;
779         CamelStream *ostream = NULL;
780         CamelDataWrapper *dw;
781         CamelContentType *ct;
782
783         poolp = PORT_NewArena(1024);
784         if (poolp == NULL) {
785                 camel_exception_setv(ex, 1, "Out of memory");
786                 return -1;
787         }
788
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");
793                 goto fail;
794         }
795
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]);
800                         goto fail;
801                 }
802         }
803
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");
807                 goto fail;
808         }
809
810         /* Generate a new bulk key based on the common algorithm - expensive */
811         type = PK11_AlgtagToMechanism(bulkalgtag);
812         slot = PK11_GetBestSlot(type, context);
813         if (slot == NULL) {
814                 /* PORT_GetError(); ?? */
815                 camel_exception_setv(ex, 1, "Can't allocate slot for encryption bulk key");
816                 goto fail;
817         }
818
819         bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context);
820         PK11_FreeSlot(slot);
821
822         /* Now we can start building the message */
823         /* msg->envelopedData->data */
824         cmsg = NSS_CMSMessage_Create(NULL);
825         if (cmsg == NULL) {
826                 camel_exception_setv(ex, 1, "Can't create CMS Message");
827                 goto fail;
828         }
829
830         envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize);
831         if (envd == NULL) {
832                 camel_exception_setv(ex, 1, "Can't create CMS EnvelopedData");
833                 goto fail;
834         }
835
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");
839                 goto fail;
840         }
841
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");
845                 goto fail;
846         }
847
848         /* add recipient certs */
849         for (i=0;recipient_certs[i];i++) {
850                 NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]);
851
852                 if (ri == NULL) {
853                         camel_exception_setv(ex, 1, "Can't create CMS RecipientInfo");
854                         goto fail;
855                 }
856
857                 if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) {
858                         camel_exception_setv(ex, 1, "Can't add CMS RecipientInfo");
859                         goto fail;
860                 }
861         }
862
863         /* dump it out */
864         ostream = camel_stream_mem_new();
865         enc = NSS_CMSEncoder_Start(cmsg,
866                                    sm_write_stream, ostream,
867                                    NULL, NULL,
868                                    sm_get_passwd, context,
869                                    sm_decrypt_key, bulkkey,
870                                    NULL, NULL);
871         if (enc == NULL) {
872                 camel_exception_setv(ex, 1, "Can't create encoder context");
873                 goto fail;
874         }
875
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");
884                 goto fail;
885         }
886         camel_object_unref(mem);
887
888         if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
889                 camel_exception_setv(ex, 1, "Failed to encode data");
890                 goto fail;
891         }
892
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);
898         
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;
903
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);
909
910         camel_medium_set_content_object((CamelMedium *)opart, dw);
911         camel_object_unref(dw);
912
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);
917
918         return 0;
919
920 fail:
921         if (ostream)
922                 camel_object_unref(ostream);
923         if (cmsg)
924                 NSS_CMSMessage_Destroy(cmsg);
925         if (bulkkey)
926                 PK11_FreeSymKey(bulkkey);
927
928         if (recipient_certs) {
929                 for (i=0;recipient_certs[i];i++)
930                         CERT_DestroyCertificate(recipient_certs[i]);
931         }
932
933         PORT_FreeArena(poolp, PR_FALSE);
934
935         return -1;
936 }
937
938 static CamelCipherValidity *
939 sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
940 {
941         NSSCMSDecoderContext *dec;
942         NSSCMSMessage *cmsg;
943         CamelStreamMem *istream;
944         CamelStream *ostream;
945         CamelCipherValidity *valid = NULL;
946
947         /* FIXME: This assumes the content is only encrypted.  Perhaps its ok for
948            this api to do this ... */
949
950         ostream = camel_stream_mem_new();
951
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);
956
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 */
961
962         if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) {
963                 printf("decoder update failed\n");
964         }
965         camel_object_unref(istream);
966
967         cmsg = NSS_CMSDecoder_Finish(dec);
968         if (cmsg == NULL) {
969                 camel_exception_setv(ex, 1, "Decoder failed, error %d", PORT_GetError());
970                 goto fail;
971         }
972
973 #if 0
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);
978                 goto fail;
979         }
980 #endif
981
982         camel_stream_reset(ostream);
983         camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
984
985         if (NSS_CMSMessage_IsSigned(cmsg)) {
986                 valid = sm_verify_cmsg(context, cmsg, NULL, ex);
987         } else {
988                 valid = camel_cipher_validity_new();
989                 valid->encrypt.description = g_strdup(_("Encrypted content"));
990                 valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
991         }
992
993         NSS_CMSMessage_Destroy(cmsg);
994 fail:
995         camel_object_unref(ostream);
996
997         return valid;
998 }
999
1000 static int
1001 sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex)
1002 {
1003         camel_exception_setv(ex, 1, "import keys: unimplemented");
1004
1005         return -1;
1006 }
1007
1008 static int
1009 sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
1010 {
1011         camel_exception_setv(ex, 1, "export keys: unimplemented");
1012
1013         return -1;
1014 }
1015
1016 /* ********************************************************************** */
1017
1018 static void
1019 camel_smime_context_class_init(CamelSMIMEContextClass *klass)
1020 {
1021         CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass);
1022         
1023         parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type()));
1024         
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;
1033 }
1034
1035 static void
1036 camel_smime_context_init(CamelSMIMEContext *context)
1037 {
1038         CamelCipherContext *cipher =(CamelCipherContext *) context;
1039         
1040         cipher->sign_protocol = "application/x-pkcs7-signature";
1041         cipher->encrypt_protocol = "application/x-pkcs7-mime";
1042         cipher->key_protocol = "application/x-pkcs7-signature";
1043
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;
1048 }
1049
1050 static void
1051 camel_smime_context_finalise(CamelObject *object)
1052 {
1053         CamelSMIMEContext *context = (CamelSMIMEContext *)object;
1054
1055         /* FIXME: do we have to free the certdb? */
1056
1057         g_free(context->priv);
1058 }
1059
1060 CamelType
1061 camel_smime_context_get_type(void)
1062 {
1063         static CamelType type = CAMEL_INVALID_TYPE;
1064         
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,
1071                                            NULL,
1072                                            (CamelObjectInitFunc) camel_smime_context_init,
1073                                            (CamelObjectFinalizeFunc) camel_smime_context_finalise);
1074         }
1075         
1076         return type;
1077 }
1078
1079 #endif /* HAVE_NSS */