From e4890b5bf9e2260b36bcc36ddb77d8e97e2abe7d Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 8 Dec 2017 01:23:53 -0500 Subject: [PATCH] crypto: new decryption policy "auto" This new automatic decryption policy should make it possible to decrypt messages that we have stashed session keys for, without incurring a call to the user's asymmetric keys. --- doc/man1/notmuch-config.rst | 11 ++++++++--- lib/index.cc | 3 ++- lib/indexopts.c | 13 ++++++++----- lib/notmuch.h | 1 + mime-node.c | 7 ++++--- notmuch-client.h | 4 +++- notmuch.c | 3 ++- test/T357-index-decryption.sh | 12 +++++++++++- util/crypto.c | 9 ++++++++- util/crypto.h | 3 ++- 10 files changed, 49 insertions(+), 17 deletions(-) diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index ea3d9754..4835f897 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -142,9 +142,14 @@ 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. Be aware that the index is likely sufficient - to reconstruct the cleartext of the message itself, so please + 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. + + Be aware that the notmuch 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 ``index.decrypt=true`` without considering the security of your index. diff --git a/lib/index.cc b/lib/index.cc index 905366ae..af999bd3 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -548,7 +548,8 @@ _index_encrypted_mime_part (notmuch_message_t *message, } } #endif - clear = _notmuch_crypto_decrypt (message, crypto_ctx, encrypted_data, NULL, &err); + clear = _notmuch_crypto_decrypt (notmuch_indexopts_get_decrypt_policy (indexopts), + message, crypto_ctx, encrypted_data, NULL, &err); if (err) { _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n", err->domain, err->code, err->message); diff --git a/lib/indexopts.c b/lib/indexopts.c index 78f53391..a04d1c1c 100644 --- a/lib/indexopts.c +++ b/lib/indexopts.c @@ -33,11 +33,14 @@ notmuch_database_get_default_indexopts (notmuch_database_t *db) if (err) return ret; - if (decrypt_policy && - ((!(strcasecmp(decrypt_policy, "true"))) || - (!(strcasecmp(decrypt_policy, "yes"))) || - (!(strcasecmp(decrypt_policy, "1"))))) - notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_TRUE); + if (decrypt_policy) { + if ((!(strcasecmp(decrypt_policy, "true"))) || + (!(strcasecmp(decrypt_policy, "yes"))) || + (!(strcasecmp(decrypt_policy, "1")))) + notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_TRUE); + else if (!strcasecmp(decrypt_policy, "auto")) + notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_AUTO); + } free (decrypt_policy); return ret; diff --git a/lib/notmuch.h b/lib/notmuch.h index 47633496..ff860e06 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2241,6 +2241,7 @@ notmuch_database_get_default_indexopts (notmuch_database_t *db); typedef enum { NOTMUCH_DECRYPT_FALSE, NOTMUCH_DECRYPT_TRUE, + NOTMUCH_DECRYPT_AUTO, } notmuch_decryption_policy_t; /** diff --git a/mime-node.c b/mime-node.c index c4de708b..49d668fe 100644 --- a/mime-node.c +++ b/mime-node.c @@ -205,7 +205,8 @@ node_decrypt_and_verify (mime_node_t *node, GMimeObject *part, break; node->decrypt_attempted = true; - node->decrypted_child = _notmuch_crypto_decrypt (parent ? parent->envelope_file : NULL, + node->decrypted_child = _notmuch_crypto_decrypt (node->ctx->crypto->decrypt, + parent ? parent->envelope_file : NULL, cryptoctx, encrypteddata, &decrypt_result, &err); } if (! node->decrypted_child) { @@ -270,7 +271,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) } #if (GMIME_MAJOR_VERSION < 3) - if ((GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt == NOTMUCH_DECRYPT_TRUE)) + if ((GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) || (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) { GMimeContentType *content_type = g_mime_object_get_content_type (part); const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol"); @@ -286,7 +287,7 @@ _mime_node_create (mime_node_t *parent, GMimeObject *part) #endif /* Handle PGP/MIME parts */ - if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt == NOTMUCH_DECRYPT_TRUE)) { + if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) { if (node->nchildren != 2) { /* this violates RFC 3156 section 4, so we won't bother with it. */ fprintf (stderr, "Error: %d part(s) for a multipart/encrypted " diff --git a/notmuch-client.h b/notmuch-client.h index 50b69e35..0985a7b0 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -415,7 +415,9 @@ struct mime_node { /* Construct a new MIME node pointing to the root message part of * message. If crypto->verify is true, signed child parts will be * verified. If crypto->decrypt is NOTMUCH_DECRYPT_TRUE, encrypted - * child parts will be decrypted. If the crypto contexts + * child parts will be decrypted using either stored session keys or + * asymmetric crypto. If crypto->decrypt is NOTMUCH_DECRYPT_AUTO, + * only session keys will be tried. If the crypto contexts * (crypto->gpgctx or crypto->pkcs7) are NULL, they will be lazily * initialized. * diff --git a/notmuch.c b/notmuch.c index 7d5eeae4..1d283e26 100644 --- a/notmuch.c +++ b/notmuch.c @@ -103,6 +103,7 @@ const notmuch_opt_desc_t notmuch_shared_indexing_options [] = { .present = &indexing_cli_choices.decrypt_policy_set, .keywords = (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, { "true", NOTMUCH_DECRYPT_TRUE }, + { "auto", NOTMUCH_DECRYPT_AUTO }, { 0, 0 } }, .name = "decrypt" }, { } @@ -128,7 +129,7 @@ notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_u } } #if (GMIME_MAJOR_VERSION < 3) - if (indexing_cli_choices.opts && notmuch_indexopts_get_decrypt_policy (indexing_cli_choices.opts) == NOTMUCH_DECRYPT_TRUE) { + if (indexing_cli_choices.opts && notmuch_indexopts_get_decrypt_policy (indexing_cli_choices.opts) != NOTMUCH_DECRYPT_FALSE) { const char* gpg_path = notmuch_config_get_crypto_gpg_path (config); if (gpg_path && strcmp(gpg_path, "gpg")) fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n" diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh index 15deaa6e..7996ec67 100755 --- a/test/T357-index-decryption.sh +++ b/test/T357-index-decryption.sh @@ -140,6 +140,16 @@ test_expect_equal \ "$output" \ "$expected" +# ensure no session keys are present: +test_begin_subtest 'reindex using only session keys' +test_expect_success 'notmuch reindex --decrypt=auto tag:encrypted and property:index.decryption=success' +test_begin_subtest "reindexed encrypted messages, decrypting only with session keys" +output=$(notmuch search wumpus) +expected='' +test_expect_equal \ + "$output" \ + "$expected" + # and the same search, but by property ($expected is untouched): test_begin_subtest "emacs search by property with both messages unindexed" output=$(notmuch search property:index.decryption=success) @@ -180,7 +190,7 @@ notmuch restore <