mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-03-26 21:33:25 +00:00
* Enable basic type checking. * Fix first errors. * Add changelog fragment. * Add types to module_utils and plugin_utils (without module backends). * Add typing hints for acme_* modules. * Add typing to X.509 certificate modules, and add more helpers. * Add typing to remaining module backends. * Add typing for action, filter, and lookup plugins. * Bump ansible-core 2.19 beta requirement for typing. * Add more typing definitions. * Add typing to some unit tests.
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
|
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
import abc
|
|
import typing as t
|
|
|
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
|
ModuleFailException,
|
|
)
|
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
|
der_to_pem,
|
|
process_links,
|
|
)
|
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
|
split_pem_list,
|
|
)
|
|
|
|
|
|
if t.TYPE_CHECKING:
|
|
from .acme import ACMEClient
|
|
|
|
|
|
_CertificateChain = t.TypeVar("_CertificateChain", bound="CertificateChain")
|
|
|
|
|
|
class CertificateChain:
|
|
"""
|
|
Download and parse the certificate chain.
|
|
https://tools.ietf.org/html/rfc8555#section-7.4.2
|
|
"""
|
|
|
|
def __init__(self, url: str):
|
|
self.url = url
|
|
self.cert: str | None = None
|
|
self.chain: list[str] = []
|
|
self.alternates: list[str] = []
|
|
|
|
@classmethod
|
|
def download(
|
|
cls: t.Type[_CertificateChain], client: ACMEClient, url: str
|
|
) -> _CertificateChain:
|
|
content, info = client.get_request(
|
|
url,
|
|
parse_json_result=False,
|
|
headers={"Accept": "application/pem-certificate-chain"},
|
|
)
|
|
|
|
if not content or not info["content-type"].startswith(
|
|
"application/pem-certificate-chain"
|
|
):
|
|
raise ModuleFailException(
|
|
f"Cannot download certificate chain from {url}, as content type is not application/pem-certificate-chain: {content!r} (headers: {info})"
|
|
)
|
|
|
|
result = cls(url)
|
|
|
|
# Parse data
|
|
certs = split_pem_list(content.decode("utf-8"), keep_inbetween=True)
|
|
if certs:
|
|
result.cert = certs[0]
|
|
result.chain = certs[1:]
|
|
|
|
process_links(
|
|
info, lambda link, relation: result._process_links(client, link, relation)
|
|
)
|
|
|
|
if result.cert is None:
|
|
raise ModuleFailException(
|
|
f"Failed to parse certificate chain download from {url}: {content!r} (headers: {info})"
|
|
)
|
|
|
|
return result
|
|
|
|
def _process_links(self, client: ACMEClient, link: str, relation: str) -> None:
|
|
if relation == "up":
|
|
# Process link-up headers if there was no chain in reply
|
|
if not self.chain:
|
|
chain_result, chain_info = client.get_request(
|
|
link, parse_json_result=False
|
|
)
|
|
if chain_info["status"] in [200, 201]:
|
|
self.chain.append(der_to_pem(chain_result))
|
|
elif relation == "alternate":
|
|
self.alternates.append(link)
|
|
|
|
def to_json(self) -> dict[str, bytes]:
|
|
if self.cert is None:
|
|
raise ValueError("Has no certificate")
|
|
cert = self.cert.encode("utf8")
|
|
chain = ("\n".join(self.chain)).encode("utf8")
|
|
return {
|
|
"cert": cert,
|
|
"chain": chain,
|
|
"full_chain": cert + chain,
|
|
}
|
|
|
|
|
|
class Criterium:
|
|
def __init__(self, criterium: dict[str, t.Any], index: int):
|
|
self.index = index
|
|
self.test_certificates: t.Literal["first", "last", "all"] = criterium[
|
|
"test_certificates"
|
|
]
|
|
self.subject: dict[str, t.Any] | None = criterium["subject"]
|
|
self.issuer: dict[str, t.Any] | None = criterium["issuer"]
|
|
self.subject_key_identifier: str | None = criterium["subject_key_identifier"]
|
|
self.authority_key_identifier: str | None = criterium[
|
|
"authority_key_identifier"
|
|
]
|
|
|
|
|
|
class ChainMatcher(metaclass=abc.ABCMeta):
|
|
@abc.abstractmethod
|
|
def match(self, certificate: CertificateChain) -> bool:
|
|
"""
|
|
Check whether a certificate chain (CertificateChain instance) matches.
|
|
"""
|