cli/show: enable --decrypt=stash

Add fancy new feature, which makes "notmuch show" capable of actually
indexing messages that it just decrypted.

This enables a workflow where messages can come in in the background
and be indexed using "--decrypt=auto".  But when showing an encrypted
message for the first time, it gets automatically indexed.

This is something of a departure for "notmuch show" -- in particular,
because it requires read/write access to the database.  However, this
might be a common use case -- people get mail delivered and indexed in
the background, but only want access to their secret key to happen
when they're directly interacting with notmuch itself.

In such a scenario, they couldn't search newly-delivered, encrypted
messages, but they could search for them once they've read them.

Documentation of this new feature also uses a table form, similar to
that found in the description of index.decrypt in notmuch-config(1).

A notmuch UI that wants to facilitate this workflow while also
offering an interactive search interface might instead make use of
these additional commands while the user is at the console:

Count received encrypted messages (if > 0, there are some things we
haven't yet tried to index, and therefore can't yet search):

     notmuch count tag:encrypted and \
         not property:index.decryption=success and \
         not property:index.decryption=failure

Reindex those messages:

     notmuch reindex --try-decrypt=true tag:encrypted and \
         not property:index.decryption=success and \
         not property:index.decryption=failure
This commit is contained in:
Daniel Kahn Gillmor 2018-05-11 02:57:59 -04:00 committed by David Bremner
parent 9d114a8552
commit aa605f7e8a
4 changed files with 59 additions and 8 deletions

View file

@ -522,7 +522,7 @@ _notmuch_show()
return return
;; ;;
--decrypt) --decrypt)
COMPREPLY=( $( compgen -W "true auto false" -- "${cur}" ) ) COMPREPLY=( $( compgen -W "true auto false stash" -- "${cur}" ) )
return return
;; ;;
esac esac

View file

@ -110,7 +110,7 @@ Supported options for **show** include
supported with --format=json and --format=sexp), and the supported with --format=json and --format=sexp), and the
multipart/signed part will be replaced by the signed data. multipart/signed part will be replaced by the signed data.
``--decrypt=(false|auto|true)`` ``--decrypt=(false|auto|true|stash)``
If ``true``, decrypt any MIME encrypted parts found in the If ``true``, decrypt any MIME encrypted parts found in the
selected content (i.e. "multipart/encrypted" parts). Status of selected content (i.e. "multipart/encrypted" parts). Status of
the decryption will be reported (currently only supported the decryption will be reported (currently only supported
@ -118,17 +118,45 @@ Supported options for **show** include
decryption the multipart/encrypted part will be replaced by decryption the multipart/encrypted part will be replaced by
the decrypted content. the decrypted content.
``stash`` behaves like ``true``, but upon successful decryption it
will also stash the message's session key in the database, and
index the cleartext of the message, enabling automatic decryption
in the future.
If ``auto``, and a session key is already known for the If ``auto``, and a session key is already known for the
message, then it will be decrypted, but notmuch will not try message, then it will be decrypted, but notmuch will not try
to access the user's keys. to access the user's keys.
Use ``false`` to avoid even automatic decryption. Use ``false`` to avoid even automatic decryption.
Non-automatic decryption expects a functioning Non-automatic decryption (``stash`` or ``true``, in the absence of
**gpg-agent(1)** to provide any needed credentials. Without a stashed session key) expects a functioning **gpg-agent(1)** to
one, the decryption will fail. provide any needed credentials. Without one, the decryption will
fail.
Note: ``true`` implies --verify. Note: setting either ``true`` or ``stash`` here implies
``--verify``.
Here is a table that summarizes each of these policies:
+------------------------+-------+------+------+-------+
| | false | auto | true | stash |
+========================+=======+======+======+=======+
| Show cleartext if | | X | X | X |
| session key is | | | | |
| already known | | | | |
+------------------------+-------+------+------+-------+
| Use secret keys to | | | X | X |
| show cleartext | | | | |
+------------------------+-------+------+------+-------+
| Stash any newly | | | | X |
| recovered session keys,| | | | |
| reindexing message if | | | | |
| found | | | | |
+------------------------+-------+------+------+-------+
Note: ``--decrypt=stash`` requires write access to the database.
Otherwise, ``notmuch show`` operates entirely in read-only mode.
Default: ``auto`` Default: ``auto``

View file

@ -1124,6 +1124,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
(notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE }, (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
{ "auto", NOTMUCH_DECRYPT_AUTO }, { "auto", NOTMUCH_DECRYPT_AUTO },
{ "true", NOTMUCH_DECRYPT_NOSTASH }, { "true", NOTMUCH_DECRYPT_NOSTASH },
{ "stash", NOTMUCH_DECRYPT_TRUE },
{ 0, 0 } } }, { 0, 0 } } },
{ .opt_bool = &params.crypto.verify, .name = "verify" }, { .opt_bool = &params.crypto.verify, .name = "verify" },
{ .opt_bool = &params.output_body, .name = "body" }, { .opt_bool = &params.output_body, .name = "body" },
@ -1139,7 +1140,8 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
notmuch_process_shared_options (argv[0]); notmuch_process_shared_options (argv[0]);
/* explicit decryption implies verification */ /* explicit decryption implies verification */
if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH) if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH ||
params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
params.crypto.verify = true; params.crypto.verify = true;
/* specifying a part implies single message display */ /* specifying a part implies single message display */
@ -1202,8 +1204,11 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config); params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config);
#endif #endif
notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
if (notmuch_database_open (notmuch_config_get_database_path (config), if (notmuch_database_open (notmuch_config_get_database_path (config),
NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch)) mode, &notmuch))
return EXIT_FAILURE; return EXIT_FAILURE;
notmuch_exit_if_unmatched_db_uuid (notmuch); notmuch_exit_if_unmatched_db_uuid (notmuch);

View file

@ -80,6 +80,24 @@ test_expect_equal \
"$output" \ "$output" \
"$expected" "$expected"
# show the message using stashing decryption
test_begin_subtest "stash decryption during show"
output=$(notmuch show --decrypt=stash tag:encrypted subject:002 | notmuch_show_part 3)
expected='This is a test encrypted message with a wumpus.'
test_expect_equal \
"$output" \
"$expected"
test_begin_subtest "search should now find the contents"
output=$(notmuch search wumpus)
expected='thread:0000000000000003 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
test_subtest_known_broken
fi
test_expect_equal \
"$output" \
"$expected"
# try reinserting it with decryption, should appear again, but now we # try reinserting it with decryption, should appear again, but now we
# have two copies of the message: # have two copies of the message:
test_begin_subtest "message cleartext is present after reinserting with --decrypt=true" test_begin_subtest "message cleartext is present after reinserting with --decrypt=true"