From 7c5ec1cb62410f8cd7ca31c2d2a5ec92d7f0de7b Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 9 Dec 2020 20:03:17 +0100 Subject: Fix broken URLs. --- doc/interimap.1.md | 4 ++-- doc/pullimap.1.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 7df0100..2c9ddc6 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -568,6 +568,6 @@ A _getting started_ guide is available [there](getting-started.html). [INI file]: https://en.wikipedia.org/wiki/INI_file [PCRE]: https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions -[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html -[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/verify.html +[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html +[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-verify.html [`doveadm-deduplicate`(1)]: https://wiki.dovecot.org/Tools/Doveadm/Deduplicate diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index 98ec2ef..42ea282 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -378,5 +378,5 @@ Standards [`fetchmail`(1)]: https://www.fetchmail.info/ [`getmail`(1)]: http://pyropus.ca/software/getmail/ [`write`(2)]: https://man7.org/linux/man-pages/man2/write.2.html -[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html -[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/verify.html +[`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html +[`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/man1/openssl-verify.html -- cgit v1.2.3 From c011e17d4f238882686e3f0e59c444a1c53ac8e3 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 14:24:49 +0100 Subject: documentation: replace example.org with example.net for consistency. --- Changelog | 6 ++++++ doc/getting-started.md | 2 +- interimap.sample | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index 4d9b9a4..6036b46 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,9 @@ +interimap (0.5.4) upstream; + + - documentation: replace example.org with example.net for consistency. + + -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 + interimap (0.5.3) upstream; * libinterimap: SSL_fingerprint now supports a space-separate list of diff --git a/doc/getting-started.md b/doc/getting-started.md index 1d059b4..83d3ba9 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -198,7 +198,7 @@ for the sake of clarity we start from an empty file here. shell process doesn't linger around during the IMAP session.) 3. And finally append a `[remote]` section with your account - information at `imap.example.org` (adapt the values accordingly): + information at `imap.example.net` (adapt the values accordingly): $ cat >>${XDG_CONFIG_HOME:-~/.config}/interimap/config <<-EOF diff --git a/interimap.sample b/interimap.sample index 2a7b8de..b4d131c 100644 --- a/interimap.sample +++ b/interimap.sample @@ -1,4 +1,4 @@ -#database = imap.example.org.db +#database = imap.example.net.db # only consider subscribed mailboxes list-select-opts = SUBSCRIBED @@ -15,7 +15,7 @@ null-stderr = YES [remote] #type = imaps -host = imap.example.org +host = imap.example.net #port = 993 #proxy = socks5h://localhost:9050 username = guilhem -- cgit v1.2.3 From 17b263c49df682fc45f0e50cceb01db4366ad9a7 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 14:28:29 +0100 Subject: libinterimap: show the matching pinned SPKI in --debug mode. --- Changelog | 1 + lib/Net/IMAP/InterIMAP.pm | 1 + tests/tls-pin-fingerprint/t | 9 ++++++--- tests/tls-rsa+ecdsa/t | 7 ++++++- tests/tls-verify-peer/t | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Changelog b/Changelog index 6036b46..cd474a0 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,6 @@ interimap (0.5.4) upstream; + + libinterimap: show the matching pinned SPKI in --debug mode. - documentation: replace example.org with example.net for consistency. -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 1a71f59..849dc0f 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1635,6 +1635,7 @@ sub _ssl_verify($$$) { my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert); if (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) { + $self->log('Peer certificate matches pinned SPKI digest ', $algo .'$'. $fpr) if $self->{debug}; $rv = 1; last; } diff --git a/tests/tls-pin-fingerprint/t b/tests/tls-pin-fingerprint/t index d3830e2..6716833 100644 --- a/tests/tls-pin-fingerprint/t +++ b/tests/tls-pin-fingerprint/t @@ -28,7 +28,8 @@ check_mailbox_status "INBOX" with_remote_config <<-EOF SSL_fingerprint = $INVALID_FPR $PKEY_SHA256 EOF -interimap || error +interimap --debug || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error # and now an invalid one @@ -60,13 +61,15 @@ grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error with_remote_config <<-EOF SSL_fingerprint = sha256\$$PKEY_SHA256 $INVALID_FPR EOF -interimap || error +interimap --debug || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error # invalid + valid with_remote_config <<-EOF SSL_fingerprint = $INVALID_FPR sha256\$$PKEY_SHA256 EOF -interimap || error +interimap --debug || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error # vim: set filetype=sh : diff --git a/tests/tls-rsa+ecdsa/t b/tests/tls-rsa+ecdsa/t index 29352e9..2adf930 100644 --- a/tests/tls-rsa+ecdsa/t +++ b/tests/tls-rsa+ecdsa/t @@ -32,6 +32,9 @@ interimap --debug || error grep -Fx -e "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" \ -e "remote: Peer certificate fingerprint: sha256\$$X509_ALT_SHA256" \ <"$STDERR" || error +grep -Fx -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" \ + -e "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_ALT_SHA256" \ + <"$STDERR" || error # force RSA (XXX do we really have to force TLSv1.2 here?) cat >>"$XDG_CONFIG_HOME/interimap/config" <<-EOF @@ -40,10 +43,12 @@ cat >>"$XDG_CONFIG_HOME/interimap/config" <<-EOF EOF interimap --debug || error grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error # force ECDSA -sed -i "s/^SSL_cipherlist\\s*=.*/SSL_cipherlist = EECDH+AESGCM+aECDSA/" "$XDG_CONFIG_HOME/interimap/config" +sed -i "s/^SSL_cipherlist\\s*=.*/SSL_cipherlist = EECDH+AESGCM+aECDSA/" -- "$XDG_CONFIG_HOME/interimap/config" interimap --debug || error grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_ALT_SHA256" <"$STDERR" || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_ALT_SHA256" <"$STDERR" || error # vim: set filetype=sh : diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t index 9e4d9fa..35c7c8d 100644 --- a/tests/tls-verify-peer/t +++ b/tests/tls-verify-peer/t @@ -46,7 +46,7 @@ with_remote_config <<-EOF SSL_fingerprint = sha256\$$PKEY_SHA256 EOF unverified_peer -! grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error +grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA256" <"$STDERR" || error step_done capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX) -- cgit v1.2.3 From 26e5c04abfb81bdcbd4d89d9f9329b8433920b26 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 14:34:48 +0100 Subject: test suite: always generate new certificates on `make test`. In addition, sign test certificates with the same root CA. Hence running `make test` now requires OpenSSL 1.1.1 or later. --- Changelog | 3 +++ Makefile | 3 ++- tests/certs/.gitignore | 4 ++++ tests/certs/generate | 38 ++++++++++++++++++++++++++++++++ tests/run | 1 + tests/snippets/dovecot/dovecot.ecdsa.crt | 11 --------- tests/snippets/dovecot/dovecot.ecdsa.key | 5 ----- tests/snippets/dovecot/dovecot.rsa.crt | 19 ---------------- tests/snippets/dovecot/dovecot.rsa.key | 28 ----------------------- tests/tls-verify-peer/t | 10 ++++----- 10 files changed, 53 insertions(+), 69 deletions(-) create mode 100644 tests/certs/.gitignore create mode 100755 tests/certs/generate delete mode 100644 tests/snippets/dovecot/dovecot.ecdsa.crt delete mode 100644 tests/snippets/dovecot/dovecot.ecdsa.key delete mode 100644 tests/snippets/dovecot/dovecot.rsa.crt delete mode 100644 tests/snippets/dovecot/dovecot.rsa.key diff --git a/Changelog b/Changelog index cd474a0..7a04963 100644 --- a/Changelog +++ b/Changelog @@ -1,6 +1,9 @@ interimap (0.5.4) upstream; + libinterimap: show the matching pinned SPKI in --debug mode. + + test suite: always generate new certificates on `make test`. Hence + running `make test` now requires OpenSSL 1.1.1 or later. + + test suite: sign all test certificates with the same root CA. - documentation: replace example.org with example.net for consistency. -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/Makefile b/Makefile index 3d60dfb..b85cddd 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ $(MANUAL_FILES): $(BUILD_DOCDIR)/%: ./doc/%.md pandoc -f markdown -t json -- "$<" | ./pandoc2man.jq | pandoc -s -f json -t man -o "$@" test: - @./tests/run-all + ./tests/certs/generate + ./tests/run-all ## make html CSS="https://guilhem.org/static/css/bootstrap.min.css" BUILD_DOCDIR="$XDG_RUNTIME_DIR/Downloads" $(HTML_FILES): $(BUILD_DOCDIR)/%.html: ./doc/%.md $(HTML_TEMPLATE) diff --git a/tests/certs/.gitignore b/tests/certs/.gitignore new file mode 100644 index 0000000..8b2d0ad --- /dev/null +++ b/tests/certs/.gitignore @@ -0,0 +1,4 @@ +!/generate +/*.key +/*.crt +/*.pem diff --git a/tests/certs/generate b/tests/certs/generate new file mode 100755 index 0000000..19463d5 --- /dev/null +++ b/tests/certs/generate @@ -0,0 +1,38 @@ +#!/bin/sh + +set -ue +PATH="/usr/bin:/bin" +export PATH + +BASEDIR="$(dirname -- "$0")" +OU="InterIMAP test suite" +cd "$BASEDIR" + +cadir="$(mktemp --tmpdir --directory)" +trap 'rm -rf -- "$cadir"' EXIT INT TERM + +# generate CA (we intentionally throw away the private key and serial +# file to avoid reuse) +openssl genpkey -algorithm RSA -out "$cadir/ca.key" +openssl req -new -x509 -rand /dev/urandom -subj "/OU=$OU/CN=Fake Root CA" -key "$cadir/ca.key" -out ./ca.crt + +SERIAL=1 +new() { + local key="$1" cn="$2" + openssl req -new -rand /dev/urandom -key "$key" \ + -subj "/OU=$OU/CN=$cn" \ + -out "$cadir/new.csr" + cat >"$cadir/new-ext.cnf" <<-EOF + basicConstraints = critical, CA:FALSE + keyUsage = critical, digitalSignature, keyEncipherment + extendedKeyUsage = critical, serverAuth + EOF + openssl x509 -req -in "$cadir/new.csr" -CA ./ca.crt -CAkey "$cadir/ca.key" \ + -CAserial "$cadir/ca.srl" -CAcreateserial -extfile "$cadir/new-ext.cnf" +} + +openssl genpkey -algorithm RSA -out ./dovecot.rsa.key +new ./dovecot.rsa.key "localhost" >./dovecot.rsa.crt + +openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out ./dovecot.ecdsa.key +new ./dovecot.ecdsa.key "localhost" >./dovecot.ecdsa.crt diff --git a/tests/run b/tests/run index 8164524..0305812 100755 --- a/tests/run +++ b/tests/run @@ -102,6 +102,7 @@ prepare() { cat >>"$home/.dovecot/config" <"$TESTDIR/$u.conf" fi cp -aT -- "$BASEDIR/snippets/dovecot" "$home/.dovecot/conf.d" + cp -at "$home/.dovecot/conf.d" -- "$BASEDIR/certs/ca.crt" "$BASEDIR/certs"/dovecot.* proto="$(env -i "${ENVIRON[@]}" doveconf -c "$home/.dovecot/config" -h protocols)" if [ -n "$proto" ]; then diff --git a/tests/snippets/dovecot/dovecot.ecdsa.crt b/tests/snippets/dovecot/dovecot.ecdsa.crt deleted file mode 100644 index b928d4d..0000000 --- a/tests/snippets/dovecot/dovecot.ecdsa.crt +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBkjCCATmgAwIBAgIUWyEAqMhQ0uRLtagXgm68bUypQa4wCgYIKoZIzj0EAwIw -HzEdMBsGA1UEAwwUSW50ZXJJTUFQIHRlc3Qgc3VpdGUwHhcNMjAxMjA5MTQwOTUy -WhcNMzAxMjA3MTQwOTUyWjAfMR0wGwYDVQQDDBRJbnRlcklNQVAgdGVzdCBzdWl0 -ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFP7A0ivFsHK/WuCQzz+WWh2jBLO -7uqhWSMh+1cc//jmn2q910XNH3xVFNkIRo7ddg6X8twli3OvC66/YIbxiTyjUzBR -MB0GA1UdDgQWBBS/p0mJpdBjKpNrQ/t+oJMrehS7wzAfBgNVHSMEGDAWgBS/p0mJ -pdBjKpNrQ/t+oJMrehS7wzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA -MEQCIFMlTb7E92tElIueK8TxbllJ3NOaMb1TMjSScM38N8oOAiAiNI4AkESnimPN -IOsdnydFYjOkDEhzpXbrBEcP3EgJuQ== ------END CERTIFICATE----- diff --git a/tests/snippets/dovecot/dovecot.ecdsa.key b/tests/snippets/dovecot/dovecot.ecdsa.key deleted file mode 100644 index dfbd4a7..0000000 --- a/tests/snippets/dovecot/dovecot.ecdsa.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgleLchaikcJbUnkps -4ITR6FGkW2S2+S+w2ISJSsvgt0ehRANCAART+wNIrxbByv1rgkM8/llodowSzu7q -oVkjIftXHP/45p9qvddFzR98VRTZCEaO3XYOl/LcJYtzrwuuv2CG8Yk8 ------END PRIVATE KEY----- diff --git a/tests/snippets/dovecot/dovecot.rsa.crt b/tests/snippets/dovecot/dovecot.rsa.crt deleted file mode 100644 index d10204b..0000000 --- a/tests/snippets/dovecot/dovecot.rsa.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDHzCCAgegAwIBAgIUKSm5of13M/4NiGfhLMspFl/+YmYwDQYJKoZIhvcNAQEL -BQAwHzEdMBsGA1UEAwwUSW50ZXJJTUFQIHRlc3Qgc3VpdGUwHhcNMjAxMjA5MTM1 -NjI1WhcNMzAxMjA3MTM1NjI1WjAfMR0wGwYDVQQDDBRJbnRlcklNQVAgdGVzdCBz -dWl0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1DTaSl/4UtngRG -bAHmxHlNFZJxQVK9AM4tcYna1PGrY/JbmS5kKVFLSM6znHD5aBvTaOy0HLpF15wY -Vj+zbaWmgqtlKGYGSGoXcTzNYFNJNB/WNhOv25q5VHNNFePTX/zOgQS8geza7qrK -MZDiMlbuGKCQSKtZqKiEGiMWIyXtVi8BkkHXcTrvDggOTCQlk/0v8dWbGFZZA9ly -f7PIdxtfm6tacw6Fxcz4ukWx2uoEjOIyOYhgd4WYdM7L9Jnabrh9OHYknuiGZv38 -b2GUZZ0h0RtkcdP1zOxaz4ZTaewo+gLm6yTFsL3mhnNsK/xxx00/QE6C9OyU0Nip -gGmpT9ECAwEAAaNTMFEwHQYDVR0OBBYEFHlctzGj8GhUJ8GrlHb0mT7DR/mEMB8G -A1UdIwQYMBaAFHlctzGj8GhUJ8GrlHb0mT7DR/mEMA8GA1UdEwEB/wQFMAMBAf8w -DQYJKoZIhvcNAQELBQADggEBAJ/FGOVrBmYujPk2ZzJHJZE/+7+upZndrUA+27l7 -u/bHxhLnl94gfGmOaflU+Zyy/9eqLzllY40wkMT6d/SQmfv4C6d+fqk/dDPfdLdk -N3ew/q/sPvLuEyoj1QoHamWqc3dfgV6p5j4ek6kjyWtjBPcQbVOZ02Xes1GSLzVJ -Yo9kfbZxk4Y2mqiBDHCM+erNkG002D7cWErjj/fqhYlnjOxU+v9FEm0gLc3VqAkE -BRuYZbmyMJUklH00R39G2Fey34kcpaB1VCMOLsymWLkZEhfgrl2qPRwGyh+Wc8N5 -gR/w97oHDOfJ2oZRzjRUB7MIhGoY0ED42Ma44Ub4al57XbY= ------END CERTIFICATE----- diff --git a/tests/snippets/dovecot/dovecot.rsa.key b/tests/snippets/dovecot/dovecot.rsa.key deleted file mode 100644 index ed77230..0000000 --- a/tests/snippets/dovecot/dovecot.rsa.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNQ02kpf+FLZ4E -RmwB5sR5TRWScUFSvQDOLXGJ2tTxq2PyW5kuZClRS0jOs5xw+Wgb02jstBy6Rdec -GFY/s22lpoKrZShmBkhqF3E8zWBTSTQf1jYTr9uauVRzTRXj01/8zoEEvIHs2u6q -yjGQ4jJW7higkEirWaiohBojFiMl7VYvAZJB13E67w4IDkwkJZP9L/HVmxhWWQPZ -cn+zyHcbX5urWnMOhcXM+LpFsdrqBIziMjmIYHeFmHTOy/SZ2m64fTh2JJ7ohmb9 -/G9hlGWdIdEbZHHT9czsWs+GU2nsKPoC5uskxbC95oZzbCv8ccdNP0BOgvTslNDY -qYBpqU/RAgMBAAECggEANzx5VGlnTYttDnF09z4GeS4JNBNOJNm/sbwA5bwBudcJ -WlrT6ewCQmIkAZvL6Yr0PSiy/5+oa2gIEXVrIFFEnGMmnsDmEi52pjYQvu/1j/QP -FtIqUznrusNMuopv7ZMgLYPUrFWeEQMJXuRyWi7EpSgFcI/jPlkuTcrezbpTUw0D -bNAQjgXiDGNzyJDVmx496CWtTJHE94wwKo2QAiFU7zCZcqM7JlNCnRenws4KGqJ1 -qyFeCJJlgORQDMpiqaJLMreF41WPs++Xsu07RzQdmFKaS/sX4Um/5uyhlApmxepR -cwx3RYvOtGArQKreNONn5j16O012DSbFXIyrUjJgAQKBgQD48iZmm2iiq4oC1u+/ -kYPXMHjUBHeMj8D3lA2mnCh4W/vcEj+ZBFgmR90KyYkK2eDuFslvfwzxZqU3sqJE -au4OsITsSrxhJHAz4pVWPlJiWUCrz/7ektxY6Jw2+Jk8lR3UvLMgJbKpLWkd5Od+ -h5xKNU5Xzu198yX703k4+v5rgQKBgQDTFEcIJK9BffQ5hqMiM7NujdbMQ4ldxUy+ -ivyHk4MX4Z/kequE1rMJ1Ap8hypPJz10NhXM2naQWa8APnFYkNygwbtwTSZaiyMY -Tav8rjYoA0CfEsPfIx2AFPtDtrhWGH5o9LZI9sjhH79ud412IDZxSGdZmAho8Xdj -ky5sQr9MUQKBgByF+jplcg65Yt3CbMPhU17TkfSQ8nWrfuufDhVZ7RUlTO1BNgI9 -SjBQqZXz03zny+rbt4bL4trB7Qo9sHPwYIhUV1aPlZf3yddYDc5M47mbClrlQQmV -gCO7uzJdN4mGeF2IpWl4iEj0CAhB0vhfZ1vlUa2j6vg0ZNS+vTP3JjGBAoGAQ+gW -IgyLRWqcE5W5DdvMMhj3radcngpHclWMgKF4X0p7Aipk28umtda9uOpTNjvNjYGI -6equkioIHu/3zyJrmFw7TRnE6QQyOjNizVvOmHjTZVnIIhVN/FLDszkpfKlMob94 -lWivn51zHLrhi8s5OKCufyhmLDzix+ol2TZwDMECgYEAwjhuZRXZeIgjKkvkG+FT -8ThPNcxSplNca+YM9fQuWAuKkCbKCtvl8m5HDWYYIDx1jkKGHvDGtUl7vV4TtCgJ -OeCQPjT5SLYs9ienMqitbzKfvCGRNsIG/1NsUrerD0Lau+V0YmbqYYk1Pptr3R8x -bLzY7IMbPzdI+aPyhNF9KSg= ------END PRIVATE KEY----- diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t index 35c7c8d..9b676a6 100644 --- a/tests/tls-verify-peer/t +++ b/tests/tls-verify-peer/t @@ -28,9 +28,9 @@ verified_peer() { } # backup config -install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~" +install -m0600 -- "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~" with_remote_config() { - install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config" + install -m0600 -- "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config" cat >>"$XDG_CONFIG_HOME/interimap/config" } @@ -53,12 +53,12 @@ capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX) step_start "SSL_CAfile" if [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then - # our self-signed test cert should not be in there + # our fake root CA should not be in there with_remote_config <<<"SSL_CAfile = /etc/ssl/certs/ca-certificates.crt" unverified_peer fi -doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_cert >"$capath/ca-certificates.crt" +cp -T -- ~/.dovecot/conf.d/ca.crt "$capath/ca-certificates.crt" with_remote_config <<<"SSL_CAfile = $capath/ca-certificates.crt" verified_peer step_done @@ -66,7 +66,7 @@ step_done step_start "SSL_CApath" if [ -d "/etc/ssl/certs" ]; then - # our self-signed test cert should not be in there + # our fake root CA should not be in there with_remote_config <<<"SSL_CApath = /etc/ssl/certs" unverified_peer fi -- cgit v1.2.3 From 84d1829fd0f955cf9fb7add54f60fc314b0d42b1 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 15:26:46 +0100 Subject: libinterimap: factor out hostname/IP parsing. Also, document that enclosing 'host' value in square brackets forces its interpretation as an IP literal (hence skips name resolution). --- Changelog | 3 +++ doc/interimap.1.md | 4 +++- doc/pullimap.1.md | 4 +++- lib/Net/IMAP/InterIMAP.pm | 61 ++++++++++++++++++++++++++++++++--------------- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Changelog b/Changelog index 7a04963..71e11f7 100644 --- a/Changelog +++ b/Changelog @@ -4,6 +4,9 @@ interimap (0.5.4) upstream; + test suite: always generate new certificates on `make test`. Hence running `make test` now requires OpenSSL 1.1.1 or later. + test suite: sign all test certificates with the same root CA. + + libinterimap: factor out hostname/IP parsing. + + document that enclosing 'host' value in square brackets forces its + interpretation as an IP literal (hence skips name resolution). - documentation: replace example.org with example.net for consistency. -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 2c9ddc6..ab35275 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -317,7 +317,9 @@ Valid options are: *host* -: Server hostname, for `type=imap` and `type=imaps`. +: Server hostname or IP address, for `type=imap` and `type=imaps`. + The value can optionally be enclosed in square brackets to force its + interpretation as an IP literal (hence skip name resolution). (Default: `localhost`.) *port* diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index 42ea282..57790a6 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -139,7 +139,9 @@ Valid options are: *host* -: Server hostname, for `type=imap` and `type=imaps`. +: Server hostname or IP address, for `type=imap` and `type=imaps`. + The value can optionally be enclosed in square brackets to force its + interpretation as an IP literal (hence skip name resolution). (Default: `localhost`.) *port* diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 849dc0f..e3a539d 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1446,23 +1446,39 @@ my $RE_IPv6 = do { | (?: (?: $h16 : ){0,6} $h16 )? :: /x }; +# Parse an IPv4 or IPv6. In list context, return a pair (IP, family), +# otherwise only the IP. If the argument is not an IP (for instance if +# it's a hostname), then return (undef, undef) resp. undef. The input +# can optionaly be enclosed in square brackets which forces its +# interpretation as an IP literal: an error is raised if it is not the +# case. +my $RE_IPv4_anchored = qr/\A($RE_IPv4)\z/; +my $RE_IPv6_anchored = qr/\A($RE_IPv6)\z/; +sub _parse_hostip($) { + my $v = shift // return; + my $literal = $v =~ s/\A\[(.*)\]\z/$1/ ? 1 : 0; + my ($ip, $af) = $v =~ $RE_IPv4_anchored ? ($1, AF_INET) + : $v =~ $RE_IPv6_anchored ? ($1, AF_INET6) + : (undef, undef); + die "Invalid IP literal: $v\n" if $literal and !defined($ip); + return wantarray ? ($ip, $af) : $ip; +} # Opens a TCP socket to the given $host and $port. sub _tcp_connect($$$) { my ($self, $host, $port) = @_; my %hints = (socktype => SOCK_STREAM, protocol => IPPROTO_TCP); - if ($host =~ qr/\A$RE_IPv4\z/) { - $hints{family} = AF_INET; - $hints{flags} |= AI_NUMERICHOST; - } elsif ($host =~ qr/\A\[($RE_IPv6)\]\z/) { - $host = $1; - $hints{family} = AF_INET6; + my ($host2, $family) = _parse_hostip($host); + if (defined $family) { + $hints{family} = $family; $hints{flags} |= AI_NUMERICHOST; + } else { + $host2 = $host; } - my ($err, @res) = getaddrinfo($host, $port, \%hints); - $self->fail("Can't getaddrinfo: $err") if $err ne ''; + my ($err, @res) = getaddrinfo($host2, $port, \%hints); + $self->fail("getaddrinfo($host2): $err") if $err ne ''; SOCKETS: foreach my $ai (@res) { @@ -1520,7 +1536,7 @@ sub _proxify($$$$) { $port = getservbyname($port, 'tcp') // $self->fail("Can't getservbyname $port") unless $port =~ /\A[0-9]+\z/; - $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?($RE_IPv4|\[$RE_IPv6\]|[^:]+)(:[A-Za-z0-9]+)?\z/ + $proxy =~ /\A([A-Za-z0-9]+):\/\/(\P{Control}*\@)?([^:]+|\[[^\]]+\])(:[A-Za-z0-9]+)?\z/ or $self->fail("Invalid proxy URI $proxy"); my ($proto, $userpass, $proxyhost, $proxyport) = ($1, $2, $3, $4); $userpass =~ s/\@\z// if defined $userpass; @@ -1553,23 +1569,30 @@ sub _proxify($$$$) { $self->fail('SOCKSv5', 'No acceptable authentication methods'); } - if ($host !~ /\A(?:$RE_IPv4|\[$RE_IPv6\])\z/ and !$resolv) { + my ($hostip, $fam) = _parse_hostip($host); + unless (defined($fam) or $resolv) { # resove the hostname $host locally my ($err, @res) = getaddrinfo($host, undef, {socktype => SOCK_RAW}); - $self->fail("Can't getaddrinfo: $err") if $err ne ''; - ($host) = first { defined $_ } map { + $self->fail("getaddrinfo($host): $err") if $err ne ''; + my ($addr) = first { defined($_) } map { my ($err, $ipaddr) = getnameinfo($_->{addr}, NI_NUMERICHOST, NIx_NOSERV); - $err eq '' ? $ipaddr : undef + $err eq '' ? [$ipaddr,$_->{family}] : undef } @res; - $self->fail("Can't getnameinfo") unless defined $host; + $self->fail("Can't getnameinfo") unless defined $addr; + ($hostip, $fam) = @$addr; } # send a CONNECT command (CMD 0x01) - my ($typ, $addr) = - $host =~ /\A$RE_IPv4\z/ ? (0x01, Socket::inet_pton(AF_INET, $host)) - : ($host =~ /\A\[($RE_IPv6)\]\z/ or $host =~ /\A($RE_IPv6)\z/) ? (0x04, Socket::inet_pton(AF_INET6, $1)) - : (0x03, pack('C',length($host)).$host); - $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ).$addr.pack('n', $port)); + my ($typ, $addr); + if (defined $fam) { + $typ = $fam == AF_INET ? 0x01 : $fam == AF_INET6 ? 0x04 : $self->panic(); + $addr = Socket::inet_pton($fam, $hostip); + } else { + # let the SOCKS server do the resolution + $typ = 0x03; + $addr = pack('C',length($host)) . $host; + } + $self->_xwrite($socket, pack('C4', $v, 0x01, 0x00, $typ) . $addr . pack('n', $port)); ($v2, my $r, my $rsv, $typ) = unpack('C4', $self->_xread($socket, 4)); $self->fail('SOCKSv5', 'Invalid protocol') unless $v == $v2 and $rsv == 0x00; -- cgit v1.2.3 From 265f133600e9812726a52ea3067409ed3578e882 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 19:39:10 +0100 Subject: libinterimap: make SSL_verify check the hostname as well. More precisely, ensure that the certificate Subject Alternative Name (SAN) or Subject CommonName (CN) matches the hostname or IP literal specified by the 'host' option. Previously it was only verifying the chain of trust. This bumps the minimum Net::SSLeay version to 1.83 and OpenSSL version 1.0.2. --- Changelog | 5 +++ doc/build.md | 2 +- doc/interimap.1.md | 14 +++++--- doc/pullimap.1.md | 14 +++++--- lib/Net/IMAP/InterIMAP.pm | 32 +++++++++++++++--- tests/certs/generate | 7 ++-- tests/run | 2 +- tests/tls-verify-peer/interimap.remote | 1 - tests/tls-verify-peer/t | 61 ++++++++++++++++++++++++++++++---- 9 files changed, 111 insertions(+), 27 deletions(-) diff --git a/Changelog b/Changelog index 71e11f7..d227efb 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,10 @@ interimap (0.5.4) upstream; + * libinterimap: make SSL_verify also checks that the certificate + Subject Alternative Name (SAN) or Subject CommonName (CN) matches the + hostname or IP literal specified by the 'host' option. Previously it + was only checking the chain of trust. This bumps the minimum + Net::SSLeay version to 1.83 and OpenSSL version 1.0.2. + libinterimap: show the matching pinned SPKI in --debug mode. + test suite: always generate new certificates on `make test`. Hence running `make test` now requires OpenSSL 1.1.1 or later. diff --git a/doc/build.md b/doc/build.md index 4a4f80d..47d1a89 100644 --- a/doc/build.md +++ b/doc/build.md @@ -24,7 +24,7 @@ following Perl modules: * [`Getopt::Long`](https://perldoc.perl.org/Getopt/Long.html) (*core module*) * [`MIME::Base64`](https://perldoc.perl.org/MIME/Base64.html) (*core module*) — if authentication is required * [`List::Util`](https://perldoc.perl.org/List/Util.html) (*core module*) - * [`Net::SSLeay`](https://metacpan.org/pod/Net::SSLeay) ≥1.73 + * [`Net::SSLeay`](https://metacpan.org/pod/Net::SSLeay) ≥1.83 * [`POSIX`](https://perldoc.perl.org/POSIX.html) (*core module*) * [`Socket`](https://perldoc.perl.org/Socket.html) (*core module*) * [`Time::HiRes`](https://perldoc.perl.org/Time/HiRes.html) (*core module*) — if `logfile` is set diff --git a/doc/interimap.1.md b/doc/interimap.1.md index ab35275..d21424b 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -420,15 +420,19 @@ Valid options are: *SSL_verify* -: Whether to verify the server certificate chain. +: Whether to verify the server certificate chain, and match its + Subject Alternative Name (SAN) or Subject CommonName (CN) against + the value of the *host* option. + (Default: `YES`.) + Note that using *SSL_fingerprint* to specify the fingerprint of the server certificate provides an independent server authentication - measure as it ignores the CA chain. - (Default: `YES`.) + measure as it pins directly its key material and ignore its chain of + trust. *SSL_CApath* -: Directory to use for server certificate verification if +: Directory to use for server certificate verification when `SSL_verify=YES`. This directory must be in “hash format”, see [`verify`(1ssl)] for more information. @@ -436,7 +440,7 @@ Valid options are: *SSL_CAfile* : File containing trusted certificates to use during server - certificate verification if `SSL_verify=YES`. + certificate verification when `SSL_verify=YES`. Supported extensions {#supported-extensions} ==================== diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index 57790a6..bcf5ade 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -239,15 +239,19 @@ Valid options are: *SSL_verify* -: Whether to verify the server certificate chain. +: Whether to verify the server certificate chain, and match its + Subject Alternative Name (SAN) or Subject CommonName (CN) against + the value of the *host* option. + (Default: `YES`.) + Note that using *SSL_fingerprint* to specify the fingerprint of the server certificate provides an independent server authentication - measure as it ignores the CA chain. - (Default: `YES`.) + measure as it pins directly its key material and ignore its chain of + trust. *SSL_CApath* -: Directory to use for server certificate verification if +: Directory to use for server certificate verification when `SSL_verify=YES`. This directory must be in “hash format”, see [`verify`(1ssl)] for more information. @@ -255,7 +259,7 @@ Valid options are: *SSL_CAfile* : File containing trusted certificates to use during server - certificate verification if `SSL_verify=YES`. + certificate verification when `SSL_verify=YES`. Control flow {#control-flow} ============ diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index e3a539d..a0efcc1 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -24,7 +24,7 @@ use strict; use Compress::Raw::Zlib qw/Z_OK Z_STREAM_END Z_FULL_FLUSH Z_SYNC_FLUSH MAX_WBITS/; use Config::Tiny (); use Errno qw/EEXIST EINTR/; -use Net::SSLeay 1.73 (); +use Net::SSLeay 1.83 (); use List::Util qw/all first/; use POSIX ':signal_h'; use Socket qw/SOCK_STREAM SOCK_RAW SOCK_CLOEXEC IPPROTO_TCP SHUT_RDWR @@ -1691,6 +1691,7 @@ BEGIN { # Upgrade the $socket to SSL/TLS. sub _start_ssl($$) { my ($self, $socket) = @_; + my $openssl_version = Net::SSLeay::OPENSSL_VERSION_NUMBER(); my $ctx = Net::SSLeay::CTX_new() or $self->panic("Failed to create SSL_CTX $!"); my $ssl_options = Net::SSLeay::OP_SINGLE_DH_USE() | Net::SSLeay::OP_SINGLE_ECDH_USE(); @@ -1733,25 +1734,46 @@ sub _start_ssl($$) { or $self->_ssl_error("Can't set cipher list"); } + my $vpm = Net::SSLeay::X509_VERIFY_PARAM_new() or $self->_ssl_error("X509_VERIFY_PARAM_new()"); + my $purpose = Net::SSLeay::X509_PURPOSE_SSL_SERVER(); + $self->_ssl_error("X509_VERIFY_PARAM_set_purpose()") unless Net::SSLeay::X509_VERIFY_PARAM_set_purpose($vpm, $purpose) == 1; + $self->_ssl_error("CTX_set_purpose()") unless Net::SSLeay::CTX_set_purpose($ctx, $purpose) == 1; + if ($self->{SSL_verify} // 1) { - # verify the certificate chain + # for X509_VERIFY_PARAM_set1_{ip,host}() + $self->panic("Failed requirement libssl >=1.0.2") if $openssl_version < 0x1000200f; + + # verify certificate chain my ($file, $path) = ($self->{SSL_CAfile} // '', $self->{SSL_CApath} // ''); if ($file ne '' or $path ne '') { Net::SSLeay::CTX_load_verify_locations($ctx, $file, $path) or $self->_ssl_error("Can't load verify locations"); } + + # verify DNS hostname or IP literal + my $host = $self->{host} // $self->panic(); + my ($hostip, $hostipfam) = _parse_hostip($host); + if (defined $hostipfam) { + my $addr = Socket::inet_pton($hostipfam, $hostip) // $self->panic(); + $self->_ssl_error("X509_VERIFY_PARAM_set1_ip()") + unless Net::SSLeay::X509_VERIFY_PARAM_set1_ip($vpm, $addr) == 1; + } else { + $self->_ssl_error("X509_VERIFY_PARAM_set1_host()") + unless Net::SSLeay::X509_VERIFY_PARAM_set1_host($vpm, $host) == 1; + } } else { Net::SSLeay::CTX_set_verify_depth($ctx, 0); } - Net::SSLeay::CTX_set_purpose($ctx, Net::SSLeay::X509_PURPOSE_SSL_SERVER()) - or $self->_ssl_error("Can't set purpose"); Net::SSLeay::CTX_set_verify($ctx, Net::SSLeay::VERIFY_PEER(), sub($$) {$self->_ssl_verify(@_)}); + $self->_ssl_error("CTX_SSL_set1_param()") unless Net::SSLeay::CTX_set1_param($ctx, $vpm) == 1; my $ssl = Net::SSLeay::new($ctx) or $self->fail("Can't create new SSL structure"); Net::SSLeay::set_fd($ssl, fileno $socket) or $self->fail("SSL filehandle association failed"); $self->_ssl_error("Can't initiate TLS/SSL handshake") unless Net::SSLeay::connect($ssl) == 1; - $self->panic("Couldn't verify") unless $self->{_SSL_PEER_VERIFIED}; # sanity check + $self->panic() unless $self->{_SSL_PEER_VERIFIED}; # sanity check + $self->panic() if ($self->{SSL_verify} // 1) and Net::SSLeay::get_verify_result($ssl) != Net::SSLeay::X509_V_OK(); + Net::SSLeay::X509_VERIFY_PARAM_free($vpm); if ($self->{debug}) { my $v = Net::SSLeay::version($ssl); diff --git a/tests/certs/generate b/tests/certs/generate index 19463d5..6457765 100755 --- a/tests/certs/generate +++ b/tests/certs/generate @@ -20,19 +20,22 @@ SERIAL=1 new() { local key="$1" cn="$2" openssl req -new -rand /dev/urandom -key "$key" \ - -subj "/OU=$OU/CN=$cn" \ + -subj "/OU=$OU/CN=$cn" ${3+-addext subjectAltName="$3"} \ -out "$cadir/new.csr" cat >"$cadir/new-ext.cnf" <<-EOF basicConstraints = critical, CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = critical, serverAuth EOF + if [ -n "${3+x}" ]; then + printf "subjectAltName = %s\\n" "$3" >>"$cadir/new-ext.cnf" + fi openssl x509 -req -in "$cadir/new.csr" -CA ./ca.crt -CAkey "$cadir/ca.key" \ -CAserial "$cadir/ca.srl" -CAcreateserial -extfile "$cadir/new-ext.cnf" } openssl genpkey -algorithm RSA -out ./dovecot.rsa.key -new ./dovecot.rsa.key "localhost" >./dovecot.rsa.crt +new ./dovecot.rsa.key "localhost" "DNS:localhost,DNS:ip6-localhost,IP:127.0.0.1,IP:::1" >./dovecot.rsa.crt openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out ./dovecot.ecdsa.key new ./dovecot.ecdsa.key "localhost" >./dovecot.ecdsa.crt diff --git a/tests/run b/tests/run index 0305812..d216591 100755 --- a/tests/run +++ b/tests/run @@ -93,7 +93,7 @@ prepare() { mail_location = dbox:~/inbox:LAYOUT=index mailbox_list_index = yes ssl = no - listen = 127.0.0.1, ::1 + listen = 127.0.0.1, 127.0.1.1, ::1 namespace inbox { inbox = yes } diff --git a/tests/tls-verify-peer/interimap.remote b/tests/tls-verify-peer/interimap.remote index b02fcd0..263655f 100644 --- a/tests/tls-verify-peer/interimap.remote +++ b/tests/tls-verify-peer/interimap.remote @@ -1,2 +1 @@ -host = ::1 port = 10993 diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t index 9b676a6..2461a1f 100644 --- a/tests/tls-verify-peer/t +++ b/tests/tls-verify-peer/t @@ -1,6 +1,15 @@ +X509_SHA256="$(doveconf -c "$HOME_remote/.dovecot/config" -hx ssl_cert \ + | openssl x509 -noout -fingerprint -sha256 \ + | sed -rn "/^.*=\\s*/ {s///p;q}" | tr -d : | tr "[A-Z]" "[a-z]")" +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}")" + unverified_peer() { ! interimap --debug || error + # make sure we aborted the handshake immediately after connecting + grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify" [ -s "$TMPDIR/preverify" ] || error @@ -11,12 +20,13 @@ unverified_peer() { } verified_peer() { local i u - for ((i = 0; i < 32; i++)); do + for ((i = 0; i < 4; i++)); do u="$(shuf -n1 -e "local" "remote")" sample_message | deliver -u "$u" done interimap --debug || error + grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify" [ -s "$TMPDIR/preverify" ] || error ! grep -Fvx "preverify=1" <"$TMPDIR/preverify" || error @@ -39,9 +49,6 @@ unverified_peer step_done step_start "peer verification result honored when pinned pubkey matches" -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}")" with_remote_config <<-EOF SSL_fingerprint = sha256\$$PKEY_SHA256 EOF @@ -50,31 +57,71 @@ grep -Fx "remote: Peer certificate matches pinned SPKI digest sha256\$$PKEY_SHA2 step_done capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX) +cp -T -- ~/.dovecot/conf.d/ca.crt "$capath/ca-certificates.crt" step_start "SSL_CAfile" if [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then - # our fake root CA should not be in there + # assume our fake root CA is not there with_remote_config <<<"SSL_CAfile = /etc/ssl/certs/ca-certificates.crt" unverified_peer fi -cp -T -- ~/.dovecot/conf.d/ca.crt "$capath/ca-certificates.crt" +# default host (localhost) is the CN (and also subjectAltName) with_remote_config <<<"SSL_CAfile = $capath/ca-certificates.crt" verified_peer + +# hostnames and IPs included in the subjectAltName should work as well +for host in "ip6-localhost" "127.0.0.1" "::1"; do + with_remote_config <<-EOF + host = $host + SSL_CAfile = $capath/ca-certificates.crt + EOF + verified_peer +done + +# but not for other IPs or hostnames +for host in "ip6-loopback" "127.0.1.1"; do + with_remote_config <<-EOF + host = $host + SSL_CAfile = $capath/ca-certificates.crt + EOF + unverified_peer +done + step_done step_start "SSL_CApath" if [ -d "/etc/ssl/certs" ]; then - # our fake root CA should not be in there + # assume our fake root CA is not there with_remote_config <<<"SSL_CApath = /etc/ssl/certs" unverified_peer fi c_rehash "$capath" +# default host (localhost) is the CN (and also subjectAltName) with_remote_config <<<"SSL_CApath = $capath" verified_peer + +# hostnames and IPs included in the subjectAltName should work as well +for host in "ip6-localhost" "127.0.0.1" "::1"; do + with_remote_config <<-EOF + host = $host + SSL_CApath = $capath + EOF + verified_peer +done + +# but not for other IPs or hostnames +for host in "ip6-loopback" "127.0.1.1"; do + with_remote_config <<-EOF + host = $host + SSL_CApath = $capath + EOF + unverified_peer +done + step_done # vim: set filetype=sh : -- cgit v1.2.3 From 09376bac4fe99c542223ba0ae23ad6067410b1fa Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 20:44:40 +0100 Subject: typofix --- doc/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.md b/doc/build.md index 47d1a89..d922e43 100644 --- a/doc/build.md +++ b/doc/build.md @@ -1,7 +1,7 @@ % Build instructions % [Guilhem Moulin](mailto:guilhem@fripost.org) -On Debian 9 (codename *Stretch*) and later, installing [`interimap`(1)] +On Debian 10 (codename *Buster*) and later, installing [`interimap`(1)] is a single command away: $ sudo apt install interimap -- cgit v1.2.3 From 4ed6f0982cc0553e31e7beadf441beb8573a07d4 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 21:52:44 +0100 Subject: libinterimap: add support for the TLS SNI (Server Name Indication) extension. This is controlled by the new 'SSL_hostname' option. The default value of that option is the value of the 'host' option when it is hostname, and the empty string (which disables SNI) when it is an IP literal. --- Changelog | 5 ++++ doc/interimap.1.md | 7 +++++ doc/pullimap.1.md | 7 +++++ lib/Net/IMAP/InterIMAP.pm | 15 ++++++++-- tests/certs/generate | 3 ++ tests/list | 3 +- tests/tls-sni/interimap.remote | 3 ++ tests/tls-sni/remote.conf | 7 +++++ tests/tls-sni/t | 66 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 tests/tls-sni/interimap.remote create mode 100644 tests/tls-sni/remote.conf create mode 100644 tests/tls-sni/t diff --git a/Changelog b/Changelog index d227efb..87ce9fd 100644 --- a/Changelog +++ b/Changelog @@ -5,6 +5,11 @@ interimap (0.5.4) upstream; hostname or IP literal specified by the 'host' option. Previously it was only checking the chain of trust. This bumps the minimum Net::SSLeay version to 1.83 and OpenSSL version 1.0.2. + * libinterimap: add support for the TLS SNI (Server Name Indication) + extension, controlled by the new 'SSL_hostname' option. The default + value of that option is the value of the 'host' option when it is + hostname, and the empty string (which disables SNI) when it is an IP + literal. + libinterimap: show the matching pinned SPKI in --debug mode. + test suite: always generate new certificates on `make test`. Hence running `make test` now requires OpenSSL 1.1.1 or later. diff --git a/doc/interimap.1.md b/doc/interimap.1.md index d21424b..54c3dcf 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -442,6 +442,13 @@ Valid options are: : File containing trusted certificates to use during server certificate verification when `SSL_verify=YES`. +*SSL_hostname* + +: Name to use for the TLS SNI (Server Name Indication) extension. The + default value is taken from the *host* option when it is a hostname, + and to the empty string when it is an IP literal. + Setting *SSL_hostname* to the empty string explicitly disables SNI. + Supported extensions {#supported-extensions} ==================== diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index bcf5ade..fb3a73b 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -261,6 +261,13 @@ Valid options are: : File containing trusted certificates to use during server certificate verification when `SSL_verify=YES`. +*SSL_hostname* + +: Name to use for the TLS SNI (Server Name Indication) extension. The + default value is taken from the *host* option when it is a hostname, + and to the empty string when it is an IP literal. + Setting *SSL_hostname* to the empty string explicitly disables SNI. + Control flow {#control-flow} ============ diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index a0efcc1..e3a5d31 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -65,6 +65,7 @@ my %OPTIONS = ( SSL_protocols => qr/\A(!?$RE_SSL_PROTO(?: !?$RE_SSL_PROTO)*)\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_hostname => qr/\A(\P{Control}*)\z/, SSL_verify => qr/\A(YES|NO)\z/i, SSL_CApath => qr/\A(\P{Control}+)\z/, SSL_CAfile => qr/\A(\P{Control}+)\z/, @@ -1739,6 +1740,8 @@ sub _start_ssl($$) { $self->_ssl_error("X509_VERIFY_PARAM_set_purpose()") unless Net::SSLeay::X509_VERIFY_PARAM_set_purpose($vpm, $purpose) == 1; $self->_ssl_error("CTX_set_purpose()") unless Net::SSLeay::CTX_set_purpose($ctx, $purpose) == 1; + my $host = $self->{host} // $self->panic(); + my ($hostip, $hostipfam) = _parse_hostip($host); if ($self->{SSL_verify} // 1) { # for X509_VERIFY_PARAM_set1_{ip,host}() $self->panic("Failed requirement libssl >=1.0.2") if $openssl_version < 0x1000200f; @@ -1751,8 +1754,6 @@ sub _start_ssl($$) { } # verify DNS hostname or IP literal - my $host = $self->{host} // $self->panic(); - my ($hostip, $hostipfam) = _parse_hostip($host); if (defined $hostipfam) { my $addr = Socket::inet_pton($hostipfam, $hostip) // $self->panic(); $self->_ssl_error("X509_VERIFY_PARAM_set1_ip()") @@ -1770,6 +1771,16 @@ sub _start_ssl($$) { my $ssl = Net::SSLeay::new($ctx) or $self->fail("Can't create new SSL structure"); Net::SSLeay::set_fd($ssl, fileno $socket) or $self->fail("SSL filehandle association failed"); + + # always use 'SSL_hostname' when set, otherwise use 'host' (unless it's an IP) on OpenSSL >=0.9.8f + my $servername = $self->{SSL_hostname} // (defined $hostipfam ? "" : $host); + if ($servername ne "") { + $self->panic("Failed requirement libssl >=0.9.8f") if $openssl_version < 0x00908070; + $self->_ssl_error("Can't set TLS servername extension (value $servername)") + unless Net::SSLeay::set_tlsext_host_name($ssl, $servername) == 1; + $self->log("Using SNI with name $servername") if $self->{debug}; + } + $self->_ssl_error("Can't initiate TLS/SSL handshake") unless Net::SSLeay::connect($ssl) == 1; $self->panic() unless $self->{_SSL_PEER_VERIFIED}; # sanity check $self->panic() if ($self->{SSL_verify} // 1) and Net::SSLeay::get_verify_result($ssl) != Net::SSLeay::X509_V_OK(); diff --git a/tests/certs/generate b/tests/certs/generate index 6457765..de379a0 100755 --- a/tests/certs/generate +++ b/tests/certs/generate @@ -39,3 +39,6 @@ new ./dovecot.rsa.key "localhost" "DNS:localhost,DNS:ip6-localhost,IP:127.0.0.1, openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out ./dovecot.ecdsa.key new ./dovecot.ecdsa.key "localhost" >./dovecot.ecdsa.crt + +openssl genpkey -algorithm RSA -out ./dovecot.rsa2.key +new ./dovecot.rsa2.key "imap.example.net" "DNS:imap.example.net,DNS:localhost" >./dovecot.rsa2.crt diff --git a/tests/list b/tests/list index 0ad86e4..cb31a73 100644 --- a/tests/list +++ b/tests/list @@ -51,7 +51,8 @@ split-set Split large sets to avoid extra-long command lines tls SSL/TLS handshake ... tls-verify-peer tls-pin-fingerprint pubkey fingerprint pinning - tls-rsa+ecdsa pubkey fingerprint pinning for hybrid RSA+ECDSA + tls-rsa+ecdsa pubkey fingerprint pinning for dual-cert RSA+ECDSA + tls-sni TLS servername extension (SNI) tls-protocols force TLS protocol versions . Live synchronization (60s) diff --git a/tests/tls-sni/interimap.remote b/tests/tls-sni/interimap.remote new file mode 100644 index 0000000..9f0d521 --- /dev/null +++ b/tests/tls-sni/interimap.remote @@ -0,0 +1,3 @@ +type = imaps +port = 10993 +SSL_verify = no diff --git a/tests/tls-sni/remote.conf b/tests/tls-sni/remote.conf new file mode 100644 index 0000000..4ccfb44 --- /dev/null +++ b/tests/tls-sni/remote.conf @@ -0,0 +1,7 @@ +!include conf.d/imapd.conf +!include conf.d/ssl.conf + +local_name imap.example.net { + ssl_cert = >"$XDG_CONFIG_HOME/interimap/config" +for ip in "127.0.0.1" "[::1]"; do + sed -i "s/^host\\s*=.*/host = $ip/" -- "$XDG_CONFIG_HOME/interimap/config" + interimap --debug || error + ! grep "^remote: Using SNI with name " <"$STDERR" || error "Using SNI with IP $ip" + grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_SHA256" <"$STDERR" || error +done + +# verify that SNI actually works (ie we're served the right cert) +sni_ok() { + grep -Fx "remote: Using SNI with name $SERVERNAME" <"$STDERR" || error + grep -Fx "remote: Peer certificate fingerprint: sha256\$$X509_2_SHA256" <"$STDERR" || error +} +echo "SSL_hostname = $SERVERNAME" >>"$XDG_CONFIG_HOME/interimap/config" +interimap --debug || error +sni_ok + + +## make sure SSL_hostname doesn't affect certificate verification ## + +# bad CA, bad host +sed -i "s/^host\\s*=.*/host = 127.0.0.1/" -- "$XDG_CONFIG_HOME/interimap/config" +sed -i "s/^SSL_verify\\s*=.*/SSL_verify = YES/" -- "$XDG_CONFIG_HOME/interimap/config" +! interimap --debug || error +sni_ok +grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error + +# good CA, bad host +echo "SSL_CAfile = $HOME/.dovecot/conf.d/ca.crt" >>"$XDG_CONFIG_HOME/interimap/config" +! interimap --debug || error +sni_ok +grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error + +# bad CA, good host +sed -i "/^SSL_CAfile\\s*=/d" -- "$XDG_CONFIG_HOME/interimap/config" +sed -i "s/^host\\s*=.*/host = localhost/" -- "$XDG_CONFIG_HOME/interimap/config" +! interimap --debug || error +sni_ok +grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error + +# good CA, good host +echo "SSL_CAfile = $HOME/.dovecot/conf.d/ca.crt" >>"$XDG_CONFIG_HOME/interimap/config" +interimap --debug || error +sni_ok + +# vim: set filetype=sh : -- cgit v1.2.3 From 528bf74289c01bcd2b8a8e7e9a99eba41039b09b Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 22:27:59 +0100 Subject: Makefile: new 'release' target. Also, change the tag format from upstream/$VERSION to v$VERSION. --- Changelog | 2 ++ Makefile | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index 87ce9fd..36b0e73 100644 --- a/Changelog +++ b/Changelog @@ -17,6 +17,8 @@ interimap (0.5.4) upstream; + libinterimap: factor out hostname/IP parsing. + document that enclosing 'host' value in square brackets forces its interpretation as an IP literal (hence skips name resolution). + + Makefile: new 'release' target; also, change the tag format from + upstream/$VERSION to v$VERSION. - documentation: replace example.org with example.net for consistency. -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/Makefile b/Makefile index b85cddd..ff62bf1 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,19 @@ test: ./tests/certs/generate ./tests/run-all +release: + @if ! git diff HEAD --quiet -- ./interimap ./pullimap ./Changelog; then \ + echo "Dirty state, refusing to release!" >&2; \ + exit 1; \ + fi + sed -ri "0,/^( -- .*) .*/ s//\1 $(shell date -R)/" ./Changelog + VERS=$$(dpkg-parsechangelog -l Changelog -SVersion 2>/dev/null) && \ + sed -ri "0,/^(our \\\$$VERSION\\s*=\s*)'[0-9.]+'\\s*;/ s//\1'$$VERS';/" \ + -- ./interimap ./pullimap && \ + git commit -m "Prepare new release v$$VERS." \ + -- ./interimap ./pullimap ./Changelog && \ + git tag -sm "Release version $$VERS" "v$$VERS" + ## make html CSS="https://guilhem.org/static/css/bootstrap.min.css" BUILD_DOCDIR="$XDG_RUNTIME_DIR/Downloads" $(HTML_FILES): $(BUILD_DOCDIR)/%.html: ./doc/%.md $(HTML_TEMPLATE) mtime="$$(git --no-pager log -1 --pretty="format:%ct" -- "$<" 2>/dev/null)"; \ @@ -59,4 +72,4 @@ uninstall: clean: rm -vf -- $(MANUAL_FILES) $(HTML_FILES) -.PHONY: all manual html doc test install uninstall clean +.PHONY: all manual html doc test release install uninstall clean -- cgit v1.2.3 From dc7282c9b9ee45fbc457f1d17cf8368fb9d36926 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 22:50:00 +0100 Subject: typofix --- lib/Net/IMAP/InterIMAP.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index e3a5d31..fff1570 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1617,7 +1617,7 @@ sub _proxify($$$$) { return $socket; } else { - $self->error("Unsupported proxy protocol $proto"); + $self->fail("Unsupported proxy protocol $proto"); } } -- cgit v1.2.3 From eb60cecd3c813372ed618751fe5c77229d26df76 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 22:41:56 +0100 Subject: documentation: improve wording. --- doc/interimap.1.md | 4 ++-- doc/pullimap.1.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 54c3dcf..2d2a637 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -329,8 +329,8 @@ Valid options are: *proxy* -: An optional SOCKS proxy to use for TCP connections to the IMAP - server (`type=imap` and `type=imaps` only), formatted as +: Optional SOCKS proxy to use for TCP connections to the IMAP server + (`type=imap` and `type=imaps` only), formatted as `PROTOCOL://[USER:PASSWORD@]PROXYHOST[:PROXYPORT]`. If `PROXYPORT` is omitted, it is assumed at port 1080. Only [SOCKSv5][RFC 1928] is supported (with optional diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index fb3a73b..c9500e0 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -151,8 +151,8 @@ Valid options are: *proxy* -: An optional SOCKS proxy to use for TCP connections to the IMAP - server (`type=imap` and `type=imaps` only), formatted as +: Optional SOCKS proxy to use for TCP connections to the IMAP server + (`type=imap` and `type=imaps` only), formatted as `PROTOCOL://[USER:PASSWORD@]PROXYHOST[:PROXYPORT]`. If `PROXYPORT` is omitted, it is assumed at port 1080. Only [SOCKSv5][RFC 1928] is supported (with optional -- cgit v1.2.3 From 46fe928647ad8d38ced79a36d38cd152055ed005 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 10 Dec 2020 23:43:16 +0100 Subject: rename 'debian' branch to 'debian/latest' for DEP-14 compliance. --- Changelog | 1 + doc/build.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index 36b0e73..8cac65d 100644 --- a/Changelog +++ b/Changelog @@ -20,6 +20,7 @@ interimap (0.5.4) upstream; + Makefile: new 'release' target; also, change the tag format from upstream/$VERSION to v$VERSION. - documentation: replace example.org with example.net for consistency. + - rename 'debian' branch to 'debian/latest' for DEP-14 compliance. -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 diff --git a/doc/build.md b/doc/build.md index d922e43..b9291f7 100644 --- a/doc/build.md +++ b/doc/build.md @@ -84,12 +84,12 @@ Debian GNU/Linux users can also use [`gbp`(1)] from [`git-buildpackage`](https://tracker.debian.org/pkg/git-buildpackage) in order to build their own packages: - $ git checkout debian + $ git checkout debian/latest $ gbp buildpackage Alternatively, for the development version: - $ git checkout debian + $ git checkout debian/latest $ git merge master $ gbp buildpackage --git-force-create --git-upstream-tree=BRANCH -- cgit v1.2.3 From 8130c75c18408ba8e2e635894d87878078e3f1f8 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 11 Dec 2020 11:21:11 +0100 Subject: Prepare new release v0.5.4. --- Changelog | 2 +- interimap | 2 +- pullimap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index 8cac65d..2bd9fbe 100644 --- a/Changelog +++ b/Changelog @@ -22,7 +22,7 @@ interimap (0.5.4) upstream; - documentation: replace example.org with example.net for consistency. - rename 'debian' branch to 'debian/latest' for DEP-14 compliance. - -- Guilhem Moulin Thu, 10 Dec 2020 14:22:05 +0100 + -- Guilhem Moulin Fri, 11 Dec 2020 11:21:11 +0100 interimap (0.5.3) upstream; diff --git a/interimap b/interimap index 9dd197b..f8aa768 100755 --- a/interimap +++ b/interimap @@ -22,7 +22,7 @@ use v5.14.2; use strict; use warnings; -our $VERSION = '0.5.3'; +our $VERSION = '0.5.4'; my $NAME = 'interimap'; my $DATABASE_VERSION = 1; use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat diff --git a/pullimap b/pullimap index baee352..86d80e0 100755 --- a/pullimap +++ b/pullimap @@ -22,7 +22,7 @@ use v5.20.2; use strict; use warnings; -our $VERSION = '0.5.3'; +our $VERSION = '0.5.4'; my $NAME = 'pullimap'; use Errno 'EINTR'; -- cgit v1.2.3 From a51f2efacebbf941585809853d1adbfddc165ac2 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 11 Dec 2020 11:21:17 +0100 Subject: Prepare new release v0.5.4. --- Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog b/Changelog index 2bd9fbe..28a1ef4 100644 --- a/Changelog +++ b/Changelog @@ -22,7 +22,7 @@ interimap (0.5.4) upstream; - documentation: replace example.org with example.net for consistency. - rename 'debian' branch to 'debian/latest' for DEP-14 compliance. - -- Guilhem Moulin Fri, 11 Dec 2020 11:21:11 +0100 + -- Guilhem Moulin Fri, 11 Dec 2020 11:21:17 +0100 interimap (0.5.3) upstream; -- cgit v1.2.3