[PATCH 08/11] Add signature verification of PGP/MIME-signed parts with --verify.
authorJameson Graef Rollins <jrollins@finestructure.net>
Thu, 26 May 2011 01:01:17 +0000 (18:01 +1700)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:38:22 +0000 (09:38 -0800)
13/086868f1fa72d94673817cba0794c70f35c566 [new file with mode: 0644]

diff --git a/13/086868f1fa72d94673817cba0794c70f35c566 b/13/086868f1fa72d94673817cba0794c70f35c566
new file mode 100644 (file)
index 0000000..0ad4076
--- /dev/null
@@ -0,0 +1,461 @@
+Return-Path: <jrollins@servo.finestructure.net>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 6C579429E5A\r
+       for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:46 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -1.921\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-1.921 tagged_above=-999 required=5\r
+       tests=[NO_DNS_FOR_FROM=0.379, RCVD_IN_DNSWL_MED=-2.3]\r
+       autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id V6WZpmSd4Ove for <notmuch@notmuchmail.org>;\r
+       Wed, 25 May 2011 18:01:43 -0700 (PDT)\r
+Received: from outgoing-mail.its.caltech.edu (outgoing-mail.its.caltech.edu\r
+       [131.215.239.19])\r
+       by olra.theworths.org (Postfix) with ESMTP id 1CA9A429E30\r
+       for <notmuch@notmuchmail.org>; Wed, 25 May 2011 18:01:37 -0700 (PDT)\r
+Received: from earth-doxen.imss.caltech.edu (localhost [127.0.0.1])\r
+       by earth-doxen-postvirus (Postfix) with ESMTP id AC13666E049A;\r
+       Wed, 25 May 2011 18:01:35 -0700 (PDT)\r
+X-Spam-Scanned: at Caltech-IMSS on earth-doxen by amavisd-new\r
+Received: from servo.finestructure.net (gwave-104.ligo.caltech.edu\r
+       [131.215.114.104]) (Authenticated sender: jrollins)\r
+       by earth-doxen-submit (Postfix) with ESMTP id AC04366E04AC;\r
+       Wed, 25 May 2011 18:01:27 -0700 (PDT)\r
+Received: by servo.finestructure.net (Postfix, from userid 1000)\r
+       id 801DD7CA; Wed, 25 May 2011 18:01:26 -0700 (PDT)\r
+From: Jameson Graef Rollins <jrollins@finestructure.net>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH 08/11] Add signature verification of PGP/MIME-signed parts\r
+       with --verify.\r
+Date: Wed, 25 May 2011 18:01:17 -0700\r
+Message-Id: <1306371680-19441-9-git-send-email-jrollins@finestructure.net>\r
+X-Mailer: git-send-email 1.7.4.4\r
+In-Reply-To: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>\r
+References: <1306371680-19441-1-git-send-email-jrollins@finestructure.net>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Thu, 26 May 2011 01:01:47 -0000\r
+\r
+This is primarily for notmuch-show, although the functionality is\r
+added to show-message.  Once signatures are processed a new\r
+part_sigstatus formatter is emitted, and the entire multipart/signed\r
+part is replaced with the contents of the signed part.\r
+\r
+At the moment only a json part_sigstatus formatting function is\r
+available.  Emacs support to follow.\r
+\r
+The original work for this patch was done by\r
+\r
+  Daniel Kahn Gillmor <dkg@fifthhorseman.net>\r
+\r
+whose help with this functionality I greatly appreciate.\r
+---\r
+ Makefile.local          |    1 +\r
+ notmuch-client.h        |    5 ++\r
+ notmuch-gmime-session.c |   49 ++++++++++++++++++++++++\r
+ notmuch-reply.c         |    2 +\r
+ notmuch-show.c          |   96 +++++++++++++++++++++++++++++++++++++++++++++++\r
+ notmuch.1               |   11 +++++\r
+ notmuch.c               |    8 ++++\r
+ show-message.c          |   41 +++++++++++++++++++-\r
+ 8 files changed, 212 insertions(+), 1 deletions(-)\r
+ create mode 100644 notmuch-gmime-session.c\r
+\r
+diff --git a/Makefile.local b/Makefile.local\r
+index 8a8832d..f726f1f 100644\r
+--- a/Makefile.local\r
++++ b/Makefile.local\r
+@@ -246,6 +246,7 @@ notmuch_client_srcs =              \\r
+       notmuch-show.c          \\r
+       notmuch-tag.c           \\r
+       notmuch-time.c          \\r
++      notmuch-gmime-session.c \\r
+       query-string.c          \\r
+       show-message.c          \\r
+       json.c                  \\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index b278bc7..dc4ed7a 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -67,6 +67,7 @@ typedef struct notmuch_show_format {\r
+     const char *body_start;\r
+     void (*part_start) (GMimeObject *part,\r
+                       int *part_count);\r
++    void (*part_sigstatus) (const GMimeSignatureValidity* validity);\r
+     void (*part_content) (GMimeObject *part);\r
+     void (*part_end) (GMimeObject *part);\r
+     const char *part_sep;\r
+@@ -80,6 +81,7 @@ typedef struct notmuch_show_params {\r
+     int entire_thread;\r
+     int raw;\r
+     int part;\r
++    GMimeCipherContext* cryptoctx;\r
+ } notmuch_show_params_t;\r
\r
+ /* There's no point in continuing when we've detected that we've done\r
+@@ -233,4 +235,7 @@ notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,\r
+ notmuch_bool_t\r
+ debugger_is_active (void);\r
\r
++GType\r
++notmuch_gmime_session_get_type (void);\r
++\r
+ #endif\r
+diff --git a/notmuch-gmime-session.c b/notmuch-gmime-session.c\r
+new file mode 100644\r
+index 0000000..d83d9b3\r
+--- /dev/null\r
++++ b/notmuch-gmime-session.c\r
+@@ -0,0 +1,49 @@\r
++#include "notmuch-client.h"\r
++\r
++/* CRUFTY BOILERPLATE for GMimeSession (dkg thinks this will go away once GMime 2.6 comes out) */\r
++typedef struct _NotmuchGmimeSession NotmuchGmimeSession;\r
++typedef struct _NotmuchGmimeSessionClass NotmuchGmimeSessionClass;\r
++\r
++struct _NotmuchGmimeSession {\r
++    GMimeSession parent_object;\r
++};\r
++\r
++struct _NotmuchGmimeSessionClass {\r
++    GMimeSessionClass parent_class;\r
++};\r
++\r
++static void notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass);\r
++\r
++static GMimeSessionClass *parent_class = NULL;\r
++\r
++GType\r
++notmuch_gmime_session_get_type (void)\r
++{\r
++    static GType type = 0;\r
++\r
++    if (!type) {\r
++      static const GTypeInfo info = {\r
++          sizeof (NotmuchGmimeSessionClass),\r
++          NULL, /* base_class_init */\r
++          NULL, /* base_class_finalize */\r
++          (GClassInitFunc) notmuch_gmime_session_class_init,\r
++          NULL, /* class_finalize */\r
++          NULL, /* class_data */\r
++          sizeof (NotmuchGmimeSession),\r
++          0,    /* n_preallocs */\r
++          NULL, /* object_init */\r
++          NULL, /* value_table */\r
++      };\r
++      type = g_type_register_static (GMIME_TYPE_SESSION, "NotmuchGmimeSession", &info, 0);\r
++    }\r
++    return type;\r
++}\r
++\r
++static void\r
++notmuch_gmime_session_class_init (NotmuchGmimeSessionClass *klass)\r
++{\r
++    GMimeSessionClass *session_class = GMIME_SESSION_CLASS (klass);\r
++    parent_class = g_type_class_ref (GMIME_TYPE_SESSION);\r
++    session_class->request_passwd = NULL;\r
++}\r
++/* END CRUFTY BOILERPLATE */\r
+diff --git a/notmuch-reply.c b/notmuch-reply.c\r
+index 9c35475..99bb15f 100644\r
+--- a/notmuch-reply.c\r
++++ b/notmuch-reply.c\r
+@@ -33,6 +33,7 @@ static const notmuch_show_format_t format_reply = {\r
+           "", NULL, "",\r
+           "",\r
+               NULL,\r
++              NULL,\r
+               reply_part_content,\r
+               NULL,\r
+               "",\r
+@@ -447,6 +448,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_\r
+     const notmuch_show_format_t *format = &format_reply;\r
+     notmuch_show_params_t params;\r
+     params.part = -1;\r
++    params.cryptoctx = NULL;\r
\r
+     for (messages = notmuch_query_search_messages (query);\r
+        notmuch_messages_valid (messages);\r
+diff --git a/notmuch-show.c b/notmuch-show.c\r
+index 363cdbf..bb54e56 100644\r
+--- a/notmuch-show.c\r
++++ b/notmuch-show.c\r
+@@ -44,6 +44,7 @@ static const notmuch_show_format_t format_text = {\r
+           "\fheader{\n", format_headers_text, "\fheader}\n",\r
+           "\fbody{\n",\r
+               format_part_start_text,\r
++              NULL,\r
+               format_part_content_text,\r
+               format_part_end_text,\r
+               "",\r
+@@ -65,6 +66,9 @@ format_part_start_json (unused (GMimeObject *part),\r
+                       int *part_count);\r
\r
+ static void\r
++format_part_sigstatus_json (const GMimeSignatureValidity* validity);\r
++\r
++static void\r
+ format_part_content_json (GMimeObject *part);\r
\r
+ static void\r
+@@ -76,6 +80,7 @@ static const notmuch_show_format_t format_json = {\r
+           ", \"headers\": {", format_headers_json, "}",\r
+           ", \"body\": [",\r
+               format_part_start_json,\r
++              format_part_sigstatus_json,\r
+               format_part_content_json,\r
+               format_part_end_json,\r
+               ", ",\r
+@@ -97,6 +102,7 @@ static const notmuch_show_format_t format_mbox = {\r
+                 NULL,\r
+                 NULL,\r
+                 NULL,\r
++                NULL,\r
+                 "",\r
+             "",\r
+         "", "",\r
+@@ -112,6 +118,7 @@ static const notmuch_show_format_t format_raw = {\r
+           "", NULL, "",\r
+             "",\r
+                 NULL,\r
++                NULL,\r
+                 format_part_content_raw,\r
+                 NULL,\r
+                 "",\r
+@@ -396,6 +403,22 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out)\r
+       g_object_unref(stream_filter);\r
+ }\r
\r
++static const char*\r
++signerstatustostring (GMimeSignerStatus x)\r
++{\r
++    switch (x) {\r
++    case GMIME_SIGNER_STATUS_NONE:\r
++      return "none";\r
++    case GMIME_SIGNER_STATUS_GOOD:\r
++      return "good";\r
++    case GMIME_SIGNER_STATUS_BAD:\r
++      return "bad";\r
++    case GMIME_SIGNER_STATUS_ERROR:\r
++      return "error";\r
++    }\r
++    return "unknown";\r
++}\r
++\r
+ static void\r
+ format_part_start_text (GMimeObject *part, int *part_count)\r
+ {\r
+@@ -473,6 +496,65 @@ format_part_start_json (unused (GMimeObject *part), int *part_count)\r
+ }\r
\r
+ static void\r
++format_part_sigstatus_json (const GMimeSignatureValidity* validity)\r
++{\r
++    printf (", \"sigstatus\": [");\r
++\r
++    if (!validity) {\r
++      printf ("]");\r
++      return;\r
++    }\r
++\r
++    const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);\r
++    int first = 1;\r
++    void *ctx_quote = talloc_new (NULL);\r
++\r
++    while (signer) {\r
++      if (first)\r
++          first = 0;\r
++      else\r
++          printf (", ");\r
++\r
++      printf ("{");\r
++\r
++      /* status */\r
++      printf ("\"status\": %s", json_quote_str (ctx_quote, signerstatustostring(signer->status)));\r
++\r
++      if (signer->status == GMIME_SIGNER_STATUS_GOOD)\r
++      {\r
++          if (signer->fingerprint)\r
++              printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));\r
++          /* these dates are seconds since the epoch; should we\r
++           * provide a more human-readable format string? */\r
++          if (signer->created)\r
++              printf (", \"created\": %d", (int) signer->created);\r
++          if (signer->expires)\r
++              printf (", \"expires\": %d", (int) signer->expires);\r
++          /* output user id only if validity is FULL or ULTIMATE. */\r
++          /* note that gmime is using the term "trust" here, which\r
++           * is WRONG.  It's actually user id "validity". */\r
++          if ((signer->name) && (signer->trust)) {\r
++              if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))\r
++                  printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));\r
++           }\r
++       } else {\r
++           if (signer->keyid)\r
++               printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));\r
++       }\r
++       if (signer->errors != GMIME_SIGNER_ERROR_NONE) {\r
++           printf (", \"errors\": %x", signer->errors);\r
++       }\r
++\r
++       printf ("}");\r
++       signer = signer->next;\r
++    }\r
++\r
++    printf ("]");\r
++\r
++    talloc_free (ctx_quote);\r
++}\r
++\r
++static void\r
+ format_part_content_json (GMimeObject *part)\r
+ {\r
+     GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\r
+@@ -739,6 +821,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+     params.entire_thread = 0;\r
+     params.raw = 0;\r
+     params.part = -1;\r
++    params.cryptoctx = NULL;\r
\r
+     for (i = 0; i < argc && argv[i][0] == '-'; i++) {\r
+       if (strcmp (argv[i], "--") == 0) {\r
+@@ -767,6 +850,16 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+           params.part = atoi(argv[i] + sizeof ("--part=") - 1);\r
+       } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {\r
+           params.entire_thread = 1;\r
++      } else if (STRNCMP_LITERAL (argv[i], "--verify") == 0) {\r
++          if (params.cryptoctx == NULL) {\r
++              GMimeSession* session = g_object_new(notmuch_gmime_session_get_type(), NULL);\r
++              if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg")))\r
++                  fprintf (stderr, "Failed to construct gpg context.\n");\r
++              else\r
++                  g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);\r
++              g_object_unref (session);\r
++              session = NULL;\r
++          }\r
+       } else {\r
+           fprintf (stderr, "Unrecognized option: %s\n", argv[i]);\r
+           return 1;\r
+@@ -824,5 +917,8 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))\r
+     notmuch_query_destroy (query);\r
+     notmuch_database_close (notmuch);\r
\r
++    if (params.cryptoctx)\r
++      g_object_unref(params.cryptoctx);\r
++\r
+     return 0;\r
+ }\r
+diff --git a/notmuch.1 b/notmuch.1\r
+index a1c7fa8..daa9c6f 100644\r
+--- a/notmuch.1\r
++++ b/notmuch.1\r
+@@ -321,6 +321,17 @@ a depth-first walk of the message MIME structure, and are identified\r
+ in the 'json' or 'text' output formats.\r
+ .RE\r
\r
++.RS 4\r
++.TP 4\r
++.B \-\-verify\r
++\r
++Compute and report the validity of any MIME cryptographic signatures\r
++found in the selected content (ie. "multipart/signed" parts). Status\r
++of the signature will be reported (currently only supported with\r
++--format=json), and the multipart/signed part will be replaced by the\r
++signed data.\r
++.RE\r
++\r
+ A common use of\r
+ .B notmuch show\r
+ is to display a single thread of email messages. For this, use a\r
+diff --git a/notmuch.c b/notmuch.c\r
+index 262d677..cd3cb1b 100644\r
+--- a/notmuch.c\r
++++ b/notmuch.c\r
+@@ -294,6 +294,14 @@ static command_t commands[] = {\r
+       "\t\tmessage MIME structure, and are identified in the 'json' or\n"\r
+       "\t\t'text' output formats.\n"\r
+       "\n"\r
++      "\t--verify\n"\r
++      "\n"\r
++      "\t\tCompute and report the validity of any MIME cryptographic\n"\r
++      "\t\tsignatures found in the selected content (ie.\n"\r
++      "\t\t\"multipart/signed\" parts). Status of the signature will be\n"\r
++      "\t\treported (currently only supported with --format=json) and\n"\r
++      "\t\tthe multipart/signed part will be replaced by the signed data.\n"\r
++      "\n"\r
+       "\n"\r
+       "\tA common use of \"notmuch show\" is to display a single\n"\r
+       "\tthread of email messages. For this, use a search term of\n"\r
+diff --git a/show-message.c b/show-message.c\r
+index fbae530..c90f310 100644\r
+--- a/show-message.c\r
++++ b/show-message.c\r
+@@ -51,9 +51,48 @@ show_message_part (GMimeObject *part,\r
\r
+       if (format->part_start)\r
+           format->part_start (part, &(state->part_count));\r
+-      format->part_content (part);\r
+     }\r
\r
++    /* handle PGP/MIME parts */\r
++    if (GMIME_IS_MULTIPART (part) && params->cryptoctx) {\r
++      GMimeMultipart *multipart = GMIME_MULTIPART (part);\r
++      GError* err = NULL;\r
++\r
++      if (GMIME_IS_MULTIPART_SIGNED (part))\r
++      {\r
++          if ( g_mime_multipart_get_count (multipart) != 2 ) {\r
++              /* this violates RFC 3156 section 5, so we won't bother with it. */\r
++              fprintf (stderr,\r
++                       "Error: %d part(s) for a multipart/signed message (should be exactly 2)\n",\r
++                       g_mime_multipart_get_count (multipart));\r
++          } else {\r
++              /* For some reason the GMimeSignatureValidity returned\r
++               * here is not a const (inconsistent with that\r
++               * returned by\r
++               * g_mime_multipart_encrypted_get_signature_validity,\r
++               * and therefore needs to be properly disposed of.\r
++               * Hopefully the API will become more consistent. */\r
++              GMimeSignatureValidity *sigvalidity = g_mime_multipart_signed_verify (GMIME_MULTIPART_SIGNED (part), params->cryptoctx, &err);\r
++              if (!sigvalidity) {\r
++                  fprintf (stderr, "Failed to verify signed part: %s\n", (err ? err->message : "no error explanation given"));\r
++              }\r
++              if ((selected || state->in_zone) && format->part_sigstatus)\r
++                  format->part_sigstatus (sigvalidity);\r
++              /* extract only data part, and ignore signature part */\r
++              part = g_mime_multipart_get_part (multipart, 0);\r
++              if (sigvalidity)\r
++                  g_mime_signature_validity_free (sigvalidity);\r
++          }\r
++      }\r
++\r
++      if (err)\r
++          g_error_free (err);\r
++    }\r
++    /* end handle PGP/MIME parts */\r
++\r
++    if (selected || state->in_zone)\r
++      format->part_content (part);\r
++\r
+     if (GMIME_IS_MULTIPART (part)) {\r
+       GMimeMultipart *multipart = GMIME_MULTIPART (part);\r
+       int i;\r
+-- \r
+1.7.4.4\r
+\r