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.
This commit is contained in:
Daniel Kahn Gillmor 2017-12-08 01:23:53 -05:00 committed by David Bremner
parent 798aa789b5
commit e4890b5bf9
10 changed files with 49 additions and 17 deletions

View file

@ -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.

View file

@ -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);

View file

@ -33,11 +33,14 @@ notmuch_database_get_default_indexopts (notmuch_database_t *db)
if (err)
return ret;
if (decrypt_policy &&
((!(strcasecmp(decrypt_policy, "true"))) ||
if (decrypt_policy) {
if ((!(strcasecmp(decrypt_policy, "true"))) ||
(!(strcasecmp(decrypt_policy, "yes"))) ||
(!(strcasecmp(decrypt_policy, "1")))))
(!(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;

View file

@ -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;
/**

View file

@ -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 "

View file

@ -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.
*

View file

@ -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"

View file

@ -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 <<EOF
#notmuch-dump batch-tag:3 config,properties,tags
#= simple-encrypted@crypto.notmuchmail.org session-key=9%3AFC09987F5F927CC0CC0EE80A96E4C5BBF4A499818FB591207705DFDDD6112CF9
EOF
notmuch reindex --decrypt=true id:simple-encrypted@crypto.notmuchmail.org
notmuch reindex --decrypt=auto id:simple-encrypted@crypto.notmuchmail.org
output=$(notmuch search sekrit)
expected='thread:0000000000000001 2016-12-22 [1/1] Daniel Kahn Gillmor; encrypted message (encrypted inbox unread)'
if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then

View file

@ -140,13 +140,16 @@ void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
#endif
GMimeObject *
_notmuch_crypto_decrypt (notmuch_message_t *message,
_notmuch_crypto_decrypt (notmuch_decryption_policy_t decrypt,
notmuch_message_t *message,
g_mime_3_unused(GMimeCryptoContext* crypto_ctx),
GMimeMultipartEncrypted *part,
GMimeDecryptResult **decrypt_result,
GError **err)
{
GMimeObject *ret = NULL;
if (decrypt == NOTMUCH_DECRYPT_FALSE)
return NULL;
/* the versions of notmuch that can support session key decryption */
#if HAVE_GMIME_SESSION_KEYS
@ -184,6 +187,10 @@ _notmuch_crypto_decrypt (notmuch_message_t *message,
g_error_free (*err);
*err = NULL;
}
if (decrypt == NOTMUCH_DECRYPT_AUTO)
return ret;
#if (GMIME_MAJOR_VERSION < 3)
ret = g_mime_multipart_encrypted_decrypt(part, crypto_ctx,
decrypt_result, err);

View file

@ -16,7 +16,8 @@ typedef struct _notmuch_crypto {
} _notmuch_crypto_t;
GMimeObject *
_notmuch_crypto_decrypt (notmuch_message_t *message,
_notmuch_crypto_decrypt (notmuch_decryption_policy_t decrypt,
notmuch_message_t *message,
GMimeCryptoContext* crypto_ctx,
GMimeMultipartEncrypted *part,
GMimeDecryptResult **decrypt_result,