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