2018-05-21 21:02:38 +00:00
|
|
|
from fnmatch import fnmatch
|
|
|
|
|
|
|
|
import OpenSSL
|
|
|
|
|
2018-08-15 19:32:24 +00:00
|
|
|
|
2018-05-21 21:02:38 +00:00
|
|
|
class CertInvalidException(Exception):
|
|
|
|
""" Exception raised when a certificate could not be parsed/loaded. """
|
|
|
|
pass
|
|
|
|
|
2018-08-15 19:32:24 +00:00
|
|
|
|
2018-05-21 21:02:38 +00:00
|
|
|
class KeyInvalidException(Exception):
|
|
|
|
""" Exception raised when a key could not be parsed/loaded or successfully applied to a cert. """
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def load_certificate(cert_contents):
|
|
|
|
""" Loads the certificate from the given contents and returns it or raises a CertInvalidException
|
|
|
|
on failure.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_contents)
|
|
|
|
return SSLCertificate(cert)
|
|
|
|
except OpenSSL.crypto.Error as ex:
|
|
|
|
raise CertInvalidException(ex.message[0][2])
|
|
|
|
|
|
|
|
|
|
|
|
_SUBJECT_ALT_NAME = 'subjectAltName'
|
|
|
|
|
2018-08-15 19:32:24 +00:00
|
|
|
|
2018-05-21 21:02:38 +00:00
|
|
|
class SSLCertificate(object):
|
|
|
|
""" Helper class for easier working with SSL certificates. """
|
2018-08-15 19:32:24 +00:00
|
|
|
|
2018-05-21 21:02:38 +00:00
|
|
|
def __init__(self, openssl_cert):
|
|
|
|
self.openssl_cert = openssl_cert
|
|
|
|
|
|
|
|
def validate_private_key(self, private_key_path):
|
|
|
|
""" Validates that the private key found at the given file path applies to this certificate.
|
|
|
|
Raises a KeyInvalidException on failure.
|
|
|
|
"""
|
|
|
|
context = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
|
|
|
|
context.use_certificate(self.openssl_cert)
|
|
|
|
|
|
|
|
try:
|
|
|
|
context.use_privatekey_file(private_key_path)
|
|
|
|
context.check_privatekey()
|
|
|
|
except OpenSSL.SSL.Error as ex:
|
|
|
|
raise KeyInvalidException(ex.message[0][2])
|
|
|
|
|
|
|
|
def matches_name(self, check_name):
|
|
|
|
""" Returns true if this SSL certificate matches the given DNS hostname. """
|
|
|
|
for dns_name in self.names:
|
|
|
|
if fnmatch(check_name, dns_name):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def expired(self):
|
|
|
|
""" Returns whether the SSL certificate has expired. """
|
|
|
|
return self.openssl_cert.has_expired()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def common_name(self):
|
|
|
|
""" Returns the defined common name for the certificate, if any. """
|
|
|
|
return self.openssl_cert.get_subject().commonName
|
|
|
|
|
|
|
|
@property
|
|
|
|
def names(self):
|
|
|
|
""" Returns all the DNS named to which the certificate applies. May be empty. """
|
|
|
|
dns_names = set()
|
|
|
|
common_name = self.common_name
|
|
|
|
if common_name is not None:
|
|
|
|
dns_names.add(common_name)
|
|
|
|
|
|
|
|
# Find the DNS extension, if any.
|
|
|
|
for i in range(0, self.openssl_cert.get_extension_count()):
|
|
|
|
ext = self.openssl_cert.get_extension(i)
|
|
|
|
if ext.get_short_name() == _SUBJECT_ALT_NAME:
|
|
|
|
value = str(ext)
|
|
|
|
for san_name in value.split(','):
|
|
|
|
san_name_trimmed = san_name.strip()
|
|
|
|
if san_name_trimmed.startswith('DNS:'):
|
|
|
|
dns_names.add(san_name_trimmed[4:])
|
|
|
|
|
|
|
|
return dns_names
|