[PATCH 1/5] crypto: prepare for decryption of inline PGP encrypted messages

Daniel Kahn Gillmor dkg at fifthhorseman.net
Mon Dec 11 23:15:49 PST 2017


Inline PGP encrypted messages are clearly worse than PGP/MIME
structured encrypted messages.  There are no standards for how they
are formed, and they don't offer any structured metadata about how to
interpret the bytestream produced by decrypting them.

However, some other MUAs and end-user workflows may make creation of
inline PGP encrypted messages the only available option for message
encryption, and when Notmuch encounters such a message, it should make
a reasonable best-effort to render the cleartext to the user.

Due to ambiguities in interpretation of signatures on inline messages
(e.g. which parts of the message were actually signed?  what character
encoding should the bytestream be interpreted as), we continue to
ignore inline-signed messages entirely, and we do not look at the
validity of any signatures that might be found when decrypting inline
PGP encrypted messages.

We make use here of GMime's optimization function for detecting the
presence of inline PGP encrypted content, which is only found in GMime
3.0 or later.

This change prepares the internal codebase for decrypting inline
encrypted messages, but does not yet actually use the capability.
---
 lib/index.cc  |  6 +++---
 mime-node.c   | 24 ++++++++++++++----------
 util/crypto.c | 35 +++++++++++++++++++++++++++--------
 util/crypto.h |  2 +-
 4 files changed, 45 insertions(+), 22 deletions(-)

diff --git a/lib/index.cc b/lib/index.cc
index 22ca9ec1..f144b9fb 100644
--- a/lib/index.cc
+++ b/lib/index.cc
@@ -367,7 +367,7 @@ _index_content_type (notmuch_message_t *message, GMimeObject *part)
 static void
 _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
 			    GMimeContentType *content_type,
-			    GMimeMultipartEncrypted *part);
+			    GMimeObject *part);
 
 /* Callback to generate terms for each mime part of a message. */
 static void
