diff options
| -rw-r--r-- | Changelog | 6 | ||||
| -rw-r--r-- | doc/interimap.1.md | 12 | ||||
| -rw-r--r-- | doc/pullimap.1.md | 12 | ||||
| -rw-r--r-- | lib/Net/IMAP/InterIMAP.pm | 23 | ||||
| -rw-r--r-- | tests/tls-pin-fingerprint/t | 37 | 
5 files changed, 75 insertions, 15 deletions
@@ -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 :  | 
