Add minimalistic protected-headers support
[gpgol.git] / src / parsecontroller.cpp
1 /* @file parsecontroller.cpp
2  * @brief Parse a mail and decrypt / verify accordingly
3  *
4  * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik
5  * Software engineering by Intevation GmbH
6  *
7  * This file is part of GpgOL.
8  *
9  * GpgOL is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * GpgOL is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22 #include "config.h"
23
24 #include "parsecontroller.h"
25 #include "attachment.h"
26 #include "mimedataprovider.h"
27
28 #include "keycache.h"
29
30 #include <gpgme++/context.h>
31 #include <gpgme++/decryptionresult.h>
32 #include <gpgme++/key.h>
33
34 #include <sstream>
35
36 #ifdef HAVE_W32_SYSTEM
37 #include "common.h"
38 /* We use UTF-8 internally. */
39 #undef _
40 # define _(a) utf8_gettext (a)
41 #else
42 # define _(a) a
43 #endif
44
45
46 const char decrypt_template_html[] = {
47 "<html><head></head><body>"
48 "<table border=\"0\" width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\" bgcolor=\"#0069cc\">"
49 "<tr>"
50 "<td bgcolor=\"#0080ff\">"
51 "<p><span style=\"font-weight:600; background-color:#0080ff;\"><center>%s %s</center><span></p></td></tr>"
52 "<tr>"
53 "<td bgcolor=\"#e0f0ff\">"
54 "<center>"
55 "<br/>%s"
56 "</td></tr>"
57 "</table></body></html>"};
58
59 const char decrypt_template[] = {"%s %s\n\n%s"};
60
61 using namespace GpgME;
62
63 static bool
64 expect_no_headers (msgtype_t type)
65 {
66   TSTART;
67   TRETURN type != MSGTYPE_GPGOL_MULTIPART_SIGNED;
68 }
69
70 static bool
71 expect_no_mime (msgtype_t type)
72 {
73   TSTART;
74   TRETURN type == MSGTYPE_GPGOL_PGP_MESSAGE ||
75          type == MSGTYPE_GPGOL_CLEAR_SIGNED;
76 }
77
78 #ifdef BUILD_TESTS
79 static void
80 get_and_print_key_test (const char *fingerprint, GpgME::Protocol proto)
81 {
82   if (!fingerprint)
83     {
84       STRANGEPOINT;
85       return;
86     }
87   auto ctx = std::unique_ptr<GpgME::Context> (GpgME::Context::createForProtocol
88                                               (proto));
89
90   if (!ctx)
91     {
92       STRANGEPOINT;
93       return;
94     }
95   ctx->setKeyListMode (GpgME::KeyListMode::Local |
96                        GpgME::KeyListMode::Signatures |
97                        GpgME::KeyListMode::Validate |
98                        GpgME::KeyListMode::WithTofu);
99
100   GpgME::Error err;
101   const auto newKey = ctx->key (fingerprint, err, false);
102
103   std::stringstream ss;
104   ss << newKey;
105
106   log_debug ("Key: %s", ss.str().c_str());
107   return;
108 }
109 #endif
110
111 #ifdef HAVE_W32_SYSTEM
112 ParseController::ParseController(LPSTREAM instream, msgtype_t type):
113     m_inputprovider  (new MimeDataProvider(instream,
114                           expect_no_headers(type))),
115     m_outputprovider (new MimeDataProvider(expect_no_mime(type))),
116     m_type (type),
117     m_block_html (false)
118 {
119   TSTART;
120   memdbg_ctor ("ParseController");
121   log_data ("%s:%s: Creating parser for stream: %p of type %i"
122                    " expect no headers: %i expect no mime: %i",
123                    SRCNAME, __func__, instream, type,
124                    expect_no_headers (type), expect_no_mime (type));
125   TRETURN;
126 }
127 #endif
128
129 ParseController::ParseController(FILE *instream, msgtype_t type):
130     m_inputprovider  (new MimeDataProvider(instream,
131                           expect_no_headers(type))),
132     m_outputprovider (new MimeDataProvider(expect_no_mime(type))),
133     m_type (type),
134     m_block_html (false)
135 {
136   TSTART;
137   memdbg_ctor ("ParseController");
138   log_data ("%s:%s: Creating parser for stream: %p of type %i",
139                    SRCNAME, __func__, instream, type);
140   TRETURN;
141 }
142
143 ParseController::~ParseController()
144 {
145   TSTART;
146   log_debug ("%s:%s", SRCNAME, __func__);
147   memdbg_dtor ("ParseController");
148   delete m_inputprovider;
149   delete m_outputprovider;
150   TRETURN;
151 }
152
153 static void
154 operation_for_type(msgtype_t type, bool *decrypt,
155                    bool *verify)
156 {
157   *decrypt = false;
158   *verify = false;
159   switch (type)
160     {
161       case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
162       case MSGTYPE_GPGOL_PGP_MESSAGE:
163         *decrypt = true;
164         break;
165       case MSGTYPE_GPGOL_MULTIPART_SIGNED:
166       case MSGTYPE_GPGOL_CLEAR_SIGNED:
167         *verify = true;
168         break;
169       case MSGTYPE_GPGOL_OPAQUE_SIGNED:
170         *verify = true;
171         break;
172       case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
173         *decrypt = true;
174         break;
175       default:
176         log_error ("%s:%s: Unknown data type: %i",
177                    SRCNAME, __func__, type);
178     }
179 }
180
181 static bool
182 is_smime (Data &data)
183 {
184   TSTART;
185   data.seek (0, SEEK_SET);
186   auto id = data.type();
187   data.seek (0, SEEK_SET);
188   TRETURN id == Data::CMSSigned || id == Data::CMSEncrypted;
189 }
190
191 static std::string
192 format_recipients(GpgME::DecryptionResult result, Protocol protocol)
193 {
194   TSTART;
195   std::string msg;
196   for (const auto recipient: result.recipients())
197     {
198       auto ctx = Context::createForProtocol(protocol);
199       Error e;
200       if (!ctx) {
201           /* Can't happen */
202           TRACEPOINT;
203           continue;
204       }
205       const auto key = ctx->key(recipient.keyID(), e, false);
206       delete ctx;
207       if (!key.isNull() && key.numUserIDs() && !e) {
208         msg += std::string("<br/>") + key.userIDs()[0].id() + " (0x" + recipient.keyID() + ")";
209         continue;
210       }
211       msg += std::string("<br/>") + _("Unknown Key:") + " 0x" + recipient.keyID();
212     }
213   TRETURN msg;
214 }
215
216 static std::string
217 format_error(GpgME::DecryptionResult result, Protocol protocol)
218 {
219   TSTART;
220   char *buf;
221   bool no_sec = false;
222   std::string msg;
223
224   if (result.error ().isCanceled () ||
225       result.error ().code () == GPG_ERR_NO_SECKEY)
226     {
227        msg = _("Decryption canceled or timed out.");
228     }
229
230   if (result.error ().code () == GPG_ERR_DECRYPT_FAILED ||
231       result.error ().code () == GPG_ERR_NO_SECKEY)
232     {
233       no_sec = true;
234       for (const auto &recipient: result.recipients ()) {
235         no_sec &= (recipient.status ().code () == GPG_ERR_NO_SECKEY);
236       }
237     }
238
239   if (no_sec)
240     {
241       msg = _("No secret key found to decrypt the message. "
242               "It is encrypted to the following keys:");
243       msg += format_recipients (result, protocol);
244     }
245   else
246     {
247       msg = _("Could not decrypt the data: ");
248
249       if (result.isNull ())
250         {
251           msg += _("Failed to parse the mail.");
252         }
253       else if (result.isLegacyCipherNoMDC())
254         {
255           msg += _("Data is not integrity protected. "
256                    "Decrypting it could be a security problem. (no MDC)");
257         }
258       else
259         {
260           msg += result.error().asString();
261         }
262     }
263
264   if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html :
265                       decrypt_template,
266                       protocol == OpenPGP ? "OpenPGP" : "S/MIME",
267                       _("Encrypted message (decryption not possible)"),
268                       msg.c_str()) == -1)
269     {
270       log_error ("%s:%s:Failed to Format error.",
271                  SRCNAME, __func__);
272       TRETURN "Failed to Format error.";
273     }
274   msg = buf;
275   memdbg_alloc (buf);
276   xfree (buf);
277   TRETURN msg;
278 }
279
280 void
281 ParseController::setSender(const std::string &sender)
282 {
283   TSTART;
284   m_sender = sender;
285   TRETURN;
286 }
287
288 static void
289 handle_autocrypt_info (const autocrypt_s &info)
290 {
291   TSTART;
292   if (info.pref.size() && info.pref != "mutual")
293     {
294       log_debug ("%s:%s: Autocrypt preference is %s which is unhandled.",
295                  SRCNAME, __func__, info.pref.c_str());
296       TRETURN;
297     }
298
299 #ifndef BUILD_TESTS
300   if (!KeyCache::import_pgp_key_data (info.data))
301     {
302       log_error ("%s:%s: Failed to import", SRCNAME, __func__);
303     }
304 #endif
305   /* No need to free b64decoded as gpgme_data_release handles it */
306
307   TRETURN;
308 }
309
310 static bool
311 is_valid_chksum(const GpgME::Signature &sig)
312 {
313   TSTART;
314   const auto sum = sig.summary();
315   static unsigned int valid_mask = (unsigned int) (
316       GpgME::Signature::Valid |
317       GpgME::Signature::Green |
318       GpgME::Signature::KeyRevoked |
319       GpgME::Signature::KeyExpired |
320       GpgME::Signature::SigExpired |
321       GpgME::Signature::CrlMissing |
322       GpgME::Signature::CrlTooOld |
323       GpgME::Signature::TofuConflict );
324
325   TRETURN sum & valid_mask;
326 }
327
328
329 /* Note on stability:
330
331    Experiments have shown that we can have a crash if parse
332    Returns at time that is not good for the state of Outlook.
333
334    This happend in my test instance after a delay of > 1s < 3s
335    with a < 1% chance :-/
336
337    So if you have really really bad luck this might still crash
338    although it usually should be either much quicker or much slower
339    (slower e.g. when pinentry is requrired).
340 */
341 void
342 ParseController::parse()
343 {
344   TSTART;
345   // Wrap the input stream in an attachment / GpgME Data
346   Protocol protocol;
347   bool decrypt, verify;
348
349   Data input (m_inputprovider);
350
351   auto inputType = input.type ();
352
353   if (m_autocrypt_info.exists)
354     {
355       handle_autocrypt_info (m_autocrypt_info);
356     }
357
358   if (inputType == Data::Type::PGPSigned)
359     {
360       verify = true;
361       decrypt = false;
362     }
363   else
364     {
365       operation_for_type (m_type, &decrypt, &verify);
366     }
367
368   if ((m_inputprovider->signature() && is_smime (*m_inputprovider->signature())) ||
369       is_smime (input))
370     {
371       protocol = Protocol::CMS;
372     }
373   else
374     {
375       protocol = Protocol::OpenPGP;
376     }
377   auto ctx = std::unique_ptr<Context> (Context::createForProtocol (protocol));
378   if (!ctx)
379     {
380       log_error ("%s:%s:Failed to create context. Installation broken.",
381                  SRCNAME, __func__);
382       char *buf;
383       const char *proto = protocol == OpenPGP ? "OpenPGP" : "S/MIME";
384       if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html :
385                           decrypt_template,
386                           proto,
387                           _("Encrypted message (decryption not possible)"),
388                           _("Failed to find GnuPG please ensure that GnuPG or "
389                             "Gpg4win is properly installed.")) == -1)
390         {
391           log_error ("%s:%s:Failed format error.",
392                      SRCNAME, __func__);
393           /* Should never happen */
394           m_error = std::string("Bad installation");
395         }
396       memdbg_alloc (buf);
397       m_error = buf;
398       xfree (buf);
399       TRETURN;
400     }
401
402   /* Maybe a different option for this ? */
403   if (opt.autoretrieve)
404     {
405       ctx->setFlag("auto-key-retrieve", "1");
406     }
407
408   ctx->setArmor(true);
409
410   if (!m_sender.empty())
411     {
412       ctx->setSender(m_sender.c_str());
413     }
414
415   Data output (m_outputprovider);
416   log_debug ("%s:%s:%p decrypt: %i verify: %i with protocol: %s sender: %s type: %i",
417              SRCNAME, __func__, this,
418              decrypt, verify,
419              protocol == OpenPGP ? "OpenPGP" :
420              protocol == CMS ? "CMS" : "Unknown",
421              m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType);
422   if (decrypt)
423     {
424       input.seek (0, SEEK_SET);
425       TRACEPOINT;
426       auto combined_result = ctx->decryptAndVerify(input, output);
427       log_debug ("%s:%s:%p decrypt / verify done.",
428                  SRCNAME, __func__, this);
429       m_decrypt_result = combined_result.first;
430       m_verify_result = combined_result.second;
431
432       if ((!m_decrypt_result.error () &&
433           m_verify_result.signatures ().empty() &&
434           m_outputprovider->signature ()) ||
435           is_smime (output) ||
436           output.type() == Data::Type::PGPSigned)
437         {
438           TRACEPOINT;
439           /* There is a signature in the output. So we have
440              to verify it now as an extra step. */
441           input = Data (m_outputprovider);
442           delete m_inputprovider;
443           m_inputprovider = m_outputprovider;
444           m_outputprovider = new MimeDataProvider();
445           output = Data(m_outputprovider);
446           verify = true;
447           TRACEPOINT;
448         }
449       else
450         {
451           verify = false;
452         }
453       TRACEPOINT;
454       if (m_decrypt_result.error () || m_decrypt_result.isNull () ||
455           m_decrypt_result.error ().isCanceled ())
456         {
457           m_error = format_error (m_decrypt_result, protocol);
458         }
459     }
460   if (verify)
461     {
462       TRACEPOINT;
463       GpgME::Data *sig = m_inputprovider->signature();
464       input.seek (0, SEEK_SET);
465       if (sig)
466         {
467           sig->seek (0, SEEK_SET);
468           TRACEPOINT;
469           m_verify_result = ctx->verifyDetachedSignature(*sig, input);
470           log_debug ("%s:%s:%p verify done.",
471                      SRCNAME, __func__, this);
472           /* Copy the input to output to do a mime parsing. */
473           char buf[4096];
474           input.seek (0, SEEK_SET);
475           output.seek (0, SEEK_SET);
476           size_t nread;
477           while ((nread = input.read (buf, 4096)) > 0)
478             {
479               output.write (buf, nread);
480             }
481         }
482       else
483         {
484           TRACEPOINT;
485           m_verify_result = ctx->verifyOpaqueSignature(input, output);
486           TRACEPOINT;
487
488           const auto sigs = m_verify_result.signatures();
489           bool allBad = sigs.size();
490           for (const auto s :sigs)
491             {
492               if (!(s.summary() & Signature::Red))
493                 {
494                   allBad = false;
495                   break;
496                 }
497             }
498 #ifdef HAVE_W32_SYSTEM
499           if (allBad)
500             {
501               log_debug ("%s:%s:%p inline verify error trying native to utf8.",
502                          SRCNAME, __func__, this);
503
504
505               /* The proper solution would be to take the encoding from
506                  the mail / headers. Then convert the wchar body to that
507                  encoding. Verify, and convert it after verifcation to
508                  UTF-8 which the rest of the code expects.
509
510                  Or native_body from native ACP to InternetCodepage, then
511                  verify and convert the output back to utf8 as the rest
512                  expects.
513
514                  But as this is clearsigned and we don't really want that.
515                  Meh.
516                  */
517               char *utf8 = native_to_utf8 (input.toString().c_str());
518               if (utf8)
519                 {
520                   // Try again after conversion.
521                   ctx = std::unique_ptr<Context> (Context::createForProtocol (protocol));
522                   ctx->setArmor (true);
523                   if (!m_sender.empty())
524                     {
525                       ctx->setSender(m_sender.c_str());
526                     }
527
528                   input = Data (utf8, strlen (utf8));
529                   xfree (utf8);
530
531                   // Use a fresh output
532                   auto provider = new MimeDataProvider (true);
533
534                   // Warning: The dtor of the Data object touches
535                   // the provider. So we have to delete it after
536                   // the assignment.
537                   output = Data (provider);
538                   delete m_outputprovider;
539                   m_outputprovider = provider;
540
541                   // Try again
542                   m_verify_result = ctx->verifyOpaqueSignature(input, output);
543                 }
544             }
545 #else
546 (void)allBad;
547 #endif
548         }
549     }
550   log_debug ("%s:%s:%p: decrypt err: %i verify err: %i",
551              SRCNAME, __func__, this, m_decrypt_result.error().code(),
552              m_verify_result.error().code());
553
554   bool has_valid_encrypted_checksum = false;
555   /* Ensure that the Keys for the signatures are available
556      and if it has a valid encrypted checksum. */
557   for (const auto sig: m_verify_result.signatures())
558     {
559       TRACEPOINT;
560       has_valid_encrypted_checksum = is_valid_chksum (sig);
561
562 #ifndef BUILD_TESTS
563       KeyCache::instance ()->update (sig.fingerprint (), protocol);
564 #endif
565       TRACEPOINT;
566     }
567
568   if (protocol == Protocol::CMS && decrypt && !m_decrypt_result.error() &&
569       !has_valid_encrypted_checksum)
570     {
571       log_debug ("%s:%s:%p Encrypted S/MIME without checksum. Block HTML.",
572                  SRCNAME, __func__, this);
573       m_block_html = true;
574     }
575
576   /* Import any application/pgp-keys attachments if the option is set. */
577   if (opt.autoimport)
578     {
579       for (const auto &attach: get_attachments())
580         {
581           if (attach->get_content_type () == "application/pgp-keys")
582             {
583 #ifndef BUILD_TESTS
584               KeyCache::import_pgp_key_data (attach->get_data());
585 #endif
586             }
587         }
588     }
589
590   if (opt.enable_debug & DBG_DATA)
591     {
592       std::stringstream ss;
593       TRACEPOINT;
594       ss << m_decrypt_result << '\n' << m_verify_result;
595       for (const auto sig: m_verify_result.signatures())
596         {
597           const auto key = sig.key();
598           if (key.isNull())
599             {
600 #ifndef BUILD_TESTS
601               ss << '\n' << "Cached key:\n" << KeyCache::instance()->getByFpr(
602                   sig.fingerprint(), false);
603 #else
604               get_and_print_key_test (sig.fingerprint (), protocol);
605 #endif
606             }
607           else
608             {
609               ss << '\n' << key;
610             }
611         }
612        log_debug ("Decrypt / Verify result: %s", ss.str().c_str());
613     }
614   else
615     {
616       log_debug ("%s:%s:%p Decrypt / verify done errs: %i / %i numsigs: %i.",
617                  SRCNAME, __func__, this, m_decrypt_result.error().code(),
618                  m_verify_result.error().code(), m_verify_result.numSignatures());
619     }
620   TRACEPOINT;
621
622   if (m_outputprovider)
623     {
624       m_outputprovider->finalize ();
625     }
626
627   TRETURN;
628 }
629
630 const std::string
631 ParseController::get_html_body () const
632 {
633   TSTART;
634   if (m_outputprovider)
635     {
636       TRETURN m_outputprovider->get_html_body ();
637     }
638   else
639     {
640       TRETURN std::string();
641     }
642 }
643
644 const std::string
645 ParseController::get_body () const
646 {
647   TSTART;
648   if (m_outputprovider)
649     {
650       TRETURN m_outputprovider->get_body ();
651     }
652   else
653     {
654       TRETURN std::string();
655     }
656 }
657
658 const std::string
659 ParseController::get_body_charset() const
660 {
661   TSTART;
662   if (m_outputprovider)
663     {
664       TRETURN m_outputprovider->get_body_charset();
665     }
666   else
667     {
668       TRETURN std::string();
669     }
670 }
671
672 const std::string
673 ParseController::get_html_charset() const
674 {
675   TSTART;
676   if (m_outputprovider)
677     {
678       TRETURN m_outputprovider->get_html_charset();
679     }
680   else
681     {
682       TRETURN std::string();
683     }
684 }
685
686 std::vector<std::shared_ptr<Attachment> >
687 ParseController::get_attachments() const
688 {
689   TSTART;
690   if (m_outputprovider)
691     {
692       TRETURN m_outputprovider->get_attachments();
693     }
694   else
695     {
696       TRETURN std::vector<std::shared_ptr<Attachment> >();
697     }
698 }
699
700 std::string
701 ParseController::get_internal_subject() const
702 {
703   TSTART;
704   if (m_outputprovider)
705     {
706       TRETURN m_outputprovider->get_internal_subject ();
707     }
708   TRETURN std::string();
709 }