@@ -421,7 +421,7 @@ _index_mime_part (notmuch_message_t *message,
 		if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
 		    _index_encrypted_mime_part(message, indexopts,
 					       content_type,
-					       GMIME_MULTIPART_ENCRYPTED (part));
+					       part);
 		} else {
 		    if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
 			_notmuch_database_log (notmuch_message_get_database (message),
@@ -518,7 +518,7 @@ static void
 _index_encrypted_mime_part (notmuch_message_t *message,
 			    notmuch_indexopts_t *indexopts,
 			    g_mime_3_unused(GMimeContentType *content_type),
-			    GMimeMultipartEncrypted *encrypted_data)
+			    GMimeObject *encrypted_data)
 {
     notmuch_status_t status;
     GError *err = NULL;
diff --git a/mime-node.c b/mime-node.c
index 75b79f98..973133d9 100644
--- a/mime-node.c
+++ b/mime-node.c
@@ -196,10 +196,10 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
 {
     GError *err = NULL;
     GMimeDecryptResult *decrypt_result = NULL;
-    GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
     notmuch_message_t *message = NULL;
 
-    if (! node->decrypted_child) {
+    if (GMIME_IS_PART (part) || /* must be inline */
+	(GMIME_IS_MULTIPART_ENCRYPTED (part) && ! node->decrypted_child)) {
 	for (mime_node_t *parent = node; parent; parent = parent->parent)
 	    if (parent->envelope_file) {
 		message = parent->envelope_file;
@@ -209,7 +209,7 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
 	node->decrypted_child = _notmuch_crypto_decrypt (&node->decrypt_attempted,
 							 node->ctx->crypto->decrypt,
 							 message,
-							 cryptoctx, encrypteddata, &decrypt_result, &err);
+							 cryptoctx, part, &decrypt_result, &err);
     }
     if (! node->decrypted_child) {
 	fprintf (stderr, "Failed to decrypt part: %s\n",
@@ -217,15 +217,19 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
 	goto DONE;
     }
 
-    node->decrypt_success = true;
-    node->verify_attempted = true;
 
     if (decrypt_result) {
-	/* This may be NULL if the part is not signed. */
-	node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
-	if (node->sig_list) {
-	    g_object_ref (node->sig_list);
-	    set_signature_list_destructor (node);
+	node->decrypt_success = true;
+	if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
+	    /* Only check signatures on PGP/MIME messages, not inline
+	       messages. To understand why, see
+	       https://dkg.fifthhorseman.net/notes/inline-pgp-harmful/ */
+	    node->verify_attempted = true;
+	    node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
+	    if (node->sig_list) {
+		g_object_ref (node->sig_list);
+		set_signature_list_destructor (node);
+	    }
 	}
 
 #if HAVE_GMIME_SESSION_KEYS
diff --git a/util/crypto.c b/util/crypto.c
index 9d3b6dad..7965ab8e 100644
--- a/util/crypto.c
+++ b/util/crypto.c
@@ -144,7 +144,7 @@ _notmuch_crypto_decrypt (bool *attempted,
 			 notmuch_decryption_policy_t decrypt,
 			 notmuch_message_t *message,
 			 g_mime_3_unused(GMimeCryptoContext* crypto_ctx),
-			 GMimeMultipartEncrypted *part,
+			 GMimeObject *part,
 			 GMimeDecryptResult **decrypt_result,
 			 GError **err)
 {
@@ -166,15 +166,26 @@ _notmuch_crypto_decrypt (bool *attempted,
 	    if (attempted)
 		*attempted = true;
 #if (GMIME_MAJOR_VERSION < 3)
-	    ret = g_mime_multipart_encrypted_decrypt_session (part,
+	    ret = g_mime_multipart_encrypted_decrypt_session (GMIME_MULTIPART_ENCRYPTED (part),
 							      crypto_ctx,
 							      notmuch_message_properties_value (list),
 							      decrypt_result, err);
 #else
-	    ret = g_mime_multipart_encrypted_decrypt (part,
-						      GMIME_DECRYPT_NONE,
-						      notmuch_message_properties_value (list),
-						      decrypt_result, err);
+	    if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
+		ret = g_mime_multipart_encrypted_decrypt (GMIME_MULTIPART_ENCRYPTED (part),
+							  GMIME_DECRYPT_NONE,
+							  notmuch_message_properties_value (list),
+							  decrypt_result, err);
+	    } else if (GMIME_IS_PART (part) && g_mime_part_get_openpgp_data (GMIME_PART (part)) == GMIME_OPENPGP_DATA_ENCRYPTED) {
+		*decrypt_result = g_mime_part_openpgp_decrypt (GMIME_PART (part),
+							       GMIME_DECRYPT_NONE,
+							       notmuch_message_properties_value (list),
+							       err);
+		if (decrypt_result) {
+		    ret = part;
+		    g_object_ref (ret);
+		}
+	    }
 #endif
 	    if (ret)
 		break;
@@ -214,8 +225,16 @@ _notmuch_crypto_decrypt (bool *attempted,
     GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
     if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result)
 	flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
-    ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
-					     decrypt_result, err);
+    if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
+	ret = g_mime_multipart_encrypted_decrypt(GMIME_MULTIPART_ENCRYPTED (part), flags, NULL,
+						 decrypt_result, err);
+    } else if (GMIME_IS_PART (part) && g_mime_part_get_openpgp_data (GMIME_PART (part)) == GMIME_OPENPGP_DATA_ENCRYPTED) {
+	*decrypt_result = g_mime_part_openpgp_decrypt (GMIME_PART (part), flags, NULL, err);
+	if (decrypt_result) {
+	    ret = part;
+	    g_object_ref (ret);
+	}
+    }
 #endif
     return ret;
 }
diff --git a/util/crypto.h b/util/crypto.h
index c384601c..d9e4a1c7 100644
--- a/util/crypto.h
+++ b/util/crypto.h
@@ -20,7 +20,7 @@ _notmuch_crypto_decrypt (bool *attempted,
 			 notmuch_decryption_policy_t decrypt,
 			 notmuch_message_t *message,
 			 GMimeCryptoContext* crypto_ctx,
-			 GMimeMultipartEncrypted *part,
+			 GMimeObject *part,
 			 GMimeDecryptResult **decrypt_result,
 			 GError **err);
 
-- 
2.15.1



More information about the notmuch mailing list