lib: index PKCS7 SignedData parts

When we are indexing, we should treat SignedData parts the same way
that we treat a multipart object, indexing the wrapped part as a
distinct MIME object.

Unfortunately, this means doing some sort of cryptographic
verification whose results we throw away, because GMime doesn't offer
us any way to unwrap without doing signature verification.

I've opened https://github.com/jstedfast/gmime/issues/67 to request
the capability from GMime but for now, we'll just accept the
additional performance hit.

As we do this indexing, we also apply the "signed" tag, by analogy
with how we handle multipart/signed messages.  These days, that kind
of change should probably be done with a property instead, but that's
a different set of changes.  This one is just for consistency.

Note that we are currently *only* handling signedData parts, which are
basically clearsigned messages.  PKCS#7 parts can also be
envelopedData and authEnvelopedData (which are effectively encryption
layers), and compressedData (which afaict isn't implemented anywhere,
i've never encountered it).  We're laying the groundwork for indexing
these other S/MIME types here, but we're only dealing with signedData
for now.

Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
This commit is contained in:
Daniel Kahn Gillmor 2020-05-12 18:29:31 -04:00 committed by David Bremner
parent 7880092964
commit 38bd0df922
2 changed files with 57 additions and 2 deletions

View file

@ -372,6 +372,12 @@ _index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *ind
GMimeMultipartEncrypted *part, GMimeMultipartEncrypted *part,
_notmuch_message_crypto_t *msg_crypto); _notmuch_message_crypto_t *msg_crypto);
static void
_index_pkcs7_part (notmuch_message_t *message,
notmuch_indexopts_t *indexopts,
GMimeObject *part,
_notmuch_message_crypto_t *msg_crypto);
/* Callback to generate terms for each mime part of a message. */ /* Callback to generate terms for each mime part of a message. */
static void static void
_index_mime_part (notmuch_message_t *message, _index_mime_part (notmuch_message_t *message,
@ -466,6 +472,11 @@ _index_mime_part (notmuch_message_t *message,
goto DONE; goto DONE;
} }
if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) {
_index_pkcs7_part (message, indexopts, part, msg_crypto);
goto DONE;
}
if (! (GMIME_IS_PART (part))) { if (! (GMIME_IS_PART (part))) {
_notmuch_database_log (notmuch_message_get_database (message), _notmuch_database_log (notmuch_message_get_database (message),
"Warning: Not indexing unknown mime part: %s.\n", "Warning: Not indexing unknown mime part: %s.\n",
@ -608,6 +619,52 @@ _index_encrypted_mime_part (notmuch_message_t *message,
} }
static void
_index_pkcs7_part (notmuch_message_t *message,
notmuch_indexopts_t *indexopts,
GMimeObject *part,
_notmuch_message_crypto_t *msg_crypto)
{
GMimeApplicationPkcs7Mime *pkcs7;
GMimeSecureMimeType p7type;
GMimeObject *mimeobj = NULL;
GMimeSignatureList *sigs = NULL;
GError *err = NULL;
notmuch_database_t *notmuch = NULL;
pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
p7type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7);
notmuch = notmuch_message_get_database (message);
_index_content_type (message, part);
if (p7type == GMIME_SECURE_MIME_TYPE_SIGNED_DATA) {
sigs = g_mime_application_pkcs7_mime_verify (pkcs7, GMIME_VERIFY_NONE, &mimeobj, &err);
if (sigs == NULL) {
_notmuch_database_log (notmuch, "Failed to verify PKCS#7 SignedData during indexing. (%d:%d) [%s]\n",
err->domain, err->code, err->message);
g_error_free (err);
goto DONE;
}
_notmuch_message_add_term (message, "tag", "signed");
GMimeObject *toindex = mimeobj;
if (_notmuch_message_crypto_potential_payload (msg_crypto, mimeobj, part, 0) &&
msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
toindex = _notmuch_repair_crypto_payload_skip_legacy_display (mimeobj);
if (toindex != mimeobj)
notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
}
_index_mime_part (message, indexopts, toindex, msg_crypto);
} else {
_notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n",
g_mime_object_get_content_type_parameter (part, "smime-type"));
}
DONE:
if (mimeobj)
g_object_unref (mimeobj);
if (sigs)
g_object_unref (sigs);
}
static notmuch_status_t static notmuch_status_t
_notmuch_message_index_user_headers (notmuch_message_t *message, GMimeMessage *mime_message) _notmuch_message_index_user_headers (notmuch_message_t *message, GMimeMessage *mime_message)
{ {

View file

@ -132,13 +132,11 @@ expected=''
test_expect_equal "$expected" "$output" test_expect_equal "$expected" "$output"
test_begin_subtest "know the MIME type of the embedded part in PKCS#7 SignedData" test_begin_subtest "know the MIME type of the embedded part in PKCS#7 SignedData"
test_subtest_known_broken
output=$(notmuch search --output=messages 'mimetype:text/plain') output=$(notmuch search --output=messages 'mimetype:text/plain')
expected=id:smime-onepart-signed@protected-headers.example expected=id:smime-onepart-signed@protected-headers.example
test_expect_equal "$expected" "$output" test_expect_equal "$expected" "$output"
test_begin_subtest "PKCS#7 SignedData message is tagged 'signed'" test_begin_subtest "PKCS#7 SignedData message is tagged 'signed'"
test_subtest_known_broken
output=$(notmuch dump id:smime-onepart-signed@protected-headers.example) output=$(notmuch dump id:smime-onepart-signed@protected-headers.example)
expected='#notmuch-dump batch-tag:3 config,properties,tags expected='#notmuch-dump batch-tag:3 config,properties,tags
+inbox +signed +unread -- id:smime-onepart-signed@protected-headers.example' +inbox +signed +unread -- id:smime-onepart-signed@protected-headers.example'