From 59a9d02fa5a1b72f607ef03d3086101ea44dca30 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Mon, 7 Jul 2014 12:06:11 +0000 Subject: [PATCH] check that installed certificates are for the domains we are using the certificates for --- management/buy_certificate.py | 2 +- management/whats_next.py | 39 +++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/management/buy_certificate.py b/management/buy_certificate.py index 21d277a1..3dbea160 100755 --- a/management/buy_certificate.py +++ b/management/buy_certificate.py @@ -108,7 +108,7 @@ def buy_ssl_certificate(api_key, domain, command, env): # Check before we overwrite something we shouldn't. if os.path.exists(ssl_certificate): - cert_status = check_certificate(ssl_certificate) + cert_status = check_certificate(None, ssl_certificate) if cert_status != "SELF-SIGNED": print("Please back up and delete the file %s so I can save your new certificate." % ssl_certificate) sys.exit(1) diff --git a/management/whats_next.py b/management/whats_next.py index 333a00f1..e7699cbe 100755 --- a/management/whats_next.py +++ b/management/whats_next.py @@ -223,7 +223,7 @@ def check_ssl_cert(domain, env): # Check that the certificate is good. - cert_status = check_certificate(ssl_certificate) + cert_status = check_certificate(domain, ssl_certificate) if cert_status == "SELF-SIGNED": fingerprint = shell('check_output', [ @@ -265,9 +265,44 @@ def check_ssl_cert(domain, env): print(cert_status) print("") -def check_certificate(ssl_certificate): +def check_certificate(domain ,ssl_certificate): # Use openssl verify to check the status of a certificate. + # First check that the certificate is for the right domain. The domain + # must be found in the Subject Common Name (CN) or be one of the + # Subject Alternative Names. + cert_dump = shell('check_output', [ + "openssl", "x509", + "-in", ssl_certificate, + "-noout", "-text", "-nameopt", "rfc2253", + ]) + cert_dump = cert_dump.split("\n") + certificate_names = set() + while len(cert_dump) > 0: + line = cert_dump.pop(0) + + # Grab from the Subject Common Name. We include the indentation + # at the start of the line in case maybe the cert includes the + # common name of some other referenced entity (which would be + # indented, I hope). + m = re.match(" Subject: CN=([^,]+)", line) + if m: + certificate_names.add(m.group(1)) + + # Grab from the Subject Alternative Name, which is a comma-delim + # list of names, like DNS:mydomain.com, DNS:otherdomain.com. + m = re.match(" X509v3 Subject Alternative Name:", line) + if m: + names = re.split(",\s*", cert_dump.pop(0).strip()) + for n in names: + m = re.match("DNS:(.*)", n) + if m: + certificate_names.add(m.group(1)) + + if domain is not None and domain not in certificate_names: + return "This certificate is for the wrong domain names. It is for %s." % \ + ", ".join(sorted(certificate_names)) + # In order to verify with openssl, we need to split out any # intermediary certificates in the chain (if any) from our # certificate (at the top). They need to be passed separately.