smime: tests of X.509 certificate validity are known-broken on GMime < 3.2.7

When checking cryptographic signatures, Notmuch relies on GMime to
tell it whether the certificate that signs a message has a valid User
ID or not.

If the User ID is not valid, then notmuch does not report the signer's
User ID to the user.  This means that the consumer of notmuch's
cryptographic summary of a message (or of its protected headers) can
be confident in relaying the reported identity to the user.

However, some versions of GMime before 3.2.7 cannot report Certificate
validity for X.509 certificates.  This is resolved upstream in GMime
at https://github.com/jstedfast/gmime/pull/90.

We adapt to this by marking tests of reported User IDs for
S/MIME-signed messages as known-broken if GMime is older than 3.2.7
and has not been patched.

If GMime >= 3.2.7 and certificate validity still doesn't work for
X.509 certs, then there has likely been a regression in GMime and we
should fail early, during ./configure.

To break out these specific User ID checks from other checks, i had to
split some tests into two parts, and reuse $output across the two
subtests.

Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
This commit is contained in:
Daniel Kahn Gillmor 2020-05-21 20:42:41 -04:00 committed by David Bremner
parent 627460d7bb
commit b14d9ae204
3 changed files with 102 additions and 11 deletions

83
configure vendored
View file

@ -494,7 +494,7 @@ int main () {
if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/crypto/basic-encrypted.eml\n"); if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/crypto/basic-encrypted.eml\n");
body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL))); body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n"); if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n");
output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_EXPORT_SESSION_KEY, NULL, &decrypt_result, &error); output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_EXPORT_SESSION_KEY, NULL, &decrypt_result, &error);
if (error || output == NULL) return !! fprintf (stderr, "decryption failed\n"); if (error || output == NULL) return !! fprintf (stderr, "decryption failed\n");
@ -536,6 +536,82 @@ EOF
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
rm -rf "$TEMP_GPG" rm -rf "$TEMP_GPG"
fi fi
# see https://github.com/jstedfast/gmime/pull/90
# should be fixed in GMime in 3.2.7, but some distros might patch
printf "Checking for GMime X.509 certificate validity... "
cat > _check_x509_validity.c <<EOF
#include <stdio.h>
#include <gmime/gmime.h>
int main () {
GError *error = NULL;
GMimeParser *parser = NULL;
GMimeApplicationPkcs7Mime *body = NULL;
GMimeSignatureList *sig_list = NULL;
GMimeSignature *sig = NULL;
GMimeCertificate *cert = NULL;
GMimeObject *output = NULL;
GMimeValidity validity = GMIME_VALIDITY_UNKNOWN;
int len;
g_mime_init ();
parser = g_mime_parser_new ();
g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("$srcdir/test/corpora/pkcs7/smime-onepart-signed.eml", "r", &error));
if (error) return !! fprintf (stderr, "failed to instantiate parser with test/corpora/pkcs7/smime-onepart-signed.eml\n");
body = GMIME_APPLICATION_PKCS7_MIME(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
if (body == NULL) return !! fprintf (stderr, "did not find a application/pkcs7 message\n");
sig_list = g_mime_application_pkcs7_mime_verify (body, GMIME_VERIFY_NONE, &output, &error);
if (error || output == NULL) return !! fprintf (stderr, "verify failed\n");
if (sig_list == NULL) return !! fprintf (stderr, "no GMimeSignatureList found\n");
len = g_mime_signature_list_length (sig_list);
if (len != 1) return !! fprintf (stderr, "expected 1 signature, got %d\n", len);
sig = g_mime_signature_list_get_signature (sig_list, 0);
if (sig == NULL) return !! fprintf (stderr, "no GMimeSignature found at position 0\n");
cert = g_mime_signature_get_certificate (sig);
if (cert == NULL) return !! fprintf (stderr, "no GMimeCertificate found\n");
validity = g_mime_certificate_get_id_validity (cert);
if (validity != GMIME_VALIDITY_FULL) return !! fprintf (stderr, "Got validity %d, expected %d\n", validity, GMIME_VALIDITY_FULL);
return 0;
}
EOF
if ! TEMP_GPG=$(mktemp -d "${TMPDIR:-/tmp}/notmuch.XXXXXX"); then
printf 'No.\nCould not make tempdir for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
elif ${CC} ${CFLAGS} ${gmime_cflags} _check_x509_validity.c ${gmime_ldflags} -o _check_x509_validity \
&& echo disable-crl-checks > "$TEMP_GPG/gpgsm.conf" \
&& echo "4D:E0:FF:63:C0:E9:EC:01:29:11:C8:7A:EE:DA:3A:9A:7F:6E:C1:0D S" >> "$TEMP_GPG/trustlist.txt" \
&& GNUPGHOME=${TEMP_GPG} gpgsm --batch --quiet --import < "$srcdir"/test/smime/ca.crt
then
if GNUPGHOME=${TEMP_GPG} ./_check_x509_validity; then
gmime_x509_cert_validity=1
printf "Yes.\n"
else
gmime_x509_cert_validity=0
printf "No.\n"
if pkg-config --exists "gmime-3.0 >= 3.2.7"; then
cat <<EOF
*** Error: GMime fails to calculate X.509 certificate validity, and
is later than 3.2.7, which should have fixed this issue.
Please follow up on https://github.com/jstedfast/gmime/pull/90 with
more details.
EOF
errors=$((errors + 1))
fi
fi
else
printf 'No.\nFailed to set up gpgsm for testing X.509 certificate validity support.\n'
errors=$((errors + 1))
fi
if [ -n "$TEMP_GPG" -a -d "$TEMP_GPG" ]; then
rm -rf "$TEMP_GPG"
fi
else else
have_gmime=0 have_gmime=0
printf "No.\n" printf "No.\n"
@ -1043,7 +1119,7 @@ for flag in -Wmissing-declarations; do
done done
printf "\n\t%s\n" "${WARN_CFLAGS}" printf "\n\t%s\n" "${WARN_CFLAGS}"
rm -f minimal minimal.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys rm -f minimal minimal.c _libversion.c _libversion _libversion.sh _check_session_keys.c _check_session_keys _check_x509_validity.c _check_x509_validity
# construct the Makefile.config # construct the Makefile.config
cat > Makefile.config <<EOF cat > Makefile.config <<EOF
@ -1334,6 +1410,9 @@ NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${WITH_RETRY_LOCK}
# Which backend will Xapian use by default? # Which backend will Xapian use by default?
NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend} NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
# Whether GMime can verify X.509 certificate validity
NOTMUCH_GMIME_X509_CERT_VALIDITY=${gmime_x509_cert_validity}
# do we have man pages? # do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx)) NOTMUCH_HAVE_MAN=$((have_sphinx))

