From 71718f3b0501aa7e3b60b155e3fa7340e9d199d0 Mon Sep 17 00:00:00 2001 From: Aaron Larisch Date: Tue, 24 May 2022 20:52:20 +0200 Subject: [PATCH] Add ECDSA support This is a squashed commit of the following: commit db8051bc234c5fa67aa87a7a94f9e89eaf0e7dac Merge: 2634fdf0 04020685 Author: Aaron Larisch Date: Tue May 24 20:44:38 2022 +0200 Merge branch 'master' into add-ecdsa-support commit 2634fdf0e942c50220d6ea22319610327bd4127e Merge: 0962b90d 116c7919 Author: Aaron Larisch Date: Wed Nov 10 08:53:21 2021 +0100 Merge branch 'master' into add-ecdsa-support commit 0962b90db51586b30a20d8c5310c1b09dbfe96e3 Merge: 61522713 d90d4f96 Author: Aaron Larisch Date: Wed Jun 9 11:33:34 2021 +0200 Merge branch 'master' into add-ecdsa-support commit 61522713f1916ac8062fd7b717984d142a395c26 Merge: 72261e60 64264767 Author: Aaron Larisch Date: Fri Feb 26 18:20:30 2021 +0100 Merge branch 'master' into add-ecdsa-support commit 72261e60f0c147287fb312c51982030f69674e6a Merge: 3a1cce21 0846013e Author: Aaron Larisch Date: Wed Dec 16 21:33:55 2020 +0100 Merge branch 'master' into add-ecdsa-support commit 3a1cce2105b377410a4c4aff00d44db7fea76e85 Merge: 6a01796e c1034b89 Author: Aaron Larisch Date: Tue Sep 15 10:51:32 2020 +0200 Merge branch 'master' into add-ecdsa-support commit 6a01796e7ecbae78b89a2067080886bb59a394a1 Merge: 812adb0f 9685b4b5 Author: Aaron Larisch Date: Tue Jul 21 14:07:56 2020 +0200 Merge branch 'master' into add-ecdsa-support commit 812adb0fe1bdb5d5d832bcaf09c2c8543c71914b Author: Aaron Larisch Date: Sun Jul 5 00:12:21 2020 +0200 Fix TLSA records for ECDSA and RSA certs in DNS diagnostics Disables TLS 1.3 for the test connection to limit to RSA or ECDSA ciphers. commit 83c976999bf1da178ed9363f79ff772f9bffab93 Merge: 6fb29ab5 becc5059 Author: Aaron Larisch Date: Sat Jul 4 23:03:45 2020 +0200 Merge branch 'master' into add-ecdsa-support commit 6fb29ab554e55bac17fa3087da4580d224e95e6f Merge: 3131e171 b933a309 Author: Aaron Larisch Date: Thu May 21 11:34:30 2020 +0200 Merge branch 'master' into add-ecdsa-support commit 3131e1717f48488a81979206df6d67e6f55c10a7 Merge: 1929216e 7fa10cc3 Author: Aaron Larisch Date: Thu Apr 16 18:20:39 2020 +0200 Merge branch 'master' into add-ecdsa-support commit 1929216e6a8eff0a110bfc28425987c548e12fa0 Merge: a0edf841 a9947e99 Author: Aaron Larisch Date: Sat Mar 21 08:38:07 2020 +0100 Merge branch 'master' into add-ecdsa-support commit a0edf841427b093b03597ba231e5c6b228c0ee60 Merge: 6152b426 b5c844d7 Author: Aaron Larisch Date: Thu Feb 13 13:52:09 2020 +0100 Merge branch 'master' into add-ecdsa-support commit 6152b42616cfa9df483dee6047fdca2517b1b245 Merge: 85b791bf e6bb3069 Author: Aaron Larisch Date: Wed Jan 15 10:14:18 2020 +0100 Merge branch 'master' into add-ecdsa-support commit 85b791bf07391217a1c00d957b6eb8eff091a7b0 Merge: c2339931 ff74b8aa Author: Aaron Larisch Date: Tue Dec 17 15:21:10 2019 +0100 Merge branch 'master' into add-ecdsa-support commit c2339931c5c8ec646e1ae8ebc148b75f06c904dc Merge: eee0238b 4e8b2bfc Author: Aaron Larisch Date: Sat Dec 7 17:17:05 2019 +0100 Merge branch 'master' into add-ecdsa-support commit eee0238b6b9c97bcfae2b4183ade3575d0b97563 Merge: 9e1ff332 1d1a9a27 Author: Aaron Larisch Date: Fri Nov 8 12:53:51 2019 +0100 Merge branch 'master' into add-ecdsa-support commit 9e1ff332d163c9c2a9140bbe7388021a930dc759 Merge: ab164253 ea4da60c Author: Aaron Larisch Date: Fri Nov 1 16:06:41 2019 +0100 Merge branch 'master' into add-ecdsa-support commit ab164253306e6bf696ef1cbf2235f2de3f05e9f5 Merge: c0499263 573e62f1 Author: Aaron Larisch Date: Thu Oct 31 13:57:01 2019 +0100 Merge branch 'master' into add-ecdsa-support commit c0499263455d49a807ff4d79b9d439dfd8e66137 Merge: 6a6d6c4a c4316150 Author: Aaron Larisch Date: Thu Oct 31 13:55:28 2019 +0100 Merge pull request #1 from mhofer117/tls-sni-ecdsa Change line endings in functions.inc.php back to CRLF commit c43161507bcb48750b865a657e6fe80c02812c83 Author: Marcel Hofer Date: Sun Oct 20 19:02:17 2019 +0200 remove empty docker-entrypoint.sh commit efd6cd1847f718900be72d20fa6361fda975bf1c Merge: a2a0821a 6a6d6c4a Author: Marcel Hofer Date: Sun Oct 20 19:00:07 2019 +0200 Merge remote-tracking branch 'OpenLarry/add-ecdsa-support' into tls-sni-ecdsa # Conflicts: # data/Dockerfiles/acme/docker-entrypoint.sh # data/conf/dovecot/dovecot.conf # data/conf/nginx/site.conf # data/conf/postfix/main.cf # data/web/inc/ajax/dns_diagnostics.php # data/web/inc/functions.inc.php # docker-compose.yml # generate_config.sh # update.sh commit a2a0821a38a55cb99ba1dd32383344ed6504f451 Merge: 4a62809d 05e7c958 Author: Marcel Hofer Date: Sun Oct 20 18:50:16 2019 +0200 Merge branch 'tls-sni' into tls-sni-ecdsa commit 4a62809d33d1ef88c09f89cf1168a1f01f578e6f Author: Marcel Hofer Date: Sat Oct 19 13:04:02 2019 +0200 [SSL] add optional ecdsa certs in addition to rsa certs commit 6a6d6c4a604cb5d310308e1adf1b709febe2460d Merge: 351abd29 9f66b83a Author: Aaron Larisch Date: Tue Oct 1 11:01:28 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 351abd29b2f41f97a57681dccb042d9c2321498d Merge: eddb2693 04853794 Author: Aaron Larisch Date: Mon Sep 9 17:40:47 2019 +0200 Merge branch 'master' into add-ecdsa-support commit eddb26938d04dee6e2dd29ab65a3fefd1d07732d Merge: 9d13ead4 6e82a359 Author: Aaron Larisch Date: Thu Aug 29 11:36:03 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 9d13ead450239a1408937e85aeed36afdd48631e Merge: 478c4d1f f21cf134 Author: Aaron Larisch Date: Thu Aug 8 13:22:58 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 478c4d1f6364d60e58205656e8d0dd784ce0f088 Merge: 46dbf3bc 7665cc2a Author: Aaron Larisch Date: Thu Jun 27 14:49:09 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 46dbf3bc5e454b7bf74d1886f42e73b7d5b46d7b Merge: 680a2726 69fb7f7a Author: Aaron Larisch Date: Fri May 31 12:33:32 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 680a272664c451323dbc58f7dcbe17f96d387f1c Merge: f69559f0 b20ff13e Author: Aaron Larisch Date: Tue May 7 12:29:34 2019 +0200 Merge branch 'master' into add-ecdsa-support commit f69559f03bd25752c2c526c1ac1b475a02508377 Merge: 3e8a9583 cd881652 Author: Aaron Larisch Date: Mon Apr 15 12:55:41 2019 +0200 Merge branch 'master' into add-ecdsa-support commit 3e8a9583826d8e413e3f21c12fef0e116ad11b54 Merge: 956a487f 4aae7277 Author: Aaron Date: Mon Mar 18 16:52:37 2019 +0100 Merge branch 'master' into add-ecdsa-support commit 956a487f822580314186de7b0332e7f8ffaf289f Author: Aaron Larisch Date: Sun Mar 17 12:34:56 2019 +0100 Set SKIP_ECDSA_CERT to y by default commit 7103fe7e89b8ba8d45d59e65a6823c7c160565c4 Author: Aaron Larisch Date: Sun Mar 17 11:05:05 2019 +0100 Add SKIP_ECDSA_CERT config parameter commit 91fca4fa3835f146571d9e5546c37fceffec2a42 Author: Aaron Larisch Date: Sat Mar 16 18:48:28 2019 +0100 Show TLSA records for ECDSA certificates in DNS diagnostics commit cc521b02501f03f692686f3dedc43339b342aa96 Author: Aaron Larisch Date: Sat Mar 16 13:04:03 2019 +0100 Add ECDSA support Co-authored-by: Marcel Hofer --- data/Dockerfiles/acme/acme.sh | 106 ++++++++++++++++-- data/Dockerfiles/acme/obtain-certificate.sh | 5 +- data/Dockerfiles/dovecot/docker-entrypoint.sh | 7 ++ data/Dockerfiles/postfix/postfix.sh | 3 + data/assets/ssl-example/ecdsa-cert.pem | 1 + data/assets/ssl-example/ecdsa-key.pem | 1 + data/conf/dovecot/dovecot.conf | 2 + data/conf/nginx/templates/sites.template.sh | 8 ++ data/conf/postfix/main.cf | 2 + data/web/inc/ajax/dns_diagnostics.php | 38 ++++++- data/web/inc/functions.inc.php | 14 ++- docker-compose.yml | 1 + generate_config.sh | 4 + update.sh | 11 ++ 14 files changed, 181 insertions(+), 22 deletions(-) create mode 120000 data/assets/ssl-example/ecdsa-cert.pem create mode 120000 data/assets/ssl-example/ecdsa-key.pem diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh index 1cd456a4..d6ededd1 100755 --- a/data/Dockerfiles/acme/acme.sh +++ b/data/Dockerfiles/acme/acme.sh @@ -44,6 +44,17 @@ if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then exec $(readlink -f "$0") fi +if [[ "${SKIP_ECDSA_CERT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_ECDSA_CERT=y +fi + +ACME_BASE=/var/lib/acme +SSL_EXAMPLE=/var/lib/ssl-example + +# Symlink ECDSA to RSA certificate if not present +[[ ! -f ${ACME_BASE}/ecdsa-cert.pem ]] && [[ ! -L ${ACME_BASE}/ecdsa-cert.pem ]] && ln -s cert.pem ${ACME_BASE}/ecdsa-cert.pem +[[ ! -f ${ACME_BASE}/ecdsa-key.pem ]] && [[ ! -L ${ACME_BASE}/ecdsa-key.pem ]] && ln -s key.pem ${ACME_BASE}/ecdsa-key.pem + log_f "Waiting for Docker API..." until ping dockerapi -c1 > /dev/null; do sleep 1 @@ -62,9 +73,6 @@ until ping dovecot -c1 > /dev/null; do done log_f "Dovecot OK" -ACME_BASE=/var/lib/acme -SSL_EXAMPLE=/var/lib/ssl-example - mkdir -p ${ACME_BASE}/acme # Migrate @@ -92,6 +100,18 @@ if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]] && [[ $(stat ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* && ${ISSUER} != *"Fake LE Intermediate"* ]]; then log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..." + + # Make sure we do not combine Letsencrypt ECDSA with another RSA certificate + # Remove ECDSA if that is the case + if [[ -f ${ACME_BASE}/ecdsa-cert.pem ]] && [[ -f ${ACME_BASE}/ecdsa-key.pem ]] && [[ ! -L ${ACME_BASE}/ecdsa-cert.pem ]] && [[ ! -L ${ACME_BASE}/ecdsa-key.pem ]]; then + ISSUER=$(openssl x509 -in ${ACME_BASE}/ecdsa-cert.pem -noout -issuer) + if [[ ${ISSUER} == *"Let's Encrypt"* || ${ISSUER} == *"mailcow"* || ${ISSUER} == *"Fake LE Intermediate"* ]]; then + log_f "Remove Let's Encrypt ECDSA certificate in favour of a custom RSA one" + ln -sf cert.pem ${ACME_BASE}/ecdsa-cert.pem + ln -sf key.pem ${ACME_BASE}/ecdsa-key.pem + fi + fi + sleep 3650d exec $(readlink -f "$0") fi @@ -100,17 +120,27 @@ else log_f "Restoring previous acme certificate and restarting script..." cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ${ACME_BASE}/cert.pem cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem ${ACME_BASE}/key.pem + + if [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-cert.pem ]] && [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-key.pem ]] && verify_hash_match ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-cert.pem ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-key.pem; then + # Remove symlink before copying + cp --remove-destination ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-cert.pem ${ACME_BASE}/ecdsa-cert.pem + cp --remove-destination ${ACME_BASE}/${MAILCOW_HOSTNAME}/ecdsa-key.pem ${ACME_BASE}/ecdsa-key.pem + fi # Restarting with env var set to trigger a restart, exec env TRIGGER_RESTART=1 $(readlink -f "$0") else + ISSUER="mailcow" log_f "Restoring mailcow snake-oil certificates and restarting script..." cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem + ln -sf cert.pem ${ACME_BASE}/ecdsa-cert.pem + ln -sf key.pem ${ACME_BASE}/ecdsa-key.pem exec env TRIGGER_RESTART=1 $(readlink -f "$0") fi fi chmod 600 ${ACME_BASE}/key.pem +chmod 600 ${ACME_BASE}/ecdsa-key.pem log_f "Waiting for database..." while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do @@ -142,10 +172,14 @@ log_f "OK" no_date log_f "Initializing, please wait..." while true; do - POSTFIX_CERT_SERIAL="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" - DOVECOT_CERT_SERIAL="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" - POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" - DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_NEW_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" # Re-using previous acme-mailcow account and domain keys if [[ ! -f ${ACME_BASE}/acme/key.pem ]]; then log_f "Generating missing domain private rsa key..." @@ -153,6 +187,12 @@ while true; do else log_f "Using existing domain rsa key ${ACME_BASE}/acme/key.pem" fi + if [[ ! -f ${ACME_BASE}/acme/ecdsa-key.pem ]]; then + log_f "Generating missing domain private ecdsa key..." + openssl ecparam -genkey -name secp384r1 -noout > ${ACME_BASE}/acme/ecdsa-key.pem + else + log_f "Using existing domain ecdsa key ${ACME_BASE}/acme/ecdsa-key.pem" + fi if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then log_f "Generating missing Lets Encrypt account key..." if [[ ! -z ${ACME_CONTACT} ]]; then @@ -173,6 +213,7 @@ while true; do fi chmod 600 ${ACME_BASE}/acme/key.pem + chmod 600 ${ACME_BASE}/acme/ecdsa-key.pem chmod 600 ${ACME_BASE}/acme/account.pem unset EXISTING_CERTS @@ -309,6 +350,24 @@ while true; do cp ${ACME_BASE}/${CERT_NAME}/cert.pem ${ACME_BASE}/cert.pem cp ${ACME_BASE}/${CERT_NAME}/key.pem ${ACME_BASE}/key.pem fi + + if [[ "${SKIP_ECDSA_CERT}" != "y" ]]; then + DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh ecdsa + RETURN="$?" + if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully + CERT_AMOUNT_CHANGED=1 + CERT_CHANGED=1 + elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully + CERT_CHANGED=1 + elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal + : + else + CERT_ERRORS=1 + fi + # create relative symbolic link as server certificate + ln -sf ${CERT_NAME}/ecdsa-cert.pem ${ACME_BASE}/ecdsa-cert.pem + ln -sf ${CERT_NAME}/ecdsa-key.pem ${ACME_BASE}/ecdsa-key.pem + fi fi # individual certificates for SNI [@] @@ -345,6 +404,21 @@ while true; do else CERT_ERRORS=1 fi + + if [[ "${SKIP_ECDSA_CERT}" != "y" ]]; then + DOMAINS=${VALIDATED_DOMAINS_SORTED[@]} /srv/obtain-certificate.sh ecdsa + RETURN="$?" + if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully + CERT_AMOUNT_CHANGED=1 + CERT_CHANGED=1 + elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully + CERT_CHANGED=1 + elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal + : + else + CERT_ERRORS=1 + fi + fi fi done fi @@ -364,9 +438,19 @@ while true; do DATE=$(date +%Y-%m-%d_%H_%M_%S) log_f "Found orphaned certificate: ${EXISTING_CERT} - archiving it at ${ACME_BASE}/backups/${EXISTING_CERT}/" BACKUP_DIR=${ACME_BASE}/backups/${EXISTING_CERT}/${DATE} + BACKUP_DIR_ECDSA=${ACME_BASE}/backups/${EXISTING_CERT}/ecdsa-${DATE} + + # archive ecdsa cert (if exists) + mkdir -p ${BACKUP_DIR_ECDSA}/ + [[ -f ${ACME_BASE}/${EXISTING_CERT}/ecdsa-cert.pem && -f ${ACME_BASE}/${EXISTING_CERT}/domains ]] && cp ${ACME_BASE}/${EXISTING_CERT}/domains ${BACKUP_DIR_ECDSA}/ + [[ -f ${ACME_BASE}/${EXISTING_CERT}/ecdsa-cert.pem ]] && mv ${ACME_BASE}/${EXISTING_CERT}/ecdsa-cert.pem ${BACKUP_DIR_ECDSA}/ + [[ -f ${ACME_BASE}/${EXISTING_CERT}/ecdsa-key.pem ]] && mv ${ACME_BASE}/${EXISTING_CERT}/ecdsa-key.pem ${BACKUP_DIR_ECDSA}/ + [[ -f ${ACME_BASE}/${EXISTING_CERT}/ecdsa-acme.csr ]] && mv ${ACME_BASE}/${EXISTING_CERT}/ecdsa-acme.csr ${BACKUP_DIR_ECDSA}/ + # archive rsa cert and any other files mkdir -p ${ACME_BASE}/backups/${EXISTING_CERT} mv ${ACME_BASE}/${EXISTING_CERT} ${BACKUP_DIR} + CERT_CHANGED=1 CERT_AMOUNT_CHANGED=1 fi @@ -377,7 +461,7 @@ while true; do if [[ "${CERT_CHANGED}" == "1" ]]; then rm -f "${ACME_BASE}/force_renew" 2> /dev/null RELOAD_LOOP_C=1 - while [[ "${POSTFIX_CERT_SERIAL}" == "${POSTFIX_CERT_SERIAL_NEW}" ]] || [[ "${DOVECOT_CERT_SERIAL}" == "${DOVECOT_CERT_SERIAL_NEW}" ]] || [[ ${#POSTFIX_CERT_SERIAL_NEW} -ne 36 ]] || [[ ${#DOVECOT_CERT_SERIAL_NEW} -ne 36 ]]; do + while [[ "${POSTFIX_CERT_SERIAL}" == "${POSTFIX_CERT_SERIAL_NEW}" ]] || [[ "${DOVECOT_CERT_SERIAL}" == "${DOVECOT_CERT_SERIAL_NEW}" ]] || [[ "${POSTFIX_CERT_SERIAL_ECDSA}" == "${POSTFIX_CERT_SERIAL_NEW_ECDSA}" ]] || [[ "${DOVECOT_CERT_SERIAL_ECDSA}" == "${DOVECOT_CERT_SERIAL_NEW_ECDSA}" ]] || [[ ${#POSTFIX_CERT_SERIAL_NEW} -ne 36 ]] || [[ ${#DOVECOT_CERT_SERIAL_NEW} -ne 36 ]] || [[ ${#POSTFIX_CERT_SERIAL_NEW_ECDSA} -ne 36 ]] || [[ ${#DOVECOT_CERT_SERIAL_NEW_ECDSA} -ne 36 ]]; do log_f "Reloading or restarting services... (${RELOAD_LOOP_C})" RELOAD_LOOP_C=$((RELOAD_LOOP_C + 1)) CERT_AMOUNT_CHANGED=${CERT_AMOUNT_CHANGED} /srv/reload-configurations.sh @@ -389,8 +473,10 @@ while true; do until nc -z postfix 25; do sleep 1 done - POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" - DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -tls1_2 -cipher 'aRSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + POSTFIX_CERT_SERIAL_NEW_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" + DOVECOT_CERT_SERIAL_NEW_ECDSA="$(echo | openssl s_client -tls1_2 -cipher 'aECDSA' -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" if [[ ${RELOAD_LOOP_C} -gt 3 ]]; then log_f "Some services do return old end dates, something went wrong!" ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" diff --git a/data/Dockerfiles/acme/obtain-certificate.sh b/data/Dockerfiles/acme/obtain-certificate.sh index 74344119..2f223b60 100644 --- a/data/Dockerfiles/acme/obtain-certificate.sh +++ b/data/Dockerfiles/acme/obtain-certificate.sh @@ -15,8 +15,9 @@ ACME_BASE=/var/lib/acme TYPE=${1} PREFIX="" -# only support rsa certificates for now -if [[ "${TYPE}" != "rsa" ]]; then +if [[ "${TYPE}" == "ecdsa" ]]; then + PREFIX="ecdsa-" +elif [[ "${TYPE}" != "rsa" ]]; then log_f "Unknown certificate type '${TYPE}' requested" exit 5 fi diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 18746deb..4cc3fb25 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -274,6 +274,13 @@ for cert_dir in /etc/ssl/mail/*/ ; do echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf; echo ' ssl_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf; echo ' ssl_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf; + if [[ -f ${cert_dir}ecdsa-cert.pem && -f ${cert_dir}ecdsa-key.pem ]]; then + echo ' ssl_alt_cert = <'${cert_dir}'ecdsa-cert.pem' >> /etc/dovecot/sni.conf; + echo ' ssl_alt_key = <'${cert_dir}'ecdsa-key.pem' >> /etc/dovecot/sni.conf; + else + echo ' ssl_alt_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf; + echo ' ssl_alt_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf; + fi echo '}' >> /etc/dovecot/sni.conf; done done diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 78b070e0..7c5d710a 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -36,6 +36,9 @@ else IFS=" " read -r -a domains <<< "$(cat "${cert_dir}domains")" for domain in "${domains[@]}"; do echo -n "${domain} ${cert_dir}key.pem ${cert_dir}cert.pem" >> /opt/postfix/conf/sni.map; + if [[ -f ${cert_dir}ecdsa-cert.pem && -f ${cert_dir}ecdsa-key.pem ]]; then + echo -n " ${cert_dir}ecdsa-key.pem ${cert_dir}ecdsa-cert.pem" >> /opt/postfix/conf/sni.map; + fi echo "" >> /opt/postfix/conf/sni.map; done done diff --git a/data/assets/ssl-example/ecdsa-cert.pem b/data/assets/ssl-example/ecdsa-cert.pem new file mode 120000 index 00000000..f9bcc09f --- /dev/null +++ b/data/assets/ssl-example/ecdsa-cert.pem @@ -0,0 +1 @@ +cert.pem \ No newline at end of file diff --git a/data/assets/ssl-example/ecdsa-key.pem b/data/assets/ssl-example/ecdsa-key.pem new file mode 120000 index 00000000..9ba3d1b8 --- /dev/null +++ b/data/assets/ssl-example/ecdsa-key.pem @@ -0,0 +1 @@ +key.pem \ No newline at end of file diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index b7aca757..d88059fc 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -162,6 +162,8 @@ service lmtp { listen = *,[::] ssl_cert = &$current) { if ($current['type'] == 'TXT' && stripos($current['txt'], 'v=dmarc') === 0 && $record[2] == $dmarc_link) { @@ -368,6 +379,21 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm } elseif ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { + // Ignore $current TLSA record if it already matches another one in $records. + // E.g. hide found TLSA records for an RSA certificate in the status column + // of missing TLSA records for an ECDSA certificate. + if($current['type'] == 'TLSA' && $record[1] == 'TLSA') { + foreach($records as $other_record) { + if($record != $other_record && + $other_record[1] == 'TLSA' && + $other_record[0] == $current['host'] && + $other_record[2] == $current[$data_field[$current['type']]]) { + unset($currents[$i]); + continue 2; + } + } + } + $state = state_nomatch; if ($current[$data_field[$current['type']]] == $record[2]) { $state = state_good; diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index de1855fa..09e9a1a2 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -628,13 +628,16 @@ function expand_ipv6($ip) { $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); return $ip; } -function generate_tlsa_digest($hostname, $port, $starttls = null) { +function generate_tlsa_digest($hostname, $port, $starttls = null, $ciphers = 'DEFAULT') { if (!is_valid_domain_name($hostname)) { return "Not a valid hostname"; } if (empty($starttls)) { - $context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true))); - $stream = stream_socket_client('ssl://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context); + $context = stream_context_create(array("ssl" => array("ciphers" => $ciphers, "capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true))); + $stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context); + if(stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) === false) { + return false; // negotiation failed + } if (!$stream) { $error_msg = isset($error_msg) ? $error_msg : '-'; return $error_nr . ': ' . $error_msg; @@ -673,7 +676,10 @@ function generate_tlsa_digest($hostname, $port, $starttls = null) { stream_context_set_option($stream, 'ssl', 'verify_peer', false); stream_context_set_option($stream, 'ssl', 'verify_peer_name', false); stream_context_set_option($stream, 'ssl', 'allow_self_signed', true); - stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT); + stream_context_set_option($stream, 'ssl', 'ciphers', $ciphers); + if(stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) === false) { + return false; // negotiation failed + } stream_set_blocking($stream, false); } $params = stream_context_get_params($stream); diff --git a/docker-compose.yml b/docker-compose.yml index ad3053ae..8098f2d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -404,6 +404,7 @@ services: - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized} - DIRECTORY_URL=${DIRECTORY_URL:-} - ENABLE_SSL_SNI=${ENABLE_SSL_SNI:-n} + - SKIP_ECDSA_CERT=${SKIP_ECDSA_CERT:-y} - SKIP_IP_CHECK=${SKIP_IP_CHECK:-n} - SKIP_HTTP_VERIFICATION=${SKIP_HTTP_VERIFICATION:-n} - ONLY_MAILCOW_HOSTNAME=${ONLY_MAILCOW_HOSTNAME:-n} diff --git a/generate_config.sh b/generate_config.sh index 89af0f64..4fcb95e5 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -313,6 +313,10 @@ SKIP_LETS_ENCRYPT=n # see https://wiki.dovecot.org/SSL/SNIClientSupport ENABLE_SSL_SNI=n +# Skip issuing Let's Encrypt ECDSA certificates - y/n + +SKIP_ECDSA_CERT=n + # Skip IPv4 check in ACME container - y/n SKIP_IP_CHECK=n diff --git a/update.sh b/update.sh index 34d17354..9cc4780a 100755 --- a/update.sh +++ b/update.sh @@ -391,6 +391,7 @@ CONFIG_ARRAY=( "SOLR_HEAP" "SKIP_SOLR" "ENABLE_SSL_SNI" + "SKIP_ECDSA_CERT" "ALLOW_ADMIN_EMAIL_LOGIN" "SKIP_HTTP_VERIFICATION" "SOGO_EXPIRE_SESSION" @@ -533,6 +534,16 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf echo "ENABLE_SSL_SNI=n" >> mailcow.conf fi + elif [[ ${option} == "SKIP_ECDSA_CERT" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo "# Skip issuing Let's Encrypt ECDSA certificates - y/n" >> mailcow.conf + echo "# ECDSA certificates are disabled by default after upgrading." >> mailcow.conf + echo "# This should only be enabled if either" >> mailcow.conf + echo "# * you haven't set any TLSA DNS records or" >> mailcow.conf + echo "# * you will add additional TLSA DNS records for the ECDSA certificate. (See domain's DNS config in Mailcow UI after enabling.)" >> mailcow.conf + echo "SKIP_ECDSA_CERT=y" >> mailcow.conf + fi elif [[ ${option} == "SKIP_SOGO" ]]; then if ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf"