Fix FSF address (Tobias Mueller, #470445)
[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 Lesser 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 Lesser General Public License for more details.
22  *
23  *  You should have received a copy of the GNU Lesser General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32
33 #ifdef ENABLE_SMIME
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 <glib.h>
47 #include <glib/gi18n-lib.h>
48
49 #include "camel-data-wrapper.h"
50 #include "camel-exception.h"
51 #include "camel-mime-filter-basic.h"
52 #include "camel-mime-filter-canon.h"
53 #include "camel-mime-part.h"
54 #include "camel-multipart-signed.h"
55 #include "camel-operation.h"
56 #include "camel-smime-context.h"
57 #include "camel-stream-filter.h"
58 #include "camel-stream-fs.h"
59 #include "camel-stream-mem.h"
60
61 #define d(x)
62
63 struct _CamelSMIMEContextPrivate {
64         CERTCertDBHandle *certdb;
65
66         char *encrypt_key;
67         camel_smime_sign_t sign_mode;
68
69         int password_tries;
70         unsigned int send_encrypt_key_prefs:1;
71 };
72
73 static CamelCipherContextClass *parent_class = NULL;
74
75 /* used for decode content callback, for streaming decode */
76 static void
77 sm_write_stream(void *arg, const char *buf, unsigned long len)
78 {
79         camel_stream_write((CamelStream *)arg, buf, len);
80 }
81
82 static PK11SymKey *
83 sm_decrypt_key(void *arg, SECAlgorithmID *algid)
84 {
85         printf("Decrypt key called\n");
86         return (PK11SymKey *)arg;
87 }
88
89 /**
90  * camel_smime_context_new:
91  * @session: session
92  *
93  * Creates a new sm cipher context object.
94  *
95  * Returns a new sm cipher context object.
96  **/
97 CamelCipherContext *
98 camel_smime_context_new(CamelSession *session)
99 {
100         CamelCipherContext *cipher;
101         CamelSMIMEContext *ctx;
102         
103         g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
104         
105         ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type());
106         
107         cipher =(CamelCipherContext *) ctx;
108         cipher->session = session;
109         camel_object_ref(session);
110         
111         return cipher;
112 }
113
114 void
115 camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
116 {
117         context->priv->send_encrypt_key_prefs = use;
118         g_free(context->priv->encrypt_key);
119         context->priv->encrypt_key = g_strdup(key);
120 }
121
122 /* set signing mode, clearsigned multipart/signed or enveloped */
123 void
124 camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
125 {
126         context->priv->sign_mode = type;
127 }
128
129 /* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
130 guint32
131 camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
132 {
133         guint32 flags = 0;
134         CamelContentType *ct;
135         const char *tmp;
136
137         if (!part)
138                 return flags;
139
140         ct = camel_mime_part_get_content_type(part);
141
142         if (camel_content_type_is(ct, "multipart", "signed")) {
143                 tmp = camel_content_type_param(ct, "protocol");
144                 if (tmp &&
145                     (g_ascii_strcasecmp(tmp, ((CamelCipherContext *)context)->sign_protocol) == 0
146                      || g_ascii_strcasecmp(tmp, "application/pkcs7-signature") == 0))
147                         flags = CAMEL_SMIME_SIGNED;
148         } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
149                 CamelStreamMem *istream;
150                 NSSCMSMessage *cmsg;
151                 NSSCMSDecoderContext *dec;
152
153                 /* FIXME: stream this to the decoder incrementally */
154                 istream = (CamelStreamMem *)camel_stream_mem_new();
155                 camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)part), (CamelStream *)istream);
156                 camel_stream_reset((CamelStream *)istream);
157
158                 dec = NSS_CMSDecoder_Start(NULL, 
159                                            NULL, NULL,
160                                            NULL, NULL,  /* password callback    */
161                                            NULL, NULL); /* decrypt key callback */
162                 
163                 NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
164                 camel_object_unref(istream);
165                 
166                 cmsg = NSS_CMSDecoder_Finish(dec);
167                 if (cmsg) {
168                         if (NSS_CMSMessage_IsSigned(cmsg)) {
169                                 printf("message is signed\n");
170                                 flags |= CAMEL_SMIME_SIGNED;
171                         }
172                         
173                         if (NSS_CMSMessage_IsEncrypted(cmsg)) {
174                                 printf("message is encrypted\n");
175                                 flags |= CAMEL_SMIME_ENCRYPTED;
176                         }
177 #if 0
178                         if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
179                                 printf("message contains certs or crls\n");
180                                 flags |= CAMEL_SMIME_CERTS;
181                         }
182 #endif
183                         NSS_CMSMessage_Destroy(cmsg);
184                 } else {
185                         printf("Message could not be parsed\n");
186                 }
187         }
188
189         return flags;
190 }
191
192 static const char *
193 sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
194 {
195         switch(hash) {
196         case CAMEL_CIPHER_HASH_MD5:
197                 return "md5";
198         case CAMEL_CIPHER_HASH_SHA1:
199         case CAMEL_CIPHER_HASH_DEFAULT:
200                 return "sha1";
201         default:
202                 return NULL;
203         }
204 }
205
206 static CamelCipherHash
207 sm_id_to_hash(CamelCipherContext *context, const char *id)
208 {
209         if (id) {
210                 if (!strcmp(id, "md5"))
211                         return CAMEL_CIPHER_HASH_MD5;
212                 else if (!strcmp(id, "sha1"))
213                         return CAMEL_CIPHER_HASH_SHA1;
214         }
215         
216         return CAMEL_CIPHER_HASH_DEFAULT;
217 }
218
219 static NSSCMSMessage *
220 sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex)
221 {
222         struct _CamelSMIMEContextPrivate *p = context->priv;
223         NSSCMSMessage *cmsg = NULL;
224         NSSCMSContentInfo *cinfo;
225         NSSCMSSignedData *sigd;
226         NSSCMSSignerInfo *signerinfo;
227         CERTCertificate *cert= NULL, *ekpcert = NULL;
228
229         if ((cert = CERT_FindUserCertByUsage(p->certdb,
230                                              (char *)nick,
231                                              certUsageEmailSigner,
232                                              PR_FALSE,
233                                              NULL)) == NULL) {
234                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find certificate for '%s'"), nick);
235                 return NULL;
236         }
237
238         cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
239         if (cmsg == NULL) {
240                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS message"));
241                 goto fail;
242         }
243
244         if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
245                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS signed data"));
246                 goto fail;
247         }
248
249         cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
250         if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) {
251                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS signed data"));
252                 goto fail;
253         }
254
255         /* if !detatched, the contentinfo will alloc a data item for us */
256         cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
257         if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, detached) != SECSuccess) {
258                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS data"));
259                 goto fail;
260         }
261
262         signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
263         if (signerinfo == NULL) {
264                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS Signer information"));
265                 goto fail;
266         }
267
268         /* we want the cert chain included for this one */
269         if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) {
270                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find certificate chain"));
271                 goto fail;
272         }
273
274         /* SMIME RFC says signing time should always be added */
275         if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) {
276                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS Signing time"));
277                 goto fail;
278         }
279
280 #if 0
281         /* this can but needn't be added.  not sure what general usage is */
282         if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
283                 fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
284                 goto loser;
285         }
286 #endif
287
288         /* Check if we need to send along our return encrypt cert, rfc2633 2.5.3 */
289         if (p->send_encrypt_key_prefs) {
290                 CERTCertificate *enccert = NULL;
291
292                 if (p->encrypt_key) {
293                         /* encrypt key has its own nick */
294                         if ((ekpcert = CERT_FindUserCertByUsage(
295                                      p->certdb,
296                                      p->encrypt_key,
297                                      certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
298                                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Encryption certificate for '%s' does not exist"), p->encrypt_key);
299                                 goto fail;
300                         }
301                         enccert = ekpcert;
302                 } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) {
303                         /* encrypt key is signing key */
304                         enccert = cert;
305                 } else {
306                         /* encrypt key uses same nick */
307                         if ((ekpcert = CERT_FindUserCertByUsage(
308                                      p->certdb, (char *)nick,
309                                      certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
310                                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Encryption certificate for '%s' does not exist"), nick);
311                                 goto fail;
312                         }
313                         enccert = ekpcert;
314                 }
315
316                 if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
317                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add SMIMEEncKeyPrefs attribute"));
318                         goto fail;
319                 }
320
321                 if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
322                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add MS SMIMEEncKeyPrefs attribute"));
323                         goto fail;
324                 }
325
326                 if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
327                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add encryption certificate"));
328                         goto fail;
329                 }
330         }
331
332         if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
333                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS Signer information"));
334                 goto fail;
335         }
336
337         if (ekpcert)
338                 CERT_DestroyCertificate(ekpcert);
339
340         if (cert)
341                 CERT_DestroyCertificate(cert);
342
343         return cmsg;
344 fail:
345         if (ekpcert)
346                 CERT_DestroyCertificate(ekpcert);
347
348         if (cert)
349                 CERT_DestroyCertificate(cert);
350
351         NSS_CMSMessage_Destroy(cmsg);
352
353         return NULL;
354 }
355
356 static int
357 sm_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
358 {
359         int res = -1;
360         NSSCMSMessage *cmsg;
361         CamelStream *ostream, *istream;
362         SECOidTag sechash;
363         NSSCMSEncoderContext *enc;
364         CamelDataWrapper *dw;
365         CamelContentType *ct;
366
367         switch (hash) {
368         case CAMEL_CIPHER_HASH_SHA1:
369         case CAMEL_CIPHER_HASH_DEFAULT:
370         default:
371                 sechash = SEC_OID_SHA1;
372                 break;
373         case CAMEL_CIPHER_HASH_MD5:
374                 sechash = SEC_OID_MD5;
375                 break;
376         }
377
378         cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)context, userid, sechash,
379                                      ((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
380         if (cmsg == NULL)
381                 return -1;
382
383         ostream = camel_stream_mem_new();
384
385         /* FIXME: stream this, we stream output at least */
386         istream = camel_stream_mem_new();
387         if (camel_cipher_canonical_to_stream(ipart,
388                                              CAMEL_MIME_FILTER_CANON_STRIP
389                                              |CAMEL_MIME_FILTER_CANON_CRLF
390                                              |CAMEL_MIME_FILTER_CANON_FROM, istream) == -1) {
391                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
392                                      _("Could not generate signing data: %s"), g_strerror(errno));
393                 goto fail;
394         }
395
396         enc = NSS_CMSEncoder_Start(cmsg, 
397                                    sm_write_stream, ostream, /* DER output callback  */
398                                    NULL, NULL,     /* destination storage  */
399                                    NULL, NULL,     /* password callback    */
400                                    NULL, NULL,     /* decrypt key callback */
401                                    NULL, NULL );   /* detached digests    */
402         if (!enc) {
403                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create encoder context"));
404                 goto fail;
405         }
406
407         if (NSS_CMSEncoder_Update(enc, ((CamelStreamMem *)istream)->buffer->data, ((CamelStreamMem *)istream)->buffer->len) != SECSuccess) {
408                 NSS_CMSEncoder_Cancel(enc);
409                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to add data to CMS encoder"));
410                 goto fail;
411         }
412
413         if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
414                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to encode data"));
415                 goto fail;
416         }
417
418         res = 0;
419
420         dw = camel_data_wrapper_new();
421         camel_stream_reset(ostream);
422         camel_data_wrapper_construct_from_stream(dw, ostream);
423         dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
424
425         if (((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
426                 CamelMultipartSigned *mps;
427                 CamelMimePart *sigpart;
428
429                 sigpart = camel_mime_part_new();
430                 ct = camel_content_type_new("application", "x-pkcs7-signature");
431                 camel_content_type_set_param(ct, "name", "smime.p7s");
432                 camel_data_wrapper_set_mime_type_field(dw, ct);
433                 camel_content_type_unref(ct);
434
435                 camel_medium_set_content_object((CamelMedium *)sigpart, dw);
436
437                 camel_mime_part_set_filename(sigpart, "smime.p7s");
438                 camel_mime_part_set_disposition(sigpart, "attachment");
439                 camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64);
440
441                 mps = camel_multipart_signed_new();
442                 ct = camel_content_type_new("multipart", "signed");
443                 camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
444                 camel_content_type_set_param(ct, "protocol", context->sign_protocol);
445                 camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
446                 camel_content_type_unref(ct);
447                 camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
448
449                 mps->signature = sigpart;
450                 mps->contentraw = istream;
451                 camel_stream_reset(istream);
452                 camel_object_ref(istream);
453                 
454                 camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
455         } else {
456                 ct = camel_content_type_new("application", "x-pkcs7-mime");
457                 camel_content_type_set_param(ct, "name", "smime.p7m");
458                 camel_content_type_set_param(ct, "smime-type", "signed-data");
459                 camel_data_wrapper_set_mime_type_field(dw, ct);
460                 camel_content_type_unref(ct);
461                 
462                 camel_medium_set_content_object((CamelMedium *)opart, dw);
463
464                 camel_mime_part_set_filename(opart, "smime.p7m");
465                 camel_mime_part_set_description(opart, "S/MIME Signed Message");
466                 camel_mime_part_set_disposition(opart, "attachment");
467                 camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
468         }
469         
470         camel_object_unref(dw);
471 fail:   
472         camel_object_unref(ostream);
473         camel_object_unref(istream);
474
475         return res;
476 }
477
478 static const char *
479 sm_status_description(NSSCMSVerificationStatus status)
480 {
481         /* could use this but then we can't control i18n? */
482         /*NSS_CMSUtil_VerificationStatusToString(status));*/
483
484         switch(status) {
485         case NSSCMSVS_Unverified:
486         default:
487                 return _("Unverified");
488         case NSSCMSVS_GoodSignature:
489                 return _("Good signature");
490         case NSSCMSVS_BadSignature:
491                 return _("Bad signature");
492         case NSSCMSVS_DigestMismatch:
493                 return _("Content tampered with or altered in transit");
494         case NSSCMSVS_SigningCertNotFound:
495                 return _("Signing certificate not found");
496         case NSSCMSVS_SigningCertNotTrusted:
497                 return _("Signing certificate not trusted");
498         case NSSCMSVS_SignatureAlgorithmUnknown:
499                 return _("Signature algorithm unknown");
500         case NSSCMSVS_SignatureAlgorithmUnsupported:
501                 return _("Signature algorithm unsupported");
502         case NSSCMSVS_MalformedSignature:
503                 return _("Malformed signature");
504         case NSSCMSVS_ProcessingError:
505                 return _("Processing error");
506         }
507 }
508
509 static CamelCipherValidity *
510 sm_verify_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
511 {
512         struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
513         NSSCMSSignedData *sigd = NULL;
514         NSSCMSEnvelopedData *envd;
515         NSSCMSEncryptedData *encd;
516         SECAlgorithmID **digestalgs;
517         NSSCMSDigestContext *digcx;
518         int count, i, nsigners, j;
519         SECItem **digests;
520         PLArenaPool *poolp = NULL;
521         CamelStreamMem *mem;
522         NSSCMSVerificationStatus status;
523         CamelCipherValidity *valid;
524         GString *description;
525
526         description = g_string_new("");
527         valid = camel_cipher_validity_new();
528         camel_cipher_validity_set_valid(valid, TRUE);
529         status = NSSCMSVS_Unverified;
530
531         /* NB: this probably needs to go into a decoding routine that can be used for processing
532            enveloped data too */
533         count = NSS_CMSMessage_ContentLevelCount(cmsg);
534         for (i = 0; i < count; i++) {
535                 NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg, i);
536                 SECOidTag typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
537
538                 switch (typetag) {
539                 case SEC_OID_PKCS7_SIGNED_DATA:
540                         sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
541                         if (sigd == NULL) {
542                                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("No signed data in signature"));
543                                 goto fail;
544                         }
545
546                         /* need to build digests of the content */
547                         if (!NSS_CMSSignedData_HasDigests(sigd)) {
548                                 if (extstream == NULL) {
549                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Digests missing from enveloped data"));
550                                         goto fail;
551                                 }
552
553                                 if ((poolp = PORT_NewArena(1024)) == NULL) {
554                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
555                                         goto fail;
556                                 }
557
558                                 digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
559                                 
560                                 digcx = NSS_CMSDigestContext_StartMultiple(digestalgs);
561                                 if (digcx == NULL) {
562                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot calculate digests"));
563                                         goto fail;
564                                 }
565
566                                 mem = (CamelStreamMem *)camel_stream_mem_new();
567                                 camel_stream_write_to_stream(extstream, (CamelStream *)mem);
568                                 NSS_CMSDigestContext_Update(digcx, mem->buffer->data, mem->buffer->len);
569                                 camel_object_unref(mem);
570
571                                 if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) {
572                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot calculate digests"));
573                                         goto fail;
574                                 }
575
576                                 if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) {
577                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot set message digests"));
578                                         goto fail;
579                                 }
580
581                                 PORT_FreeArena(poolp, PR_FALSE);
582                                 poolp = NULL;
583                         }
584
585                         /* import all certificates present */
586                         if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_TRUE) != SECSuccess) {
587                                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Certificate import failed"));
588                                 goto fail;
589                         }
590
591                         if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailRecipient, PR_TRUE) != SECSuccess) {
592                                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Certificate import failed"));
593                                 goto fail;
594                         }
595
596                         /* check for certs-only message */
597                         nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
598                         if (nsigners == 0) {
599                                                           
600                                 /* already imported certs above, not sure what usage we should use here or if this isn't handled above */
601                                 if (NSS_CMSSignedData_VerifyCertsOnly(sigd, p->certdb, certUsageEmailSigner) != SECSuccess) {
602                                         g_string_printf(description, _("Certificate is the only message, cannot verify certificates"));
603                                 } else {
604                                         status = NSSCMSVS_GoodSignature;
605                                         g_string_printf(description, _("Certificate is the only message, certificates imported and verified"));
606                                 }
607                         } else {
608                                 if (!NSS_CMSSignedData_HasDigests(sigd)) {
609                                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find signature digests"));
610                                         goto fail;
611                                 }
612
613                                 for (j = 0; j < nsigners; j++) {
614                                         NSSCMSSignerInfo *si;
615                                         char *cn, *em;
616                                         
617                                         si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
618                                         NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner);
619                                         
620                                         status = NSS_CMSSignerInfo_GetVerificationStatus(si);
621                                         
622                                         cn = NSS_CMSSignerInfo_GetSignerCommonName(si);
623                                         em = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
624                                         
625                                         g_string_append_printf(description, _("Signer: %s <%s>: %s\n"),
626                                                                cn?cn:"<unknown>", em?em:"<unknown>",
627                                                                sm_status_description(status));
628                                         
629                                         camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
630
631                                         if (cn)
632                                                 PORT_Free(cn);
633                                         if (em)
634                                                 PORT_Free(em);
635                                         
636                                         if (status != NSSCMSVS_GoodSignature)
637                                                 camel_cipher_validity_set_valid(valid, FALSE);
638                                 }
639                         }
640                         break;
641                 case SEC_OID_PKCS7_ENVELOPED_DATA:
642                         envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
643                         break;
644                 case SEC_OID_PKCS7_ENCRYPTED_DATA:
645                         encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
646                         break;
647                 case SEC_OID_PKCS7_DATA:
648                         break;
649                 default:
650                         break;
651                 }
652         }
653
654         camel_cipher_validity_set_valid(valid, status == NSSCMSVS_GoodSignature);
655         camel_cipher_validity_set_description(valid, description->str);
656         g_string_free(description, TRUE);
657
658         return valid;
659
660 fail:
661         camel_cipher_validity_free(valid);
662         g_string_free(description, TRUE);
663
664         return NULL;
665 }
666
667 static CamelCipherValidity *
668 sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
669 {
670         NSSCMSDecoderContext *dec;
671         NSSCMSMessage *cmsg;
672         CamelStreamMem *mem;
673         CamelStream *constream = NULL;
674         CamelCipherValidity *valid = NULL;
675         CamelContentType *ct;
676         const char *tmp;
677         CamelMimePart *sigpart;
678         CamelDataWrapper *dw;
679
680         dw = camel_medium_get_content_object((CamelMedium *)ipart);
681         ct = dw->mime_type;
682
683         /* FIXME: we should stream this to the decoder */
684         mem = (CamelStreamMem *)camel_stream_mem_new();
685         
686         if (camel_content_type_is(ct, "multipart", "signed")) {
687                 CamelMultipart *mps = (CamelMultipart *)dw;
688
689                 tmp = camel_content_type_param(ct, "protocol");
690                 if (!CAMEL_IS_MULTIPART_SIGNED(mps)
691                     || tmp == NULL
692                     || (g_ascii_strcasecmp(tmp, context->sign_protocol) != 0
693                         && g_ascii_strcasecmp(tmp, "application/pkcs7-signature") != 0)) {
694                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
695                                              _("Cannot verify message signature: Incorrect message format"));
696                         goto fail;
697                 }
698
699                 constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
700                 if (constream == NULL)
701                         goto fail;
702
703                 sigpart = camel_multipart_get_part(mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
704                 if (sigpart == NULL) {
705                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
706                                              _("Cannot verify message signature: Incorrect message format"));
707                         goto fail;
708                 }
709         } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
710                 sigpart = ipart;
711         } else {
712                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
713                                      _("Cannot verify message signature: Incorrect message format"));
714                 goto fail;
715         }
716
717         dec = NSS_CMSDecoder_Start(NULL, 
718                                    NULL, NULL, /* content callback     */
719                                    NULL, NULL,  /* password callback    */
720                                    NULL, NULL); /* decrypt key callback */
721
722         camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)sigpart), (CamelStream *)mem);
723         (void)NSS_CMSDecoder_Update(dec, mem->buffer->data, mem->buffer->len);
724         cmsg = NSS_CMSDecoder_Finish(dec);
725         if (cmsg == NULL) {
726                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Decoder failed"));
727                 goto fail;
728         }
729         
730         valid = sm_verify_cmsg(context, cmsg, constream, ex);
731         
732         NSS_CMSMessage_Destroy(cmsg);
733 fail:
734         camel_object_unref(mem);
735         if (constream)
736                 camel_object_unref(constream);
737
738         return valid;
739 }
740
741 static int
742 sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
743 {
744         struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
745         /*NSSCMSRecipientInfo **recipient_infos;*/
746         CERTCertificate **recipient_certs = NULL;
747         NSSCMSContentInfo *cinfo;
748         PK11SymKey *bulkkey = NULL;
749         SECOidTag bulkalgtag;
750         int bulkkeysize, i;
751         CK_MECHANISM_TYPE type;
752         PK11SlotInfo *slot;
753         PLArenaPool *poolp;
754         NSSCMSMessage *cmsg = NULL;
755         NSSCMSEnvelopedData *envd;
756         NSSCMSEncoderContext *enc = NULL;
757         CamelStreamMem *mem;
758         CamelStream *ostream = NULL;
759         CamelDataWrapper *dw;
760         CamelContentType *ct;
761
762         poolp = PORT_NewArena(1024);
763         if (poolp == NULL) {
764                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
765                 return -1;
766         }
767
768         /* Lookup all recipients certs, for later working */
769         recipient_certs = (CERTCertificate **)PORT_ArenaZAlloc(poolp, sizeof(*recipient_certs[0])*(recipients->len + 1));
770         if (recipient_certs == NULL) {
771                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
772                 goto fail;
773         }
774
775         for (i=0;i<recipients->len;i++) {
776                 recipient_certs[i] = CERT_FindCertByNicknameOrEmailAddr(p->certdb, recipients->pdata[i]);
777                 if (recipient_certs[i] == NULL) {
778                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find certificate for `%s'"), recipients->pdata[i]);
779                         goto fail;
780                 }
781         }
782
783         /* Find a common algorithm, probably 3DES anyway ... */
784         if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) {
785                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find common bulk encryption algorithm"));
786                 goto fail;
787         }
788
789         /* Generate a new bulk key based on the common algorithm - expensive */
790         type = PK11_AlgtagToMechanism(bulkalgtag);
791         slot = PK11_GetBestSlot(type, context);
792         if (slot == NULL) {
793                 /* PORT_GetError(); ?? */
794                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot allocate slot for encryption bulk key"));
795                 goto fail;
796         }
797
798         bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context);
799         PK11_FreeSlot(slot);
800
801         /* Now we can start building the message */
802         /* msg->envelopedData->data */
803         cmsg = NSS_CMSMessage_Create(NULL);
804         if (cmsg == NULL) {
805                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS Message"));
806                 goto fail;
807         }
808
809         envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize);
810         if (envd == NULL) {
811                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS Enveloped data"));
812                 goto fail;
813         }
814
815         cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
816         if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) {
817                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS Enveloped data"));
818                 goto fail;
819         }
820
821         cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
822         if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) {
823                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS data object"));
824                 goto fail;
825         }
826
827         /* add recipient certs */
828         for (i=0;recipient_certs[i];i++) {
829                 NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]);
830
831                 if (ri == NULL) {
832                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS Recipient information"));
833                         goto fail;
834                 }
835
836                 if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) {
837                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS Recipient information"));
838                         goto fail;
839                 }
840         }
841
842         /* dump it out */
843         ostream = camel_stream_mem_new();
844         enc = NSS_CMSEncoder_Start(cmsg,
845                                    sm_write_stream, ostream,
846                                    NULL, NULL,
847                                    NULL, NULL,
848                                    sm_decrypt_key, bulkkey,
849                                    NULL, NULL);
850         if (enc == NULL) {
851                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create encoder context"));
852                 goto fail;
853         }
854
855         /* FIXME: Stream the input */
856         /* FIXME: Canonicalise the input? */
857         mem = (CamelStreamMem *)camel_stream_mem_new();
858         camel_data_wrapper_write_to_stream((CamelDataWrapper *)ipart, (CamelStream *)mem);
859         if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) {
860                 NSS_CMSEncoder_Cancel(enc);
861                 camel_object_unref(mem);
862                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to add data to encoder"));
863                 goto fail;
864         }
865         camel_object_unref(mem);
866
867         if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
868                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to encode data"));
869                 goto fail;
870         }
871
872         PK11_FreeSymKey(bulkkey);
873         NSS_CMSMessage_Destroy(cmsg);
874         for (i=0;recipient_certs[i];i++)
875                 CERT_DestroyCertificate(recipient_certs[i]);
876         PORT_FreeArena(poolp, PR_FALSE);
877         
878         dw = camel_data_wrapper_new();
879         camel_data_wrapper_construct_from_stream(dw, ostream);
880         camel_object_unref(ostream);
881         dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
882
883         ct = camel_content_type_new("application", "x-pkcs7-mime");
884         camel_content_type_set_param(ct, "name", "smime.p7m");
885         camel_content_type_set_param(ct, "smime-type", "enveloped-data");
886         camel_data_wrapper_set_mime_type_field(dw, ct);
887         camel_content_type_unref(ct);
888
889         camel_medium_set_content_object((CamelMedium *)opart, dw);
890         camel_object_unref(dw);
891
892         camel_mime_part_set_disposition(opart, "attachment");
893         camel_mime_part_set_filename(opart, "smime.p7m");
894         camel_mime_part_set_description(opart, "S/MIME Encrypted Message");
895         camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
896
897         return 0;
898
899 fail:
900         if (ostream)
901                 camel_object_unref(ostream);
902         if (cmsg)
903                 NSS_CMSMessage_Destroy(cmsg);
904         if (bulkkey)
905                 PK11_FreeSymKey(bulkkey);
906
907         if (recipient_certs) {
908                 for (i=0;recipient_certs[i];i++)
909                         CERT_DestroyCertificate(recipient_certs[i]);
910         }
911
912         PORT_FreeArena(poolp, PR_FALSE);
913
914         return -1;
915 }
916
917 static CamelCipherValidity *
918 sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
919 {
920         NSSCMSDecoderContext *dec;
921         NSSCMSMessage *cmsg;
922         CamelStreamMem *istream;
923         CamelStream *ostream;
924         CamelCipherValidity *valid = NULL;
925
926         /* FIXME: This assumes the content is only encrypted.  Perhaps its ok for
927            this api to do this ... */
928
929         ostream = camel_stream_mem_new();
930         camel_stream_mem_set_secure((CamelStreamMem *)ostream);
931
932         /* FIXME: stream this to the decoder incrementally */
933         istream = (CamelStreamMem *)camel_stream_mem_new();
934         camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)ipart), (CamelStream *)istream);
935         camel_stream_reset((CamelStream *)istream);
936
937         dec = NSS_CMSDecoder_Start(NULL, 
938                                    sm_write_stream, ostream, /* content callback     */
939                                    NULL, NULL,
940                                    NULL, NULL); /* decrypt key callback */
941
942         if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) {
943                 printf("decoder update failed\n");
944         }
945         camel_object_unref(istream);
946
947         cmsg = NSS_CMSDecoder_Finish(dec);
948         if (cmsg == NULL) {
949                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Decoder failed, error %d"), PORT_GetError());
950                 goto fail;
951         }
952
953 #if 0
954         /* not sure if we really care about this? */
955         if (!NSS_CMSMessage_IsEncrypted(cmsg)) {
956                 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("S/MIME Decrypt: No encrypted content found"));
957                 NSS_CMSMessage_Destroy(cmsg);
958                 goto fail;
959         }
960 #endif
961
962         camel_stream_reset(ostream);
963         camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
964
965         if (NSS_CMSMessage_IsSigned(cmsg)) {
966                 valid = sm_verify_cmsg(context, cmsg, NULL, ex);
967         } else {
968                 valid = camel_cipher_validity_new();
969                 valid->encrypt.description = g_strdup(_("Encrypted content"));
970                 valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
971         }
972
973         NSS_CMSMessage_Destroy(cmsg);
974 fail:
975         camel_object_unref(ostream);
976
977         return valid;
978 }
979
980 static int
981 sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex)
982 {
983         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("import keys: unimplemented"));
984         
985         return -1;
986 }
987
988 static int
989 sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
990 {
991         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("export keys: unimplemented"));
992         
993         return -1;
994 }
995
996 /* ********************************************************************** */
997
998 static void
999 camel_smime_context_class_init(CamelSMIMEContextClass *klass)
1000 {
1001         CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass);
1002         
1003         parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type()));
1004         
1005         cipher_class->hash_to_id = sm_hash_to_id;
1006         cipher_class->id_to_hash = sm_id_to_hash;
1007         cipher_class->sign = sm_sign;
1008         cipher_class->verify = sm_verify;
1009         cipher_class->encrypt = sm_encrypt;
1010         cipher_class->decrypt = sm_decrypt;
1011         cipher_class->import_keys = sm_import_keys;
1012         cipher_class->export_keys = sm_export_keys;
1013 }
1014
1015 static void
1016 camel_smime_context_init(CamelSMIMEContext *context)
1017 {
1018         CamelCipherContext *cipher =(CamelCipherContext *) context;
1019         
1020         cipher->sign_protocol = "application/x-pkcs7-signature";
1021         cipher->encrypt_protocol = "application/x-pkcs7-mime";
1022         cipher->key_protocol = "application/x-pkcs7-signature";
1023
1024         context->priv = g_malloc0(sizeof(*context->priv));
1025         context->priv->certdb = CERT_GetDefaultCertDB();
1026         context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
1027         context->priv->password_tries = 0;
1028 }
1029
1030 static void
1031 camel_smime_context_finalise(CamelObject *object)
1032 {
1033         CamelSMIMEContext *context = (CamelSMIMEContext *)object;
1034
1035         /* FIXME: do we have to free the certdb? */
1036
1037         g_free(context->priv);
1038 }
1039
1040 CamelType
1041 camel_smime_context_get_type(void)
1042 {
1043         static CamelType type = CAMEL_INVALID_TYPE;
1044         
1045         if (type == CAMEL_INVALID_TYPE) {
1046                 type = camel_type_register(camel_cipher_context_get_type(),
1047                                            "CamelSMIMEContext",
1048                                            sizeof(CamelSMIMEContext),
1049                                            sizeof(CamelSMIMEContextClass),
1050                                            (CamelObjectClassInitFunc) camel_smime_context_class_init,
1051                                            NULL,
1052                                            (CamelObjectInitFunc) camel_smime_context_init,
1053                                            (CamelObjectFinalizeFunc) camel_smime_context_finalise);
1054         }
1055         
1056         return type;
1057 }
1058
1059 #endif /* ENABLE_SMIME */