From 9dc79416585aed33834a5630bae0c0b328d719ae Mon Sep 17 00:00:00 2001 From: David Klaftenegger Date: Mon, 4 Apr 2016 01:08:24 +0200 Subject: [PATCH] Refactor ssl functionality use pyopenssl for certificate validty and requests --- acertmgr.py | 34 ++++++---------------------------- acertmgr_ssl.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 28 deletions(-) create mode 100644 acertmgr_ssl.py diff --git a/acertmgr.py b/acertmgr.py index d55e843..44417c5 100755 --- a/acertmgr.py +++ b/acertmgr.py @@ -6,9 +6,9 @@ import acme_tiny +import acertmgr_ssl import acertmgr_web import datetime -import dateutil.parser import dateutil.relativedelta import grp import os @@ -49,20 +49,7 @@ def cert_isValid(crt_file, ttl_days): if not os.path.isfile(crt_file): return False else: - # check validity using OpenSSL - vc = subprocess.check_output(['openssl', 'x509', '-in', crt_file, '-noout', '-dates']) - - m = re.search(b"notBefore=(.+)", vc) - if m: - valid_from = dateutil.parser.parse(m.group(1), ignoretz=True) - else: - raise InvalidCertificateError("No notBefore date found") - - m = re.search(b"notAfter=(.+)", vc) - if m: - valid_to = dateutil.parser.parse(m.group(1), ignoretz=True) - else: - raise InvalidCertificateError("No notAfter date found") + (valid_from, valid_to) = acertmgr_ssl.cert_valid_times(crt_file) now = datetime.datetime.now() if valid_from > now: @@ -104,19 +91,10 @@ def cert_get(domain, settings): server = acertmgr_web.ACMEHTTPServer(port) server.start() try: - allnames = domain.split(' ') - if len(allnames) == 1: - cr = subprocess.check_output(['openssl', 'req', '-new', '-sha256', '-key', key_file, '-out', csr_file, '-subj', '/CN=%s' % domain]) - else: - cnt = 0 - altnames = [] - for alias in allnames[1:] - cnt = cnt + 1 - altnames.append('DNS.%d=%s' % cnt, alias) - subject = '/CN=%s subjectAltName=%s' % allnames[0], ','.join(altnames) - cr = subprocess.check_output(['openssl', 'req', '-new', '-sha256', '-key', key_file, '-out', csr_file, '-reqexts', 'SAN', '-subj', subject]) - - # get certificate + key_fd = open(key_file, "r") + key = key_fd.read() + key_fd.close() + cr = acertmgr_ssl.cert_request(domains.split(), key) crt = acme_tiny.get_crt(acc_file, csr_file, challenge_dir) with open(crt_file, "w") as crt_fd: crt_fd.write(crt) diff --git a/acertmgr_ssl.py b/acertmgr_ssl.py new file mode 100644 index 0000000..1a6b825 --- /dev/null +++ b/acertmgr_ssl.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# acertmgr - ssl management functions +# Copyright (c) Markus Hauschild & David Klaftenegger, 2016. +# available under the ISC license, see LICENSE + +from OpenSSL import crypto + +# @brief retrieve notBefore and notAfter dates of a certificate file +# @param cert_file the path to the certificate +# @return the tuple of dates: (notBefore, notAfter) +def cert_valid_times(cert_file): + with open(cert_file) as f: + cert_data = f.read() + cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) + asn1time = str('%Y%m%d%H%M%SZ'.encode('utf8')) + not_before = datetime.datetime.strptime(str(cert.get_notBefore()), asn1time) + not_after = datetime.datetime.strptime(str(cert.get_notAfter()), asn1time) + return (not_before, not_after) + +# @brief create a certificate signing request +# @param names list of domain names the certificate should be valid for +# @param key_data the key to use with the certificate in PEM format +# @return the CSR in PEM format +def cert_request(names, key_data): + req = crypto.X509Req() + req.get_subject().commonName = names[0] + entries = ['DNS:'+name for name in names] + extensions = [crypto.X509Extension('subjectAltName', False, ', '.join(entries))] + req.add_extensions(extensions) + key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) + req.set_pubkey(key) + req.sign(key, 'sha256') + return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req) +