change DANE TLSA record to hash the subject public key rather than the whole certificate, which means it is good for any certificate tied to the same private key
Better for short-lived certificates. This is especially in preparation to using certificates from Let's Encrypt. see #268
This commit is contained in:
parent
4305a71916
commit
392d33b902
3 changed files with 36 additions and 20 deletions
|
@ -8,6 +8,7 @@ Mail:
|
||||||
|
|
||||||
* Updated Roundcube to version 1.1.3.
|
* Updated Roundcube to version 1.1.3.
|
||||||
* Auto-create aliases for abuse@, as required by RFC2142.
|
* Auto-create aliases for abuse@, as required by RFC2142.
|
||||||
|
* The DANE TLSA record is changed to use the certificate subject public key rather than the whole certificate, which means the record remains valid after certificate changes (so long as the private key remains the same, which it does for us).
|
||||||
|
|
||||||
Control panel:
|
Control panel:
|
||||||
|
|
||||||
|
|
|
@ -283,26 +283,40 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
|
||||||
|
|
||||||
def build_tlsa_record(env):
|
def build_tlsa_record(env):
|
||||||
# A DANE TLSA record in DNS specifies that connections on a port
|
# A DANE TLSA record in DNS specifies that connections on a port
|
||||||
# must use TLS and the certificate must match a particular certificate.
|
# must use TLS and the certificate must match a particular criteria.
|
||||||
#
|
#
|
||||||
# Thanks to http://blog.huque.com/2012/10/dnssec-and-certificates.html
|
# Thanks to http://blog.huque.com/2012/10/dnssec-and-certificates.html
|
||||||
# for explaining all of this!
|
# and https://community.letsencrypt.org/t/please-avoid-3-0-1-and-3-0-2-dane-tlsa-records-with-le-certificates/7022
|
||||||
|
# for explaining all of this! Also see https://tools.ietf.org/html/rfc6698#section-2.1
|
||||||
|
# and https://github.com/mail-in-a-box/mailinabox/issues/268#issuecomment-167160243.
|
||||||
|
#
|
||||||
|
# There are several criteria. We used to use "3 0 1" criteria, which
|
||||||
|
# meant to pin a leaf (3) certificate (0) with SHA256 hash (1). But
|
||||||
|
# certificates change, and especially as we move to short-lived certs
|
||||||
|
# they change often. The TLSA record handily supports the criteria of
|
||||||
|
# a leaf certificate (3)'s subject public key (1) with SHA256 hash (1).
|
||||||
|
# The subject public key is the public key portion of the private key
|
||||||
|
# that generated the CSR that generated the certificate. Since we
|
||||||
|
# generate a private key once the first time Mail-in-a-Box is set up
|
||||||
|
# and reuse it for all subsequent certificates, the TLSA record will
|
||||||
|
# remain valid indefinitely.
|
||||||
|
|
||||||
# Get the hex SHA256 of the DER-encoded server certificate:
|
from ssl_certificates import load_cert_chain, load_pem
|
||||||
certder = shell("check_output", [
|
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||||
"/usr/bin/openssl",
|
|
||||||
"x509",
|
fn = os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem")
|
||||||
"-in", os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem"),
|
cert = load_pem(load_cert_chain(fn)[0])
|
||||||
"-outform", "DER"
|
|
||||||
],
|
subject_public_key = cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
||||||
return_bytes=True)
|
# We could have also loaded ssl_private_key.pem and called priv_key.public_key().public_bytes(...)
|
||||||
certhash = hashlib.sha256(certder).hexdigest()
|
|
||||||
|
pk_hash = hashlib.sha256(subject_public_key).hexdigest()
|
||||||
|
|
||||||
# Specify the TLSA parameters:
|
# Specify the TLSA parameters:
|
||||||
# 3: This is the certificate that the client should trust. No CA is needed.
|
# 3: Match the (leaf) certificate. (No CA, no trust path needed.)
|
||||||
# 0: The whole certificate is matched.
|
# 1: Match its subject public key.
|
||||||
# 1: The certificate is SHA256'd here.
|
# 1: Use SHA256.
|
||||||
return "3 0 1 " + certhash
|
return "3 1 1 " + pk_hash
|
||||||
|
|
||||||
def build_sshfp_records():
|
def build_sshfp_records():
|
||||||
# The SSHFP record is a way for us to embed this server's SSH public
|
# The SSHFP record is a way for us to embed this server's SSH public
|
||||||
|
|
|
@ -184,21 +184,22 @@ def install_cert(domain, ssl_cert, ssl_chain, env):
|
||||||
|
|
||||||
# When updating the cert for PRIMARY_HOSTNAME, symlink it from the system
|
# When updating the cert for PRIMARY_HOSTNAME, symlink it from the system
|
||||||
# certificate path, which is hard-coded for various purposes, and then
|
# certificate path, which is hard-coded for various purposes, and then
|
||||||
# update DNS (because of the DANE TLSA record), postfix, and dovecot,
|
# restart postfix and dovecot.
|
||||||
# which all use the file.
|
|
||||||
if domain == env['PRIMARY_HOSTNAME']:
|
if domain == env['PRIMARY_HOSTNAME']:
|
||||||
# Update symlink.
|
# Update symlink.
|
||||||
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||||
os.unlink(system_ssl_certificate)
|
os.unlink(system_ssl_certificate)
|
||||||
os.symlink(ssl_certificate, system_ssl_certificate)
|
os.symlink(ssl_certificate, system_ssl_certificate)
|
||||||
|
|
||||||
# Update DNS & restart postfix and dovecot so they pick up the new file.
|
# Restart postfix and dovecot so they pick up the new file.
|
||||||
from dns_update import do_dns_update
|
|
||||||
ret.append( do_dns_update(env) )
|
|
||||||
shell('check_call', ["/usr/sbin/service", "postfix", "restart"])
|
shell('check_call', ["/usr/sbin/service", "postfix", "restart"])
|
||||||
shell('check_call', ["/usr/sbin/service", "dovecot", "restart"])
|
shell('check_call', ["/usr/sbin/service", "dovecot", "restart"])
|
||||||
ret.append("mail services restarted")
|
ret.append("mail services restarted")
|
||||||
|
|
||||||
|
# The DANE TLSA record will remain valid so long as the private key
|
||||||
|
# hasn't changed. We don't ever change the private key automatically.
|
||||||
|
# If the user does it, they must manually update DNS.
|
||||||
|
|
||||||
# Update the web configuration so nginx picks up the new certificate file.
|
# Update the web configuration so nginx picks up the new certificate file.
|
||||||
from web_update import do_web_update
|
from web_update import do_web_update
|
||||||
ret.append( do_web_update(env) )
|
ret.append( do_web_update(env) )
|
||||||
|
|
Loading…
Reference in a new issue