more straightforward crl check function
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from atst.domain.exceptions import UnauthenticatedError, NotFoundError
|
||||
from atst.domain.users import Users
|
||||
from .utils import parse_sdn, email_from_certificate
|
||||
from .crl import Validator
|
||||
from .crl import crl_check, CRLException
|
||||
|
||||
|
||||
class AuthenticationContext():
|
||||
@@ -22,8 +22,7 @@ class AuthenticationContext():
|
||||
if not self.auth_status == "SUCCESS":
|
||||
raise UnauthenticatedError("SSL/TLS client authentication failed")
|
||||
|
||||
elif not self._crl_check():
|
||||
raise UnauthenticatedError("Client certificate failed CRL check")
|
||||
self._crl_check()
|
||||
|
||||
return True
|
||||
|
||||
@@ -45,13 +44,10 @@ class AuthenticationContext():
|
||||
return None
|
||||
|
||||
def _crl_check(self):
|
||||
validator = Validator(self.crl_cache, self.cert)
|
||||
if self.cert:
|
||||
result = validator.validate()
|
||||
return result
|
||||
|
||||
else:
|
||||
return False
|
||||
try:
|
||||
crl_check(self.crl_cache, self.cert)
|
||||
except CRLException as exc:
|
||||
raise UnauthenticatedError("CRL check failed. " + str(exc))
|
||||
|
||||
@property
|
||||
def parsed_sdn(self):
|
||||
|
@@ -4,6 +4,8 @@ import re
|
||||
import hashlib
|
||||
from OpenSSL import crypto, SSL
|
||||
|
||||
class CRLException(Exception):
|
||||
pass
|
||||
|
||||
def sha256_checksum(filename, block_size=65536):
|
||||
sha256 = hashlib.sha256()
|
||||
@@ -13,6 +15,22 @@ def sha256_checksum(filename, block_size=65536):
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def crl_check(cache, cert):
|
||||
parsed = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
store = cache.get_store(parsed)
|
||||
context = crypto.X509StoreContext(store, parsed)
|
||||
try:
|
||||
context.verify_certificate()
|
||||
return True
|
||||
|
||||
except crypto.X509StoreContextError as err:
|
||||
raise CRLException(
|
||||
"Certificate revoked or errored. Error: {}. Args: {}".format(
|
||||
type(err), err.args
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CRLCache():
|
||||
|
||||
_PEM_RE = re.compile(
|
||||
@@ -77,109 +95,3 @@ class CRLCache():
|
||||
else:
|
||||
return self.x509_stores[issuer]
|
||||
|
||||
|
||||
class Validator:
|
||||
|
||||
def __init__(self, cache, cert, logger=None):
|
||||
self.cache = cache
|
||||
self.cert = cert
|
||||
self.logger = logger
|
||||
|
||||
def validate(self):
|
||||
parsed = crypto.load_certificate(crypto.FILETYPE_PEM, self.cert)
|
||||
store = self.cache.get_store(parsed)
|
||||
context = crypto.X509StoreContext(store, parsed)
|
||||
try:
|
||||
context.verify_certificate()
|
||||
return True
|
||||
|
||||
except crypto.X509StoreContextError as err:
|
||||
self.log_error(
|
||||
"Certificate revoked or errored. Error: {}. Args: {}".format(
|
||||
type(err), err.args
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
def _add_roots(self, roots):
|
||||
with open(filename, "rb") as f:
|
||||
for raw_ca in self._parse_roots(f.read()):
|
||||
ca = crypto.load_certificate(crypto.FILETYPE_PEM, raw_ca)
|
||||
self._add_carefully("add_cert", ca)
|
||||
|
||||
def _reset(self):
|
||||
self.cache = {}
|
||||
self.store = self.base_store()
|
||||
self._add_crls(self.crl_locations)
|
||||
self._add_roots(self.roots)
|
||||
self.store.set_flags(crypto.X509StoreFlags.CRL_CHECK)
|
||||
|
||||
def log_error(self, message):
|
||||
if self.logger:
|
||||
self.logger.error(message)
|
||||
|
||||
def _add_crls(self, locations):
|
||||
for filename in locations:
|
||||
try:
|
||||
self._add_crl(filename)
|
||||
except crypto.Error as err:
|
||||
self.log_error(
|
||||
"CRL could not be parsed. Filename: {}, Error: {}, args: {}".format(
|
||||
filename, type(err), err.args
|
||||
)
|
||||
)
|
||||
|
||||
# This caches the CRL issuer with the CRL filepath and a checksum, in addition to adding the CRL to the store.
|
||||
|
||||
def _add_crl(self, filename):
|
||||
with open(filename, "rb") as crl_file:
|
||||
crl = crypto.load_crl(crypto.FILETYPE_ASN1, crl_file.read())
|
||||
self.cache[crl.get_issuer().der()] = (filename, sha256_checksum(filename))
|
||||
self._add_carefully("add_crl", crl)
|
||||
|
||||
def _parse_roots(self, root_str):
|
||||
return [match.group(0) for match in self._PEM_RE.finditer(root_str)]
|
||||
|
||||
def _add_roots(self, roots):
|
||||
for filename in roots:
|
||||
with open(filename, "rb") as f:
|
||||
for raw_ca in self._parse_roots(f.read()):
|
||||
ca = crypto.load_certificate(crypto.FILETYPE_PEM, raw_ca)
|
||||
self._add_carefully("add_cert", ca)
|
||||
|
||||
# in testing, it seems that openssl is maintaining a local cache of certs
|
||||
# in a hash table and throws errors if you try to add redundant certs or
|
||||
# CRLs. For now, we catch and ignore that error with great specificity.
|
||||
|
||||
def _add_carefully(self, method_name, obj):
|
||||
try:
|
||||
getattr(self.store, method_name)(obj)
|
||||
except crypto.Error as error:
|
||||
if self._is_preloaded_error(error):
|
||||
pass
|
||||
else:
|
||||
raise error
|
||||
|
||||
PRELOADED_CRL = (
|
||||
[
|
||||
(
|
||||
"x509 certificate routines",
|
||||
"X509_STORE_add_crl",
|
||||
"cert already in hash table",
|
||||
)
|
||||
],
|
||||
)
|
||||
PRELOADED_CERT = (
|
||||
[
|
||||
(
|
||||
"x509 certificate routines",
|
||||
"X509_STORE_add_cert",
|
||||
"cert already in hash table",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def _is_preloaded_error(self, error):
|
||||
return error.args == self.PRELOADED_CRL or error.args == self.PRELOADED_CERT
|
||||
|
||||
# Checks that the CRL currently in-memory is up-to-date via the checksum.
|
||||
|
Reference in New Issue
Block a user