crypto: actually stash session keys when decrypt=true
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Fri, 8 Dec 2017 06:24:01 +0000 (01:24 -0500)
committerDavid Bremner <david@tethera.net>
Fri, 8 Dec 2017 12:08:47 +0000 (08:08 -0400)
If you're going to store the cleartext index of an encrypted message,
in most situations you might just as well store the session key.
Doing this storage has efficiency and recoverability advantages.

Combined with a schedule of regular OpenPGP subkey rotation and
destruction, this can also offer security benefits, like "deletable
e-mail", which is the store-and-forward analog to "forward secrecy".

But wait, i hear you saying, i have a special need to store cleartext
indexes but it's really bad for me to store session keys!  Maybe
(let's imagine) i get lots of e-mails with incriminating photos
attached, and i want to be able to search for them by the text in the
e-mail, but i don't want someone with access to the index to be
actually able to see the photos themselves.

Fret not, the next patch in this series will support your wacky
uncommon use case.

doc/man1/notmuch-config.rst
doc/man1/notmuch-insert.rst
doc/man1/notmuch-new.rst
doc/man1/notmuch-reindex.rst
doc/man7/notmuch-properties.rst
lib/index.cc
test/T357-index-decryption.sh
util/crypto.c

index 1a9e08a3e805b76b88bb8367e167fb714600ce9d..dabf269f141c38b996a4be424262d47ec0c17dc6 100644 (file)
@@ -143,10 +143,12 @@ The available configuration items are described below.
         **[STORED IN DATABASE]**
         When indexing an encrypted e-mail message, if this variable is
         set to ``true``, notmuch will try to decrypt the message and
-        index the cleartext.  If ``auto``, it will try to index the
-        cleartext if a stashed session key is already known for the message,
-        but will not try to access your secret keys.  Use ``false`` to
-        avoid decrypting even when a session key is already known.
+        index the cleartext, stashing a copy of any discovered session
+        keys for the message.  If ``auto``, it will try to index the
+        cleartext if a stashed session key is already known for the message
+        (e.g. from a previous copy), but will not try to access your
+        secret keys.  Use ``false`` to avoid decrypting even when a
+        stashed session key is already present.
 
         Be aware that the notmuch index is likely sufficient to
         reconstruct the cleartext of the message itself, so please
index b22be863dc351fe4a5a56844e3829a4a6f75dec0..214f261ba6b00f95a3eb036f535c9ca992978708 100644 (file)
@@ -54,12 +54,13 @@ Supported options for **insert** include
     ``--decrypt=(true|auto|false)``
 
         If ``true`` and the message is encrypted, try to decrypt the
-        message while indexing.  If ``auto``, and notmuch already
-        knows about a session key for the message, it will try
-        decrypting using that session key but will not try to access
-        the user's secret keys.  If decryption is successful, index
-        the cleartext itself.  Either way, the message is always
-        stored to disk in its original form (ciphertext).
+        message while indexing, storing any session keys discovered.
+        If ``auto``, and notmuch already knows about a session key for
+        the message, it will try decrypting using that session key but
+        will not try to access the user's secret keys.  If decryption
+        is successful, index the cleartext itself.  Either way, the
+        message is always stored to disk in its original form
+        (ciphertext).
 
         Be aware that the index is likely sufficient to reconstruct
         the cleartext of the message itself, so please ensure that the
index 71df31d77dd64d0e6c5402f34e8b255127a611a2..27676a196994919f92bc831e4d5fb45d1b3aa4cb 100644 (file)
@@ -46,16 +46,17 @@ Supported options for **new** include
     ``--decrypt=(true|auto|false)``
 
         If ``true``, when encountering an encrypted message, try to
-        decrypt it while indexing.  If decryption is successful, index
-        the cleartext itself.  If ``auto``, try to use any session key
-        already known to belong to this message, but do not attempt to
-        use the user's secret keys.
-
-        Be aware that the index is likely
-        sufficient to reconstruct the cleartext of the message itself,
-        so please ensure that the notmuch message index is adequately
-        protected.  DO NOT USE ``--decrypt=true`` without
-        considering the security of your index.
+        decrypt it while indexing, and store any discovered session
+        keys.  If ``auto``, try to use any session key already known
+        to belong to this message, but do not attempt to use the
+        user's secret keys.  If decryption is successful, index the
+        cleartext of the message.
+
+        Be aware that the index is likely sufficient (and the session
+        key is certainly sufficient) to reconstruct the cleartext of
+        the message itself, so please ensure that the notmuch message
+        index is adequately protected.  DO NOT USE ``--decrypt=true``
+        without considering the security of your index.
 
         See also ``index.decrypt`` in **notmuch-config(1)**.
 
index e8174f3995f007025b71822a8a5b2307501edee7..477908713e89885f509c6ece7ec0ae15d5438cd5 100644 (file)
@@ -24,11 +24,11 @@ Supported options for **reindex** include
     ``--decrypt=(true|auto|false)``
 
         If ``true``, when encountering an encrypted message, try to
-        decrypt it while reindexing.  If ``auto``, and notmuch already
-        knows about a session key for the message, it will try
-        decrypting using that session key but will not try to access
-        the user's secret keys.  If decryption is successful, index
-        the cleartext itself.
+        decrypt it while reindexing, storing any session keys
+        discovered.  If ``auto``, and notmuch already knows about a
+        session key for the message, it will try decrypting using that
+        session key but will not try to access the user's secret keys.
+        If decryption is successful, index the cleartext itself.
 
         If ``false``, notmuch reindex will also delete any stashed
         session keys for all messages matching the search terms.
index 1a3f690edec4221ad6ec57cbb6f1a31df9c9b0b7..07d36a1a5993fefb0f815f7c82a44b8bef741f1a 100644 (file)
@@ -98,6 +98,10 @@ of its normal activity.
     message.  This includes attachments, cryptographic signatures, and
     other material that cannot be reconstructed from the index alone.
 
+    See ``index.decrypt`` in **notmuch-config(1)** for more
+    details about how to set notmuch's policy on when to store session
+    keys.
+
     The session key should be in the ASCII text form produced by
     GnuPG.  For OpenPGP, that consists of a decimal representation of
     the hash algorithm used (identified by number from RFC 4880,
index 3914012afddc1a25832e180ec6e3cae2be467483..0ad683fac51b7704b1f15ee0c6c24b331dcfd02f 100644 (file)
@@ -549,11 +549,15 @@ _index_encrypted_mime_part (notmuch_message_t *message,
     }
 #endif
     bool attempted = false;
+    GMimeDecryptResult *decrypt_result = NULL;
+    bool get_sk = (HAVE_GMIME_SESSION_KEYS && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
     clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts),
-                                    message, crypto_ctx, encrypted_data, NULL, &err);
+                                    message, crypto_ctx, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
     if (!attempted)
        return;
     if (err || !clear) {
+       if (decrypt_result)
+           g_object_unref (decrypt_result);
        if (err) {
            _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n",
                                   err->domain, err->code, err->message);
@@ -568,6 +572,18 @@ _index_encrypted_mime_part (notmuch_message_t *message,
                                          "property (%d)\n", status);
        return;
     }
+    if (decrypt_result) {
+#if HAVE_GMIME_SESSION_KEYS
+       if (get_sk) {
+           status = notmuch_message_add_property (message, "session-key",
+                                                  g_mime_decrypt_result_get_session_key (decrypt_result));
+           if (status)
+               _notmuch_database_log (notmuch, "failed to add session-key "
+                                      "property (%d)\n", status);
+       }
+#endif
+       g_object_unref (decrypt_result);
+    }
     _index_mime_part (message, indexopts, clear);
     g_object_unref (clear);
 
index 9f46a01b22c9530925f7bd844acb429e5c2ad6c7..fcecb1d9f4b19f6c76cc7be8d6e61b8a636e5029 100755 (executable)
@@ -48,6 +48,17 @@ test_expect_equal \
     "$output" \
     "$expected"
 
+test_begin_subtest "show the message body of the encrypted message"
+notmuch dump wumpus
+output=$(notmuch show wumpus | awk '/^\014part}/{ f=0 }; { if (f) { print $0 } } /^\014part{ ID: 3/{ f=1 }')
+expected='This is a test encrypted message with a wumpus.'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
 
 test_begin_subtest "message should go away after deletion"
 # cache the message in an env var and remove it:
@@ -129,10 +140,21 @@ test_expect_equal \
     "$output" \
     "$expected"
 
+# try a simple reindex
+test_begin_subtest 'reindex in auto mode'
+test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, should not have changed"
+output=$(notmuch search wumpus)
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
 
 # try to remove cleartext indexing
 test_begin_subtest 'reindex without cleartext'
-test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_expect_success 'notmuch reindex --decrypt=false tag:encrypted and property:index.decryption=success'
 test_begin_subtest "reindexed encrypted messages, without cleartext"
 output=$(notmuch search wumpus)
 expected=''
index 338f1d5d85e5a4b9b273488dcac4b1770c301749..066dea6e1a0380fa8b3844380269669c987e90ab 100644 (file)
@@ -197,10 +197,24 @@ _notmuch_crypto_decrypt (bool *attempted,
     if (attempted)
        *attempted = true;
 #if (GMIME_MAJOR_VERSION < 3)
+#if HAVE_GMIME_SESSION_KEYS
+    gboolean oldgetsk = g_mime_crypto_context_get_retrieve_session_key (crypto_ctx);
+    gboolean newgetsk = (decrypt_result);
+    if (newgetsk != oldgetsk)
+       /* This could return an error, but we can't do anything about it, so ignore it */
+       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, newgetsk, NULL);
+#endif
     ret = g_mime_multipart_encrypted_decrypt(part, crypto_ctx,
                                             decrypt_result, err);
+#if HAVE_GMIME_SESSION_KEYS
+    if (newgetsk != oldgetsk)
+       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, oldgetsk, NULL);
+#endif
 #else
-    ret = g_mime_multipart_encrypted_decrypt(part, GMIME_DECRYPT_NONE, NULL,
+    GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
+    if (decrypt_result)
+       flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
+    ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
                                             decrypt_result, err);
 #endif
     return ret;