Module cevast.analysis.modules.chain_inspector
This module contains an implementation of a certificate chain inspector.
The inspector can be both imported and used externally (as a standalone module) through the provided command-line interface.
Expand source code
#!/usr/bin/python3
"""
This module contains an implementation of a certificate chain inspector.
The inspector can be both imported and used externally (as a standalone module) through the provided
command-line interface.
"""
import argparse
import itertools
from OpenSSL import crypto
# noinspection PyBroadException
class ChainInspector:
"""
A class for certificate chain inspection --- classification of certificate orderings and counting of self-signed CAs.
Classified certificate orderings:
OK: Certificates came in the correct order (i.e., according to the standard). A chain starts with
the server’s certificate, and every subsequent certificate’s subject is the previous certificate’s issuer.
REORDERED: Certificates came reordered. There exists a permutation in which their ordering is correct.
REVERSED: A particular case of reordering, when certificates came in the reversed order of what would be
correct.
DISCONNECTED: There is no permutation in which certificates' ordering is correct.
TOOLONG: Chain contains too many certificates to check all permutations.
Output format: [certificate ordering]-[number of self-signed CAs]CA
Example output: REVERSED-1CA (reversed chain with one self-signed CA)
"""
@staticmethod
def inspect(input_chain, load_from_disk=True, **kwargs):
"""
Inspects the given chain.
`input_chain` is a list of paths to certificates forming a chain.
If `load_from_disk` is set to False, certificates are expected to be provided directly (as their contents),
instead of the paths.
`kwargs` are other, unexpected arguments.
The result of chain inspection is (for compatibility) returned as a single string in a list.
"""
input_chain = list(input_chain)
try:
if load_from_disk:
for i, certificate_path in enumerate(input_chain):
with open(certificate_path) as input_file:
input_chain[i] = input_file.read().encode()
original_certificate_chain = [crypto.load_certificate(crypto.FILETYPE_PEM, certificate_content) for certificate_content in input_chain]
original_pairs = ChainInspector.__get_subject_issuer_pairs(original_certificate_chain)
if len(original_pairs) <= 8:
result = "DISCONNECTED"
if ChainInspector.__is_chain_continuous(original_pairs):
result = "OK"
elif ChainInspector.__is_chain_continuous(list(reversed(original_pairs))):
result = "REVERSED"
else:
for pair_permutation in list(itertools.permutations(original_pairs)):
if ChainInspector.__is_chain_continuous(list(pair_permutation)):
result = "REORDERED"
break
else:
result = "TOOLONG"
result += "-{0}CA".format(ChainInspector.__get_ca_certificate_count(original_pairs))
except Exception:
result = "ERROR"
return [result]
@staticmethod
def __get_subject_issuer_pairs(certificate_chain):
pairs = []
for certificate in certificate_chain:
subject = str(certificate.get_subject())
issuer = str(certificate.get_issuer())
pairs.append([subject, issuer])
return pairs
@staticmethod
def __is_chain_continuous(pairs):
is_valid = True
last_issuer = None
for pair in pairs:
subject = pair[0]
issuer = pair[1]
if last_issuer is not None:
if subject != last_issuer:
is_valid = False
break
last_issuer = issuer
return is_valid
@staticmethod
def __get_ca_certificate_count(pairs):
count = 0
for pair in pairs:
subject = pair[0]
issuer = pair[1]
if subject == issuer:
count += 1
return count
if __name__ == "__main__":
argument_parser = argparse.ArgumentParser(description="chain format: CERTIFICATE [CERTIFICATE ...]")
argument_parser.add_argument("CERTIFICATE", type=str, nargs="+")
args = argument_parser.parse_args()
print(ChainInspector.inspect(args.CERTIFICATE))
Classes
class ChainInspector
-
A class for certificate chain inspection — classification of certificate orderings and counting of self-signed CAs.
Classified certificate orderings:
OK: Certificates came in the correct order (i.e., according to the standard). A chain starts with the server’s certificate, and every subsequent certificate’s subject is the previous certificate’s issuer.
REORDERED: Certificates came reordered. There exists a permutation in which their ordering is correct.
REVERSED: A particular case of reordering, when certificates came in the reversed order of what would be correct.
DISCONNECTED: There is no permutation in which certificates' ordering is correct.
TOOLONG: Chain contains too many certificates to check all permutations.
Output format: [certificate ordering]-[number of self-signed CAs]CA
Example output: REVERSED-1CA (reversed chain with one self-signed CA)
Expand source code
class ChainInspector: """ A class for certificate chain inspection --- classification of certificate orderings and counting of self-signed CAs. Classified certificate orderings: OK: Certificates came in the correct order (i.e., according to the standard). A chain starts with the server’s certificate, and every subsequent certificate’s subject is the previous certificate’s issuer. REORDERED: Certificates came reordered. There exists a permutation in which their ordering is correct. REVERSED: A particular case of reordering, when certificates came in the reversed order of what would be correct. DISCONNECTED: There is no permutation in which certificates' ordering is correct. TOOLONG: Chain contains too many certificates to check all permutations. Output format: [certificate ordering]-[number of self-signed CAs]CA Example output: REVERSED-1CA (reversed chain with one self-signed CA) """ @staticmethod def inspect(input_chain, load_from_disk=True, **kwargs): """ Inspects the given chain. `input_chain` is a list of paths to certificates forming a chain. If `load_from_disk` is set to False, certificates are expected to be provided directly (as their contents), instead of the paths. `kwargs` are other, unexpected arguments. The result of chain inspection is (for compatibility) returned as a single string in a list. """ input_chain = list(input_chain) try: if load_from_disk: for i, certificate_path in enumerate(input_chain): with open(certificate_path) as input_file: input_chain[i] = input_file.read().encode() original_certificate_chain = [crypto.load_certificate(crypto.FILETYPE_PEM, certificate_content) for certificate_content in input_chain] original_pairs = ChainInspector.__get_subject_issuer_pairs(original_certificate_chain) if len(original_pairs) <= 8: result = "DISCONNECTED" if ChainInspector.__is_chain_continuous(original_pairs): result = "OK" elif ChainInspector.__is_chain_continuous(list(reversed(original_pairs))): result = "REVERSED" else: for pair_permutation in list(itertools.permutations(original_pairs)): if ChainInspector.__is_chain_continuous(list(pair_permutation)): result = "REORDERED" break else: result = "TOOLONG" result += "-{0}CA".format(ChainInspector.__get_ca_certificate_count(original_pairs)) except Exception: result = "ERROR" return [result] @staticmethod def __get_subject_issuer_pairs(certificate_chain): pairs = [] for certificate in certificate_chain: subject = str(certificate.get_subject()) issuer = str(certificate.get_issuer()) pairs.append([subject, issuer]) return pairs @staticmethod def __is_chain_continuous(pairs): is_valid = True last_issuer = None for pair in pairs: subject = pair[0] issuer = pair[1] if last_issuer is not None: if subject != last_issuer: is_valid = False break last_issuer = issuer return is_valid @staticmethod def __get_ca_certificate_count(pairs): count = 0 for pair in pairs: subject = pair[0] issuer = pair[1] if subject == issuer: count += 1 return count
Static methods
def inspect(input_chain, load_from_disk=True, **kwargs)
-
Inspects the given chain.
input_chain
is a list of paths to certificates forming a chain. Ifload_from_disk
is set to False, certificates are expected to be provided directly (as their contents), instead of the paths.kwargs
are other, unexpected arguments.The result of chain inspection is (for compatibility) returned as a single string in a list.
Expand source code
@staticmethod def inspect(input_chain, load_from_disk=True, **kwargs): """ Inspects the given chain. `input_chain` is a list of paths to certificates forming a chain. If `load_from_disk` is set to False, certificates are expected to be provided directly (as their contents), instead of the paths. `kwargs` are other, unexpected arguments. The result of chain inspection is (for compatibility) returned as a single string in a list. """ input_chain = list(input_chain) try: if load_from_disk: for i, certificate_path in enumerate(input_chain): with open(certificate_path) as input_file: input_chain[i] = input_file.read().encode() original_certificate_chain = [crypto.load_certificate(crypto.FILETYPE_PEM, certificate_content) for certificate_content in input_chain] original_pairs = ChainInspector.__get_subject_issuer_pairs(original_certificate_chain) if len(original_pairs) <= 8: result = "DISCONNECTED" if ChainInspector.__is_chain_continuous(original_pairs): result = "OK" elif ChainInspector.__is_chain_continuous(list(reversed(original_pairs))): result = "REVERSED" else: for pair_permutation in list(itertools.permutations(original_pairs)): if ChainInspector.__is_chain_continuous(list(pair_permutation)): result = "REORDERED" break else: result = "TOOLONG" result += "-{0}CA".format(ChainInspector.__get_ca_certificate_count(original_pairs)) except Exception: result = "ERROR" return [result]