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