add CRL functionality from authnid

This commit is contained in:
dandds
2018-07-30 17:32:09 -04:00
parent 0f8e303afa
commit f0a7bfcd0e
35 changed files with 2353 additions and 6 deletions

View File

View File

@@ -0,0 +1,72 @@
import requests
import re
import os
from html.parser import HTMLParser
_DISA_CRLS = "https://iasecontent.disa.mil/pki-pke/data/crls/dod_crldps.htm"
def fetch_disa():
response = requests.get(_DISA_CRLS)
return response.text
class DISAParser(HTMLParser):
crl_list = []
_CRL_MATCH = re.compile("DOD(ROOT|EMAIL|ID)?CA")
def handle_starttag(self, tag, attrs):
if tag == "a":
href = [pair[1] for pair in attrs if pair[0] == "href"].pop()
if re.search(self._CRL_MATCH, href):
self.crl_list.append(href)
def crl_list_from_disa_html(html):
parser = DISAParser()
parser.reset()
parser.feed(html)
return parser.crl_list
def write_crl(out_dir, crl_location):
name = re.split("/", crl_location)[-1]
crl = os.path.join(out_dir, name)
with requests.get(crl_location, stream=True) as r:
with open(crl, "wb") as crl_file:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
crl_file.write(chunk)
def refresh_crls(out_dir, logger=None):
disa_html = fetch_disa()
crl_list = crl_list_from_disa_html(disa_html)
for crl_location in crl_list:
if logger:
logger.info("updating CRL from {}".format(crl_location))
try:
write_crl(out_dir, crl_location)
except requests.exceptions.ChunkedEncodingError:
if logger:
logger.error(
"Error downloading {}, continuing anyway".format(crl_location)
)
if __name__ == "__main__":
import sys
import datetime
import logging
logging.basicConfig(
level=logging.INFO, format="[%(asctime)s]:%(levelname)s: %(message)s"
)
logger = logging.getLogger()
logger.info("Updating CRLs")
try:
refresh_crls(sys.argv[1], logger=logger)
except Exception as err:
logger.exception("Fatal error encountered, stopping")
sys.exit(1)
logger.info("Finished updating CRLs")

View File

@@ -0,0 +1,124 @@
import sys
import os
import re
import hashlib
from OpenSSL import crypto, SSL
def sha256_checksum(filename, block_size=65536):
sha256 = hashlib.sha256()
with open(filename, "rb") as f:
for block in iter(lambda: f.read(block_size), b""):
sha256.update(block)
return sha256.hexdigest()
class Validator:
_PEM_RE = re.compile(
b"-----BEGIN CERTIFICATE-----\r?.+?\r?-----END CERTIFICATE-----\r?\n?",
re.DOTALL,
)
def __init__(self, crl_locations=[], roots=[], base_store=crypto.X509Store):
self.errors = []
self.crl_locations = crl_locations
self.roots = roots
self.base_store = base_store
self._reset()
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 _add_crls(self, locations):
for filename in locations:
try:
self._add_crl(filename)
except crypto.Error as err:
self.errors.append(
"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.
def refresh_cache(self, cert):
der = cert.get_issuer().der()
if der in self.cache:
filename, checksum = self.cache[der]
if sha256_checksum(filename) != checksum:
self._reset()
def validate(self, cert):
parsed = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
self.refresh_cache(parsed)
context = crypto.X509StoreContext(self.store, parsed)
try:
context.verify_certificate()
return True
except crypto.X509StoreContextError as err:
self.errors.append(
"Certificate revoked or errored. Error: {}. Args: {}".format(
type(err), err.args
)
)
return False