aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2020-12-09 15:06:37 +0100
committerGuilhem Moulin <guilhem@fripost.org>2020-12-09 15:29:54 +0100
commita1ef66a76b4a6651b7371a9fd1e35f2f99e85bfa (patch)
treefbc80ff754618b91f2fc518cff8c71175b9a0e92
parentb13c9fa6f442f555af65f869b954935dae40fcc4 (diff)
libinterimap: SSL_fingerprint now supports a space-separate list of digests to pin.
And succeeds if, and only if, the peer certificate SPKI matches one of the pinned digest values. Specifying multiple digest values can key useful in key rollover scenarios and/or when the server supports certificates of different types (for instance RSA+ECDSA).
-rw-r--r--Changelog6
-rw-r--r--doc/interimap.1.md12
-rw-r--r--doc/pullimap.1.md12
-rw-r--r--lib/Net/IMAP/InterIMAP.pm23
-rw-r--r--tests/tls-pin-fingerprint/t37
5 files changed, 75 insertions, 15 deletions
diff --git a/Changelog b/Changelog
index 341d5f7..e400b37 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,11 @@
interimap (0.5.3) upstream;
+ * libinterimap: SSL_fingerprint now supports a space-separate list of
+ digests to pin, and succeeds if, and only if, the peer certificate
+ SPKI matches one of the pinned digest values. Specifying multiple
+ digest values can key useful in key rollover scenarios and/or when
+ the server supports certificates of different types (for instance
+ RSA+ECDSA).
- libinterimap: 'null-stderr' is now ignored when the 'debug' flag is
set (the standard error is never sent to /dev/null).
- test suite: use a RSA certificate rather than ECDSA.
diff --git a/doc/interimap.1.md b/doc/interimap.1.md
index c70698b..9b53a69 100644
--- a/doc/interimap.1.md
+++ b/doc/interimap.1.md
@@ -397,9 +397,10 @@ Valid options are:
*SSL_fingerprint*
-: Fingerprint of the server certificate's Subject Public Key Info, in
- the form `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm
- (by default `sha256`).
+: Space-separated list of acceptable fingerprints for the server
+ certificate's Subject Public Key Info, in the form
+ `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm (by default
+ `sha256`).
Attempting to connect to a server with a non-matching certificate
SPKI fingerprint causes `interimap` to abort the connection during
the SSL/TLS handshake.
@@ -410,6 +411,11 @@ Valid options are:
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256
+ Specifying multiple digest values can be useful in key rollover
+ scenarios and/or when the server supports certificates of different
+ types (for instance RSA+ECDSA). In that case the connection is
+ aborted when none of the specified digests matches.
+
*SSL_verify*
: Whether to verify the server certificate chain.
diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md
index 87cafbf..2bc4212 100644
--- a/doc/pullimap.1.md
+++ b/doc/pullimap.1.md
@@ -216,9 +216,10 @@ Valid options are:
*SSL_fingerprint*
-: Fingerprint of the server certificate's Subject Public Key Info, in
- the form `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm
- (by default `sha256`).
+: Space-separated list of acceptable fingerprints for the server
+ certificate's Subject Public Key Info, in the form
+ `[ALGO$]DIGEST_HEX` where `ALGO` is the digest algorithm (by default
+ `sha256`).
Attempting to connect to a server with a non-matching certificate
SPKI fingerprint causes `pullimap` to abort the connection during
the SSL/TLS handshake.
@@ -229,6 +230,11 @@ Valid options are:
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256
+ Specifying multiple digest values can be useful in key rollover
+ scenarios and/or when the server supports certificates of different
+ types (for instance RSA+ECDSA). In that case the connection is
+ aborted when none of the specified digests matches.
+
*SSL_verify*
: Whether to verify the server certificate chain.
diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm
index bd14625..1a71f59 100644
--- a/lib/Net/IMAP/InterIMAP.pm
+++ b/lib/Net/IMAP/InterIMAP.pm
@@ -63,7 +63,7 @@ my %OPTIONS = (
'null-stderr' => qr/\A(YES|NO)\z/i,
compress => qr/\A(YES|NO)\z/i,
SSL_protocols => qr/\A(!?$RE_SSL_PROTO(?: !?$RE_SSL_PROTO)*)\z/,
- SSL_fingerprint => qr/\A((?:[A-Za-z0-9]+\$)?\p{AHex}+)\z/,
+ SSL_fingerprint => qr/\A((?:[A-Za-z0-9]+\$)?\p{AHex}+(?: (?:[A-Za-z0-9]+\$)?\p{AHex}+)*)\z/,
SSL_cipherlist => qr/\A(\P{Control}+)\z/,
SSL_verify => qr/\A(YES|NO)\z/i,
SSL_CApath => qr/\A(\P{Control}+)\z/,
@@ -1624,15 +1624,22 @@ sub _ssl_verify($$$) {
.$algo.'$'.unpack('H*', Net::SSLeay::X509_digest($cert, $type)));
}
- if (defined (my $fpr = $self->{SSL_fingerprint})) {
- (my $algo, $fpr) = $fpr =~ /^([^\$]+)\$(.*)/ ? ($1, $2) : ('sha256', $fpr);
- my $digest = pack 'H*', ($fpr =~ tr/://rd);
+ if (defined (my $fprs = $self->{SSL_fingerprint})) {
+ my $rv = 0;
+ foreach my $fpr (split /\s+/, $fprs) {
+ (my $algo, $fpr) = $fpr =~ /^([^\$]+)\$(.*)/ ? ($1, $2) : ('sha256', $fpr);
+ my $digest = pack 'H*', ($fpr =~ tr/://rd);
- my $type = Net::SSLeay::EVP_get_digestbyname($algo)
- or $self->_ssl_error("Can't find MD value for name '$algo'");
+ my $type = Net::SSLeay::EVP_get_digestbyname($algo)
+ or $self->_ssl_error("Can't find MD value for name '$algo'");
- my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert);
- unless (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) {
+ my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert);
+ if (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) {
+ $rv = 1;
+ last;
+ }
+ }
+ unless ($rv) {
$self->warn("Fingerprint doesn't match! MiTM in action?");
$ok = 0;
}
diff --git a/tests/tls-pin-fingerprint/t b/tests/tls-pin-fingerprint/t
index 612bc44..d3830e2 100644
--- a/tests/tls-pin-fingerprint/t
+++ b/tests/tls-pin-fingerprint/t
@@ -1,6 +1,8 @@
PKEY_SHA256="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_cert \
| openssl x509 -pubkey | openssl pkey -pubin -outform DER \
| openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}")"
+INVALID_FPR="sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+INVALID_FPR2="sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbee2"
# backup config
install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
@@ -22,9 +24,28 @@ interimap_init
check_mailbox_status "INBOX"
+# with default algorithm (SHA256)
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR $PKEY_SHA256
+EOF
+interimap || error
+
+
# and now an invalid one
with_remote_config <<-EOF
- SSL_fingerprint = sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+ SSL_fingerprint = $INVALID_FPR
+EOF
+! interimap --debug || error
+
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+# make sure we didn't send any credentials
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# two invalid ones
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR $INVALID_FPR2
EOF
! interimap --debug || error
@@ -34,4 +55,18 @@ grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
# make sure we didn't send any credentials
! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# valid + invalid
+with_remote_config <<-EOF
+ SSL_fingerprint = sha256\$$PKEY_SHA256 $INVALID_FPR
+EOF
+interimap || error
+
+
+# invalid + valid
+with_remote_config <<-EOF
+ SSL_fingerprint = $INVALID_FPR sha256\$$PKEY_SHA256
+EOF
+interimap || error
+
# vim: set filetype=sh :