mirror of
https://github.com/Mailu/Mailu.git
synced 2025-05-31 23:10:01 +02:00
Merge #3188
3188: Ensure we always send an ISRG root for DANE r=nextgens a=nextgens ## What type of PR? bug-fix ## What does this PR do? Ensure we always send an ISRG root for DANE. Rebuild the x509 cert chain ourselves to ensure it's valid. It's fairly obvious that we can't trust letsencrypt to keep things sane (they are now planning to sign from random intermediaries) nor certbot to be consistent. ### Related issue(s) - close #3187 - #2138 ## Prerequisites Before we can consider review and merge, please make sure the following list is done and checked. If an entry in not applicable, you can check it or remove it from the list. - [ ] In case of feature or enhancement: documentation updated accordingly - [x] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file. Co-authored-by: Florent Daigniere <nextgens@freenetproject.org>
This commit is contained in:
commit
2dad43a207
@ -8,6 +8,8 @@ COPY package.json ./
|
||||
|
||||
RUN set -euxo pipefail \
|
||||
; npm config set update-notifier false \
|
||||
; echo "#!/bin/sh" >/usr/local/bin/husky \
|
||||
; chmod +x /usr/local/bin/husky \
|
||||
; npm install --no-audit --no-fund \
|
||||
; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \
|
||||
; mkdir assets \
|
||||
|
@ -12,7 +12,7 @@ cffi==1.16.0
|
||||
charset-normalizer==3.3.2
|
||||
click==8.1.7
|
||||
colorclass==2.2.2
|
||||
cryptography==41.0.7
|
||||
cryptography==42.0.5
|
||||
defusedxml==0.7.1
|
||||
Deprecated==1.2.14
|
||||
dnspython==2.5.0
|
||||
|
@ -330,8 +330,8 @@ mail {
|
||||
{% endif %}
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
|
||||
ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem;
|
||||
ssl_certificate /certs/letsencrypt/live/mailu-ecdsa/fullchain.pem;
|
||||
ssl_certificate /certs/letsencrypt/live/mailu/DANE-chain.pem;
|
||||
ssl_certificate /certs/letsencrypt/live/mailu-ecdsa/DANE-chain.pem;
|
||||
{% endif %}
|
||||
{% if TLS_PERMISSIVE %}
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
|
@ -5,6 +5,60 @@ import logging as log
|
||||
import sys
|
||||
from socrate import system, conf
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.x509.verification import PolicyBuilder, Store, DNSName
|
||||
from cryptography.x509.oid import NameOID
|
||||
import hashlib
|
||||
|
||||
ISRG_ROOT_X1 = x509.load_pem_x509_certificate(b'''-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
''')
|
||||
ISRG_ROOT_X2 = x509.load_pem_x509_certificate(b'''-----BEGIN CERTIFICATE-----
|
||||
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||
/q4AaOeMSQ+2b1tbFfLn
|
||||
-----END CERTIFICATE-----
|
||||
''')
|
||||
|
||||
args = system.set_env()
|
||||
log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING"))
|
||||
|
||||
@ -29,21 +83,47 @@ args["TLS"] = {
|
||||
"notls": None
|
||||
}[args["TLS_FLAVOR"]]
|
||||
|
||||
def format_for_nginx(fullchain, output):
|
||||
def format_for_nginx(fullchain, output, strip_CA=args.get('LETSENCRYPT_SHORTCHAIN')):
|
||||
""" We may want to strip ISRG Root X1 out """
|
||||
if not os.path.exists(fullchain):
|
||||
return
|
||||
split = '-----END CERTIFICATE-----\n'
|
||||
with open(fullchain, 'r') as pem:
|
||||
certs = [f'{cert}{split}' for cert in pem.read().split(split) if cert]
|
||||
if len(certs)>2 and args.get('LETSENCRYPT_SHORTCHAIN'):
|
||||
del certs[-1]
|
||||
with open(output, 'w') as pem:
|
||||
pem.write(''.join(certs))
|
||||
chain=[]
|
||||
with open(fullchain, 'rb') as f:
|
||||
chain = x509.load_pem_x509_certificates(f.read())
|
||||
builder = PolicyBuilder().store(Store([ISRG_ROOT_X1, ISRG_ROOT_X2]))
|
||||
verifier = builder.build_server_verifier(DNSName(chain[0].subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value))
|
||||
try:
|
||||
valid_chain = verifier.verify(chain[0], chain[1:])
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
valid_chain = chain
|
||||
log.info(f'The certificate chain looks as follows for {fullchain}:')
|
||||
indent = ' '
|
||||
has_found_PIN = False
|
||||
for cert in valid_chain:
|
||||
pubkey_der = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
||||
digest = hashlib.sha256(pubkey_der).hexdigest()
|
||||
log.info(f'{indent}{cert.subject.rfc4514_string()} {digest}')
|
||||
indent += ' '
|
||||
if digest == '0b9fa5a59eed715c26c1020c711b4f6ec42d58b0015e14337a39dad301c5afc3': # ISRG Root X1
|
||||
log.info('ISRG X1 PIN FOUND!')
|
||||
has_found_PIN = True
|
||||
elif digest == '762195c225586ee6c0237456e2107dc54f1efc21f61a792ebd515913cce68332': # ISRG Root X2
|
||||
log.info('ISRG X2 PIN FOUND!')
|
||||
has_found_PIN = True
|
||||
if not has_found_PIN:
|
||||
log.error('Neither ISRG X1 nor ISRG X2 have been found in the certificate chain. Please check your DANE records.')
|
||||
with open(output, 'wt') as f:
|
||||
for cert in valid_chain:
|
||||
if strip_CA and (cert.subject.rfc4514_string() in ['CN=ISRG Root X1,O=Internet Security Research Group,C=US', 'CN=ISRG Root X2,O=Internet Security Research Group,C=US']):
|
||||
continue
|
||||
f.write(f'{cert.public_bytes(encoding=Encoding.PEM).decode("ascii").strip()}\n')
|
||||
|
||||
if args['TLS_FLAVOR'] in ['letsencrypt', 'mail-letsencrypt']:
|
||||
format_for_nginx('/certs/letsencrypt/live/mailu/fullchain.pem', '/certs/letsencrypt/live/mailu/nginx-chain.pem')
|
||||
format_for_nginx('/certs/letsencrypt/live/mailu/fullchain.pem', '/certs/letsencrypt/live/mailu/DANE-chain.pem', False)
|
||||
format_for_nginx('/certs/letsencrypt/live/mailu-ecdsa/fullchain.pem', '/certs/letsencrypt/live/mailu-ecdsa/nginx-chain.pem')
|
||||
format_for_nginx('/certs/letsencrypt/live/mailu-ecdsa/fullchain.pem', '/certs/letsencrypt/live/mailu-ecdsa/DANE-chain.pem', False)
|
||||
|
||||
if args["TLS"] and not all(os.path.exists(file_path) for file_path in args["TLS"]):
|
||||
print("Missing cert or key file, disabling TLS")
|
||||
@ -55,4 +135,4 @@ conf.jinja("/conf/proxy.conf", args, "/etc/nginx/proxy.conf")
|
||||
conf.jinja("/conf/nginx.conf", args, "/etc/nginx/nginx.conf")
|
||||
conf.jinja("/dovecot_conf/login.lua", args, "/etc/dovecot/login.lua")
|
||||
conf.jinja("/dovecot_conf/proxy.conf", args, "/etc/dovecot/proxy.conf")
|
||||
os.system("killall -HUP nginx dovecot")
|
||||
os.system("killall -q -HUP nginx dovecot")
|
||||
|
1
towncrier/newsfragments/3187.bugfix
Normal file
1
towncrier/newsfragments/3187.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Ensure we always send ISRG_X1 root when LE is configured. Switch to the non-crossigned version as the other one will expire in September
|
Loading…
x
Reference in New Issue
Block a user