Exemplo n.º 1
0
	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))
Exemplo n.º 2
0
    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()
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    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))
Exemplo n.º 5
0
    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, ))
Exemplo n.º 6
0
    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))
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
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",
		]