def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) self._pool = CertificatePool() self._pool.load_sources(self._args.crtsource) self._log.debug("Loaded a total of %d unique certificates in trust store.", self._pool.certificate_count) if self._args.format is not None: file_format = self._args.format else: file_format = os.path.splitext(self._args.outfile)[1].lstrip(".") self._active_substitutions = { name: getattr(self, "_substitute_" + name) for name in self.get_supported_substitutions() } self._get_cert_attributes = getattr(self, "_get_cert_attributes_" + self._args.color_scheme, None) if self._get_cert_attributes is None: raise LazyDeveloperException("Color scheme '%s' is unsupported. This is a bug." % (self._args.color_scheme)) # Load color palettes self._palette_traffic = AdvancedColorPalette(JSONTools.load_internal("x509sak.data", "palettes.json")["traffic"]) self._palette_flatui = AdvancedColorPalette(JSONTools.load_internal("x509sak.data", "palettes.json")["flatui"]) if file_format == "dot": with open(self._args.outfile, "w") as dotfile: self._write_dotfile(dotfile) elif file_format in [ "ps", "png", "pdf" ]: with tempfile.NamedTemporaryFile("w", prefix = "graph_", suffix = ".dot") as dotfile, open(self._args.outfile, "wb") as outfile: self._write_dotfile(dotfile) cmd = [ "dot", "-T%s" % (file_format), dotfile.name ] subprocess.check_call(cmd, stdout = outfile) else: raise UnknownFormatException("Unknown file format: \"%s\"" % (file_format))
def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) self._pool = CertificatePool() self._pool.load_sources(self._args.crtsource) self._log.debug( "Loaded a total of %d unique certificates in trust store.", self._pool.certificate_count) for cert in sorted(self._pool): if self._cert_matches(cert): cert.dump_pem()
class ActionFindCert(BaseAction): def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) self._pool = CertificatePool() self._pool.load_sources(self._args.crtsource) self._log.debug( "Loaded a total of %d unique certificates in trust store.", self._pool.certificate_count) for cert in sorted(self._pool): if self._cert_matches(cert): cert.dump_pem() def _cert_matches(self, cert): if self._args.hashval is not None: if not cert.hashval.hex().startswith(self._args.hashval): return False return True
def test_partial_chain(self): pool = CertificatePool() server_cert = self._load_crt("ok/johannes-bauer.com") intermediate_cert = self._load_crt("ok/johannes-bauer-intermediate") pool.add_certificate(server_cert) pool.add_certificate(intermediate_cert) chain = pool.find_chain(server_cert) self.assertEqual(chain.root, None) self.assertEqual(chain.chain, (intermediate_cert, )) self.assertEqual(chain.leaf, server_cert) self.assertEqual(chain.full_chain, (server_cert, intermediate_cert))
def test_empty_pool(self): pool = CertificatePool() server_cert = self._load_crt("ok/johannes-bauer.com") intermediate_cert = self._load_crt("ok/johannes-bauer-intermediate") root_cert = self._load_crt("ok/johannes-bauer-root") chain = pool.find_chain(server_cert) self.assertEqual(chain.root, None) self.assertEqual(chain.chain, tuple()) self.assertEqual(chain.leaf, server_cert) self.assertEqual(chain.full_chain, (server_cert, )) chain = pool.find_chain(intermediate_cert) self.assertEqual(chain.root, None) self.assertEqual(chain.chain, tuple()) self.assertEqual(chain.leaf, intermediate_cert) self.assertEqual(chain.full_chain, (intermediate_cert, )) chain = pool.find_chain(root_cert) self.assertEqual(chain.root, root_cert) self.assertEqual(chain.chain, tuple()) self.assertEqual(chain.leaf, root_cert) self.assertEqual(chain.full_chain, (root_cert, ))
def test_fullchain(self): pool = CertificatePool() server_cert = self._load_crt("ok/johannes-bauer.com") intermediate_cert = self._load_crt("ok/johannes-bauer-intermediate") root_cert = self._load_crt("ok/johannes-bauer-root") pool.add_certificate(server_cert) pool.add_certificate(intermediate_cert) pool.add_certificate(root_cert) self.assertEqual( list(pool.find_issuers(server_cert))[0], intermediate_cert) self.assertEqual( list(pool.find_issuers(intermediate_cert))[0], root_cert) self.assertEqual(list(pool.find_issuers(root_cert))[0], root_cert) chain = pool.find_chain(server_cert) self.assertEqual(chain.root, root_cert) self.assertEqual(chain.chain, (intermediate_cert, )) self.assertEqual(chain.leaf, server_cert) self.assertEqual(chain.full_chain, (server_cert, intermediate_cert, root_cert))
def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) if (self._args.private_key is not None) and (self._args.outform != "pkcs12"): raise InvalidUsageException( "A private key will only be exported with a PKCS#12 output file." ) if self._args.outform != "pkcs12": if self._args.pkcs12_legacy_crypto: raise InvalidUsageException( "Specifying PKCS#12 legacy crypto while not using PKCS#12 output format has no effect." ) if self._args.pkcs12_no_passphrase: raise InvalidUsageException( "Specifying no PKCS#12 passphrase while not using PKCS#12 output format has no effect." ) if self._args.pkcs12_passphrase_file is not None: raise InvalidUsageException( "Specifying a PKCS#12 passphrase while not using PKCS#12 output format has no effect." ) else: if (self._args.pkcs12_legacy_crypto or self._args.pkcs12_no_passphrase or self._args.pkcs12_passphrase_file is not None) and (self._args.private_key is None): raise InvalidUsageException( "Specifying any PKCS#12 passphrase/crypto options when not including a private key makes no sense." ) self._pool = CertificatePool() self._load_truststore() if self._args.inform == "pem": certs = X509Certificate.read_pemfile(self._args.crtfile) if not self._args.dont_trust_crtfile: self._pool.add_certificates(certs) cert = certs[0] elif self._args.inform == "der": cert = X509Certificate.read_derfile(self._args.crtfile) else: raise NotImplementedError("inform", self._args.inform) chain = self._pool.find_chain(cert) if (chain.root is None) and (not self._args.allow_partial_chain): raise InvalidUsageException( "Could not build full chain for certificate %s and partial chain matches are disallowed." % (self._args.crtfile)) if self._args.outform == "rootonly": if chain.root is None: print("Root certificate output requested, but none found.", file=sys.stderr) sys.exit(1) certs = [chain.root] elif self._args.outform == "intermediates": certs = list(chain.chain) elif self._args.outform in ["fullchain", "multifile", "pkcs12"]: certs = list(chain.full_chain) elif self._args.outform == "all-except-root": certs = list(chain.full_chain) if chain.root is not None: certs = certs[:-1] else: raise NotImplementedError("outform", self._args.outform) if not self._args.order_leaf_to_root: certs = certs[::-1] for (cid, cert) in enumerate(certs): self._log.debug("Cert %d: %s", cid, cert) if self._args.outform == "multifile": for (cid, cert) in enumerate(certs): filename = self._args.outfile % (cid) with open(filename, "w") as f: print(cert.to_pem_data(), file=f) elif self._args.outform == "pkcs12": if self._args.private_key is not None: if self._args.pkcs12_no_passphrase: passphrase = None else: if self._args.pkcs12_passphrase_file is not None: with open(self._args.pkcs12_passphrase_file) as f: passphrase = f.readline() else: passphrase = PassphraseGenerator.non_ambiguous( ).gen_passphrase(80) print("Passphrase: %s" % (passphrase), file=sys.stderr) else: passphrase = None private_key_storage = PrivateKeyStorage( PrivateKeyStorageForm.PEM_FILE, filename=self._args.private_key) if (self._args.private_key is not None) else None pkcs12 = OpenSSLTools.create_pkcs12( certificates=certs, private_key_storage=private_key_storage, modern_crypto=not self._args.pkcs12_legacy_crypto, passphrase=passphrase) if self._args.outfile is None: # Write to stdout sys.stdout.buffer.write(pkcs12) else: # Write to file, binary with open(self._args.outfile, "wb") as f: f.write(pkcs12) else: if self._args.outfile is not None: with open(self._args.outfile, "w") as f: self._print_certs(f, certs) else: self._print_certs(sys.stdout, certs)
class ActionGraphPool(BaseAction): _NodeAttributes = collections.namedtuple("NodeAttributes", [ "shape", "stroke_color", "fill_color", "text_color" ]) _DefaultNodeAttributes = _NodeAttributes(shape = "box", stroke_color = "#000000", fill_color = None, text_color = "#000000") _NodeAttributeMap = { "stroke_color": "color", "fill_color": "fillcolor", "text_color": "fontcolor", } def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) self._pool = CertificatePool() self._pool.load_sources(self._args.crtsource) self._log.debug("Loaded a total of %d unique certificates in trust store.", self._pool.certificate_count) if self._args.format is not None: file_format = self._args.format else: file_format = os.path.splitext(self._args.outfile)[1].lstrip(".") self._active_substitutions = { name: getattr(self, "_substitute_" + name) for name in self.get_supported_substitutions() } self._get_cert_attributes = getattr(self, "_get_cert_attributes_" + self._args.color_scheme, None) if self._get_cert_attributes is None: raise LazyDeveloperException("Color scheme '%s' is unsupported. This is a bug." % (self._args.color_scheme)) # Load color palettes self._palette_traffic = AdvancedColorPalette(JSONTools.load_internal("x509sak.data", "palettes.json")["traffic"]) self._palette_flatui = AdvancedColorPalette(JSONTools.load_internal("x509sak.data", "palettes.json")["flatui"]) if file_format == "dot": with open(self._args.outfile, "w") as dotfile: self._write_dotfile(dotfile) elif file_format in [ "ps", "png", "pdf" ]: with tempfile.NamedTemporaryFile("w", prefix = "graph_", suffix = ".dot") as dotfile, open(self._args.outfile, "wb") as outfile: self._write_dotfile(dotfile) cmd = [ "dot", "-T%s" % (file_format), dotfile.name ] subprocess.check_call(cmd, stdout = outfile) else: raise UnknownFormatException("Unknown file format: \"%s\"" % (file_format)) def _write_dotfile(self, dotfile): def escape(text): text = text.replace("\\", "\\\\") text = text.replace("\"", "\\\"") return text print("digraph G {", file = dotfile) print(" node [ fontname=\"Arial\" ];", file = dotfile) for cert in sorted(self._pool): attributes = self._get_cert_attributes(cert) attributes = self._NodeAttributes(*[concrete if (concrete is not None) else default for (concrete, default) in zip(attributes, self._DefaultNodeAttributes) ]) attributes = { self._NodeAttributeMap.get(key, key): value for (key, value) in attributes._asdict().items() if (value is not None) } if "fillcolor" in attributes: attributes["style"] = "filled" label = self._args.label if (len(self._args.label) > 0) else self.get_default_label() subs = self._all_substitutions(cert) label = [ line % subs for line in label ] label = [ TextTools.abbreviate(line, to_length = self._args.abbreviate_to) for line in label ] attributes["label"] = "\\n".join(escape(line) for line in label) node_name = "crt_" + cert.hashval.hex() print(" %s [ %s ];" % (node_name, ", ".join("%s = \"%s\"" % (key, value) for (key, value) in attributes.items())), file = dotfile) issuers = self._pool.find_issuers(cert) for issuer in issuers: issuer_node_name = "crt_" + issuer.hashval.hex() print(" %s -> %s;" % (issuer_node_name, node_name), file = dotfile) print("}", file = dotfile) dotfile.flush() def _get_cert_attributes_expiration(self, crt): now = datetime.datetime.utcnow() if now < crt.valid_not_before: # Not valid yet return self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("peter-river"), stroke_color = None, text_color = None) elif now > crt.valid_not_after: # Already expired return self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("pomegranate"), stroke_color = None, text_color = None) else: still_valid_days = ((crt.valid_not_after - now).total_seconds()) / 86400 coefficient = still_valid_days / 100 color = self._palette_traffic.get_hex_color(coefficient) return self._NodeAttributes(shape = None, fill_color = color, stroke_color = None, text_color = None) def _get_cert_attributes_certtype(self, crt): classification = crt.classify() return { X509CertificateClass.CARoot: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("sun-flower"), stroke_color = None, text_color = None), X509CertificateClass.CAIntermediate: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("orange"), stroke_color = None, text_color = None), X509CertificateClass.ClientServerAuth: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("amethyst"), stroke_color = None, text_color = None), X509CertificateClass.ClientAuth: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("emerland"), stroke_color = None, text_color = None), X509CertificateClass.ServerAuth: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("peter-river"), stroke_color = None, text_color = None), }.get(classification, self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("concrete"), stroke_color = None, text_color = None)) def _color_cryptosystem(self, cryptosystem): return { Cryptosystems.RSA: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("peter-river"), stroke_color = None, text_color = None), Cryptosystems.ECC_ECDSA: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("emerland"), stroke_color = None, text_color = None), Cryptosystems.ECC_EdDSA: self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("orange"), stroke_color = None, text_color = None), }.get(cryptosystem, self._NodeAttributes(shape = None, fill_color = self._palette_flatui.get_hex_color("concrete"), stroke_color = None, text_color = None)) def _get_cert_attributes_keytype(self, crt): return self._color_cryptosystem(crt.pubkey.pk_alg.value.cryptosystem) def _get_cert_attributes_sigtype(self, crt): signature_function = crt.signature_algorithm.value.sig_fnc.value cryptosystem = signature_function.cryptosystem return self._color_cryptosystem(cryptosystem) def _substitute_derhash(self, crt): return crt.hashval.hex()[:8] def _substitute_filename(self, crt): return crt.source def _substitute_filebasename(self, crt): return os.path.basename(crt.source) def _substitute_subject(self, crt): return crt.subject.pretty_str def _substitute_subject_rfc2253(self, crt): return crt.subject.rfc2253_str def _substitute_valid_not_after(self, crt): return crt.valid_not_after.strftime("%Y-%m-%d") def _all_substitutions(self, crt): return { name: handler(crt) for (name, handler) in self._active_substitutions.items() } @classmethod def get_supported_substitutions(cls): for methodname in dir(cls): if methodname.startswith("_substitute_"): yield methodname[12:] @classmethod def get_supported_colorschemes(cls): for methodname in dir(cls): if methodname.startswith("_get_cert_attributes_"): yield methodname[21:] @classmethod def get_default_label(cls): return [ "%(filebasename)s (%(derhash)s)", "%(subject)s", "%(valid_not_after)s", ]