View file

@ -187,13 +187,16 @@ test_valid_json "$output"
test_begin_subtest "Verify signature on PKCS#7 SignedData message" test_begin_subtest "Verify signature on PKCS#7 SignedData message"
test_subtest_known_broken test_subtest_known_broken
output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
test_json_nodes <<<"$output" \ test_json_nodes <<<"$output" \
'crypto:[0][0][0]["crypto"]["signed"]["status"][0]={ 'created:[0][0][0]["crypto"]["signed"]["status"][0]["created"]=1574813489' \
"created" : 1574813489, 'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
"expires" : 2611032858, 'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
"fingerprint" : "702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB", 'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
"userid" : "CN=Alice Lovelace",
"status" : "good" test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
}' test_subtest_known_broken
test_json_nodes <<<"$output" \
'userid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
test_done test_done

View file

@ -163,8 +163,13 @@ for variant in multipart-signed onepart-signed; do
'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \
'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \ 'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \
'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' \
'not_encrypted:[0][0][0]["crypto"]!"decrypted"' 'not_encrypted:[0][0][0]["crypto"]!"decrypted"'
test_begin_subtest "verify signed PKCS#7 subject ($variant) signer User ID"
if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ] || [ "$variant" != multipart-signed ]; then
test_subtest_known_broken
fi
test_json_nodes <<<"$output" \
'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
done done
for variant in sign+enc sign+enc+legacy-disp; do for variant in sign+enc sign+enc+legacy-disp; do
@ -175,8 +180,12 @@ for variant in sign+enc sign+enc+legacy-disp; do
'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \ 'signed_subject:[0][0][0]["crypto"]["signed"]["headers"]=["Subject"]' \
'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \ 'sig_good:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' \
'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ 'sig_fpr:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"' \
'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}' 'encrypted:[0][0][0]["crypto"]["decrypted"]={"status":"full","header-mask":{"Subject":"..."}}'
test_begin_subtest "confirm signed and encrypted PKCS#7 subject ($variant) signer User ID"
test_subtest_known_broken
test_json_nodes <<<"$output" \
'sig_uid:[0][0][0]["crypto"]["signed"]["status"][0]["userid"]="CN=Alice Lovelace"'
done done
test_begin_subtest "confirm encryption-protected PKCS#7 subject (enc+legacy-disp)" test_begin_subtest "confirm encryption-protected PKCS#7 subject (enc+legacy-disp)"