mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Work on issues found by pylint (#896)
* Look at possibly-used-before-assignment. * Use latest beta releases of ansible-core 2.19 for mypy and pylint. * Look at unsupported-*. * Look at unknown-option-value. * Look at redefined-builtin. * Look at superfluous-parens. * Look at unspecified-encoding. * Adjust to new cryptography version and to ansible-core 2.17's pylint. * Look at super-with-arguments. * Look at no-else-*. * Look at try-except-raise. * Look at inconsistent-return-statements. * Look at redefined-outer-name. * Look at redefined-argument-from-local. * Look at attribute-defined-outside-init. * Look at unused-variable. * Look at protected-access. * Look at raise-missing-from. * Look at arguments-differ. * Look at useless-suppression and use-symbolic-message-instead. * Look at consider-using-dict-items. * Look at consider-using-in. * Look at consider-using-set-comprehension. * Look at consider-using-with. * Look at use-dict-literal.
This commit is contained in:
@@ -119,7 +119,7 @@ class ACMEAccount:
|
||||
if "location" in info:
|
||||
self.client.set_account_uri(info["location"])
|
||||
return True, result
|
||||
elif info["status"] == 200:
|
||||
if info["status"] == 200:
|
||||
# Account did exist
|
||||
if result.get("status") == "deactivated":
|
||||
# A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and
|
||||
@@ -130,12 +130,11 @@ class ACMEAccount:
|
||||
# requests authorized by that account's key."
|
||||
if not allow_creation:
|
||||
return False, None
|
||||
else:
|
||||
raise ModuleFailException("Account is deactivated")
|
||||
raise ModuleFailException("Account is deactivated")
|
||||
if "location" in info:
|
||||
self.client.set_account_uri(info["location"])
|
||||
return False, result
|
||||
elif (
|
||||
if (
|
||||
info["status"] in (400, 404)
|
||||
and result["type"] == "urn:ietf:params:acme:error:accountDoesNotExist"
|
||||
and not allow_creation
|
||||
@@ -144,7 +143,7 @@ class ACMEAccount:
|
||||
# (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400.
|
||||
# Unfortunately Digicert does not care and sends 404 instead.)
|
||||
return False, None
|
||||
elif (
|
||||
if (
|
||||
info["status"] == 403
|
||||
and result["type"] == "urn:ietf:params:acme:error:unauthorized"
|
||||
and "deactivated" in (result.get("detail") or "")
|
||||
@@ -154,15 +153,13 @@ class ACMEAccount:
|
||||
# might need adjustment in error detection.
|
||||
if not allow_creation:
|
||||
return False, None
|
||||
else:
|
||||
raise ModuleFailException("Account is deactivated")
|
||||
else:
|
||||
raise ACMEProtocolException(
|
||||
module=self.client.module,
|
||||
msg="Registering ACME account failed",
|
||||
info=info,
|
||||
content_json=result,
|
||||
)
|
||||
raise ModuleFailException("Account is deactivated")
|
||||
raise ACMEProtocolException(
|
||||
module=self.client.module,
|
||||
msg="Registering ACME account failed",
|
||||
info=info,
|
||||
content_json=result,
|
||||
)
|
||||
|
||||
def get_account_data(self) -> dict[str, t.Any] | None:
|
||||
"""
|
||||
|
||||
@@ -244,7 +244,9 @@ class ACMEClient:
|
||||
passphrase=self.account_key_passphrase,
|
||||
)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException(f"Error while parsing account key: {e.msg}")
|
||||
raise ModuleFailException(
|
||||
f"Error while parsing account key: {e.msg}"
|
||||
) from e
|
||||
self.account_jwk = self.account_key_data["jwk"]
|
||||
self.account_jws_header = {
|
||||
"alg": self.account_key_data["alg"],
|
||||
@@ -307,7 +309,7 @@ class ACMEClient:
|
||||
except Exception as e:
|
||||
raise ModuleFailException(
|
||||
f"Failed to encode payload / headers as JSON: {e}"
|
||||
)
|
||||
) from e
|
||||
|
||||
return self.backend.sign(
|
||||
payload64=payload64, protected64=protected64, key_data=key_data
|
||||
@@ -456,10 +458,10 @@ class ACMEClient:
|
||||
result = decoded_result
|
||||
else:
|
||||
result = content
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
raise NetworkException(
|
||||
f"Failed to parse the ACME response: {url} {content}"
|
||||
)
|
||||
) from exc
|
||||
else:
|
||||
result = content
|
||||
|
||||
@@ -569,10 +571,10 @@ class ACMEClient:
|
||||
try:
|
||||
result = self.module.from_json(content.decode("utf8"))
|
||||
parsed_json_result = True
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
raise NetworkException(
|
||||
f"Failed to parse the ACME response: {uri} {content!r}"
|
||||
)
|
||||
) from exc
|
||||
else:
|
||||
result = content
|
||||
else:
|
||||
@@ -642,30 +644,32 @@ def create_default_argspec(
|
||||
Provides default argument spec for the options documented in the acme doc fragment.
|
||||
"""
|
||||
result = ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
acme_directory=dict(type="str", required=True),
|
||||
acme_version=dict(type="int", choices=[2], default=2),
|
||||
validate_certs=dict(type="bool", default=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "openssl", "cryptography"]
|
||||
),
|
||||
request_timeout=dict(type="int", default=10),
|
||||
),
|
||||
argument_spec={
|
||||
"acme_directory": {"type": "str", "required": True},
|
||||
"acme_version": {"type": "int", "choices": [2], "default": 2},
|
||||
"validate_certs": {"type": "bool", "default": True},
|
||||
"select_crypto_backend": {
|
||||
"type": "str",
|
||||
"default": "auto",
|
||||
"choices": ["auto", "openssl", "cryptography"],
|
||||
},
|
||||
"request_timeout": {"type": "int", "default": 10},
|
||||
},
|
||||
)
|
||||
if with_account:
|
||||
result.update_argspec(
|
||||
account_key_src=dict(type="path", aliases=["account_key"]),
|
||||
account_key_content=dict(type="str", no_log=True),
|
||||
account_key_passphrase=dict(type="str", no_log=True),
|
||||
account_uri=dict(type="str"),
|
||||
account_key_src={"type": "path", "aliases": ["account_key"]},
|
||||
account_key_content={"type": "str", "no_log": True},
|
||||
account_key_passphrase={"type": "str", "no_log": True},
|
||||
account_uri={"type": "str"},
|
||||
)
|
||||
if require_account_key:
|
||||
result.update(required_one_of=[["account_key_src", "account_key_content"]])
|
||||
result.update(mutually_exclusive=[["account_key_src", "account_key_content"]])
|
||||
if with_certificate:
|
||||
result.update_argspec(
|
||||
csr=dict(type="path"),
|
||||
csr_content=dict(type="str"),
|
||||
csr={"type": "path"},
|
||||
csr_content={"type": "str"},
|
||||
)
|
||||
result.update(
|
||||
required_one_of=[["csr", "csr_content"]],
|
||||
|
||||
@@ -211,9 +211,7 @@ class CryptographyChainMatcher(ChainMatcher):
|
||||
|
||||
class CryptographyBackend(CryptoBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(CryptographyBackend, self).__init__(
|
||||
module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE
|
||||
)
|
||||
super().__init__(module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
|
||||
def parse_key(
|
||||
self,
|
||||
@@ -242,7 +240,7 @@ class CryptographyBackend(CryptoBackend):
|
||||
password=to_bytes(passphrase) if passphrase is not None else None,
|
||||
)
|
||||
except Exception as e:
|
||||
raise KeyParsingError(f"error while loading key: {e}")
|
||||
raise KeyParsingError(f"error while loading key: {e}") from e
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
rsa_pk = key.public_key().public_numbers()
|
||||
return {
|
||||
@@ -256,7 +254,7 @@ class CryptographyBackend(CryptoBackend):
|
||||
},
|
||||
"hash": "sha256",
|
||||
}
|
||||
elif isinstance(
|
||||
if isinstance(
|
||||
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
|
||||
):
|
||||
ec_pk = key.public_key().public_numbers()
|
||||
@@ -296,8 +294,7 @@ class CryptographyBackend(CryptoBackend):
|
||||
"hash": hashalg,
|
||||
"point_size": point_size,
|
||||
}
|
||||
else:
|
||||
raise KeyParsingError(f'unknown key type "{type(key)}"')
|
||||
raise KeyParsingError(f'unknown key type "{type(key)}"')
|
||||
|
||||
def sign(
|
||||
self, *, payload64: str, protected64: str, key_data: dict[str, t.Any]
|
||||
@@ -332,6 +329,8 @@ class CryptographyBackend(CryptoBackend):
|
||||
rr = convert_int_to_hex(r, digits=2 * key_data["point_size"])
|
||||
ss = convert_int_to_hex(s, digits=2 * key_data["point_size"])
|
||||
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
|
||||
else:
|
||||
raise AssertionError("Can never be reached") # pragma: no cover
|
||||
|
||||
return {
|
||||
"protected": protected64,
|
||||
@@ -472,8 +471,10 @@ class CryptographyBackend(CryptoBackend):
|
||||
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
||||
except Exception as e:
|
||||
if cert_filename is None:
|
||||
raise BackendException(f"Cannot parse certificate: {e}")
|
||||
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
|
||||
raise BackendException(f"Cannot parse certificate: {e}") from e
|
||||
raise BackendException(
|
||||
f"Cannot parse certificate {cert_filename}: {e}"
|
||||
) from e
|
||||
|
||||
if now is None:
|
||||
now = self.get_now()
|
||||
@@ -508,8 +509,10 @@ class CryptographyBackend(CryptoBackend):
|
||||
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
||||
except Exception as e:
|
||||
if cert_filename is None:
|
||||
raise BackendException(f"Cannot parse certificate: {e}")
|
||||
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
|
||||
raise BackendException(f"Cannot parse certificate: {e}") from e
|
||||
raise BackendException(
|
||||
f"Cannot parse certificate {cert_filename}: {e}"
|
||||
) from e
|
||||
|
||||
ski = None
|
||||
try:
|
||||
|
||||
@@ -45,7 +45,12 @@ if t.TYPE_CHECKING:
|
||||
)
|
||||
|
||||
|
||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C")
|
||||
_OPENSSL_ENVIRONMENT_UPDATE = {
|
||||
"LANG": "C",
|
||||
"LC_ALL": "C",
|
||||
"LC_MESSAGES": "C",
|
||||
"LC_CTYPE": "C",
|
||||
}
|
||||
|
||||
|
||||
def _extract_date(
|
||||
@@ -66,7 +71,7 @@ def _extract_date(
|
||||
except ValueError as exc:
|
||||
raise BackendException(
|
||||
f"Failed to parse '{name}' date{cert_filename_suffix}: {exc}"
|
||||
)
|
||||
) from exc
|
||||
|
||||
|
||||
def _decode_octets(octets_text: str) -> bytes:
|
||||
@@ -118,7 +123,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
def __init__(
|
||||
self, *, module: AnsibleModule, openssl_binary: str | None = None
|
||||
) -> None:
|
||||
super(OpenSSLCLIBackend, self).__init__(module=module, with_timezone=True)
|
||||
super().__init__(module=module, with_timezone=True)
|
||||
if openssl_binary is None:
|
||||
openssl_binary = module.get_bin_path("openssl", True)
|
||||
self.openssl_binary = openssl_binary
|
||||
@@ -156,11 +161,11 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
raise KeyParsingError(
|
||||
f"failed to create temporary content file: {err}",
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
) from err
|
||||
f.close()
|
||||
# Parse key
|
||||
account_key_type = None
|
||||
with open(key_file, "rt") as fi:
|
||||
with open(key_file, "r", encoding="utf-8") as fi:
|
||||
for line in fi:
|
||||
m = re.match(
|
||||
r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line
|
||||
@@ -228,7 +233,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||
},
|
||||
"hash": "sha256",
|
||||
}
|
||||
elif account_key_type == "ec":
|
||||
if account_key_type == "ec":
|
||||
pub_data = re.search(
|
||||
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
|
||||
out_text,
|
||||
|
||||
@@ -77,14 +77,14 @@ def _parse_acme_timestamp(
|
||||
"""
|
||||
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||
timestamp_str = _reduce_fractional_digits(timestamp_str)
|
||||
for format in (
|
||||
for time_format in (
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
"%Y-%m-%dT%H:%M:%S%z",
|
||||
"%Y-%m-%dT%H:%M:%S.%f%z",
|
||||
):
|
||||
try:
|
||||
result = datetime.datetime.strptime(timestamp_str, format)
|
||||
result = datetime.datetime.strptime(timestamp_str, time_format)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
@@ -117,7 +117,7 @@ class CryptoBackend(metaclass=abc.ABCMeta):
|
||||
raise BackendException(f"Invalid value for {name}: {value!r}")
|
||||
return result
|
||||
except OpenSSLObjectError as exc:
|
||||
raise BackendException(str(exc))
|
||||
raise BackendException(str(exc)) from exc
|
||||
|
||||
def interpolate_timestamp(
|
||||
self,
|
||||
|
||||
@@ -166,11 +166,11 @@ class ACMECertificateClient:
|
||||
continue
|
||||
challenge_data = authz.get_challenge_data(client=self.client)
|
||||
data.append(
|
||||
dict(
|
||||
identifier=authz.identifier,
|
||||
identifier_type=authz.identifier_type,
|
||||
challenges=challenge_data,
|
||||
)
|
||||
{
|
||||
"identifier": authz.identifier,
|
||||
"identifier_type": authz.identifier_type,
|
||||
"challenges": challenge_data,
|
||||
}
|
||||
)
|
||||
dns_challenge = challenge_data.get(dns_challenge_type)
|
||||
if dns_challenge:
|
||||
@@ -321,7 +321,7 @@ class ACMECertificateClient:
|
||||
"""
|
||||
if self.csr is None and self.csr_content is None:
|
||||
raise ModuleFailException("No CSR has been provided")
|
||||
for identifier, authz in order.authorizations.items():
|
||||
for authz in order.authorizations.values():
|
||||
if authz.status != "valid":
|
||||
authz.raise_error(
|
||||
error_msg=f'Status is {authz.status!r} and not "valid"',
|
||||
|
||||
@@ -71,7 +71,7 @@ class CertificateChain:
|
||||
|
||||
process_links(
|
||||
info=info,
|
||||
callback=lambda link, relation: result._process_links(
|
||||
callback=lambda link, relation: result._process_links( # pylint: disable=protected-access
|
||||
client=client, link=link, relation=relation
|
||||
),
|
||||
)
|
||||
|
||||
@@ -295,10 +295,10 @@ class Authorization:
|
||||
raise ACMEProtocolException(
|
||||
module=module,
|
||||
msg=f"Failed to validate challenge for {self.combined_identifier}: {error_msg}. {'; '.join(error_details)}",
|
||||
extras=dict(
|
||||
identifier=self.combined_identifier,
|
||||
authorization=self.data,
|
||||
),
|
||||
extras={
|
||||
"identifier": self.combined_identifier,
|
||||
"authorization": self.data,
|
||||
},
|
||||
)
|
||||
|
||||
def find_challenge(self, *, challenge_type: str) -> Challenge | None:
|
||||
@@ -374,7 +374,7 @@ class Authorization:
|
||||
"""
|
||||
authz = cls(url=url)
|
||||
authz_deactivate = {"status": "deactivated"}
|
||||
result, info = client.send_signed_request(
|
||||
result, _info = client.send_signed_request(
|
||||
url, authz_deactivate, fail_on_error=True
|
||||
)
|
||||
authz._setup(client=client, data=result)
|
||||
|
||||
@@ -40,12 +40,12 @@ def format_error_problem(
|
||||
subproblems = problem.get("subproblems")
|
||||
if subproblems is not None:
|
||||
msg = f"{msg} Subproblems:"
|
||||
for index, problem in enumerate(subproblems):
|
||||
for index, subproblem in enumerate(subproblems):
|
||||
index_str = f"{subproblem_prefix}{index}"
|
||||
problem_str = format_error_problem(
|
||||
problem, subproblem_prefix=f"{index_str}."
|
||||
subproblem_str = format_error_problem(
|
||||
subproblem, subproblem_prefix=f"{index_str}."
|
||||
)
|
||||
msg = f"{msg}\n({index_str}) {problem_str}"
|
||||
msg = f"{msg}\n({index_str}) {subproblem_str}"
|
||||
return msg
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class ModuleFailException(Exception):
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str, **args: t.Any) -> None:
|
||||
super(ModuleFailException, self).__init__(self, msg)
|
||||
super().__init__(self, msg)
|
||||
self.msg = msg
|
||||
self.module_fail_args = args
|
||||
|
||||
@@ -100,7 +100,7 @@ class ACMEProtocolException(ModuleFailException):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
extras = extras or dict()
|
||||
extras = extras or {}
|
||||
error_code = None
|
||||
error_type = None
|
||||
|
||||
@@ -152,7 +152,7 @@ class ACMEProtocolException(ModuleFailException):
|
||||
elif content is not None:
|
||||
add_msg = f" The raw result: {to_text(content)}"
|
||||
|
||||
super(ACMEProtocolException, self).__init__(f"{msg}.{add_msg}", **extras)
|
||||
super().__init__(f"{msg}.{add_msg}", **extras)
|
||||
self.problem: dict[str, t.Any] = {}
|
||||
self.subproblems: list[dict[str, t.Any]] = []
|
||||
self.error_code = error_code
|
||||
|
||||
@@ -29,7 +29,7 @@ def read_file(fn: str | os.PathLike) -> bytes:
|
||||
with open(fn, "rb") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
raise ModuleFailException(f'Error while reading file "{fn}": {e}')
|
||||
raise ModuleFailException(f'Error while reading file "{fn}": {e}') from e
|
||||
|
||||
|
||||
# This function was adapted from an earlier version of https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/uri.py
|
||||
@@ -55,7 +55,7 @@ def write_file(
|
||||
raise ModuleFailException(
|
||||
f"failed to create temporary content file: {err}",
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
) from err
|
||||
f.close()
|
||||
checksum_src = None
|
||||
checksum_dest = None
|
||||
@@ -94,7 +94,7 @@ def write_file(
|
||||
raise ModuleFailException(
|
||||
f"failed to copy {tmpsrc} to {dest}: {err}",
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
) from err
|
||||
os.remove(tmpsrc)
|
||||
return changed
|
||||
|
||||
|
||||
@@ -60,13 +60,13 @@ def pem_to_der(
|
||||
lines = pem_content.splitlines()
|
||||
elif pem_filename is not None:
|
||||
try:
|
||||
with open(pem_filename, "rt") as f:
|
||||
with open(pem_filename, "r", encoding="utf-8") as f:
|
||||
lines = list(f)
|
||||
except Exception as err:
|
||||
raise ModuleFailException(
|
||||
f"cannot load PEM file {pem_filename}: {err}",
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
) from err
|
||||
else:
|
||||
raise ModuleFailException(
|
||||
"One of pem_filename and pem_content must be provided"
|
||||
|
||||
@@ -12,9 +12,9 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto._objects_
|
||||
)
|
||||
|
||||
|
||||
OID_LOOKUP: dict[str, str] = dict()
|
||||
NORMALIZE_NAMES: dict[str, str] = dict()
|
||||
NORMALIZE_NAMES_SHORT: dict[str, str] = dict()
|
||||
OID_LOOKUP: dict[str, str] = {}
|
||||
NORMALIZE_NAMES: dict[str, str] = {}
|
||||
NORMALIZE_NAMES_SHORT: dict[str, str] = {}
|
||||
|
||||
for dotted, names in OID_MAP.items():
|
||||
for name in names:
|
||||
|
||||
@@ -62,13 +62,13 @@ if HAS_CRYPTOGRAPHY:
|
||||
"aa_compromise": x509.ReasonFlags.aa_compromise,
|
||||
"remove_from_crl": x509.ReasonFlags.remove_from_crl,
|
||||
}
|
||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
||||
REVOCATION_REASON_MAP_INVERSE = {}
|
||||
for k, v in REVOCATION_REASON_MAP.items():
|
||||
REVOCATION_REASON_MAP_INVERSE[v] = k
|
||||
|
||||
else:
|
||||
REVOCATION_REASON_MAP = dict()
|
||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
||||
REVOCATION_REASON_MAP = {}
|
||||
REVOCATION_REASON_MAP_INVERSE = {}
|
||||
|
||||
|
||||
def cryptography_decode_revoked_certificate(
|
||||
@@ -145,7 +145,9 @@ def cryptography_get_signature_algorithm_oid_from_crl(
|
||||
except AttributeError:
|
||||
# Older cryptography versions do not have signature_algorithm_oid yet
|
||||
dotted = obj2txt(
|
||||
crl._backend._lib, crl._backend._ffi, crl._x509_crl.sig_alg.algorithm # type: ignore
|
||||
crl._backend._lib, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||
crl._backend._ffi, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||
crl._x509_crl.sig_alg.algorithm, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||
)
|
||||
return x509.oid.ObjectIdentifier(dotted)
|
||||
|
||||
|
||||
@@ -113,17 +113,17 @@ if t.TYPE_CHECKING:
|
||||
PKCS12KeyAndCertificates,
|
||||
)
|
||||
|
||||
CertificatePrivateKeyTypes = (
|
||||
CertificateIssuerPrivateKeyTypes
|
||||
| cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey
|
||||
| cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey
|
||||
)
|
||||
PublicKeyTypesWOEdwards = (
|
||||
DHPublicKey | DSAPublicKey | EllipticCurvePublicKey | RSAPublicKey
|
||||
)
|
||||
PrivateKeyTypesWOEdwards = (
|
||||
DHPrivateKey | DSAPrivateKey | EllipticCurvePrivateKey | RSAPrivateKey
|
||||
)
|
||||
CertificatePrivateKeyTypes = t.Union[
|
||||
CertificateIssuerPrivateKeyTypes,
|
||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey,
|
||||
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey,
|
||||
]
|
||||
PublicKeyTypesWOEdwards = t.Union[
|
||||
DHPublicKey, DSAPublicKey, EllipticCurvePublicKey, RSAPublicKey
|
||||
]
|
||||
PrivateKeyTypesWOEdwards = t.Union[
|
||||
DHPrivateKey, DSAPrivateKey, EllipticCurvePrivateKey, RSAPrivateKey
|
||||
]
|
||||
else:
|
||||
PublicKeyTypesWOEdwards = None
|
||||
PrivateKeyTypesWOEdwards = None
|
||||
@@ -146,14 +146,14 @@ DOTTED_OID = re.compile(r"^\d+(?:\.\d+)+$")
|
||||
def cryptography_get_extensions_from_cert(
|
||||
cert: x509.Certificate,
|
||||
) -> dict[str, dict[str, bool | str]]:
|
||||
result = dict()
|
||||
result = {}
|
||||
|
||||
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
||||
for ext in cert.extensions:
|
||||
result[ext.oid.dotted_string] = dict(
|
||||
critical=ext.critical,
|
||||
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||
)
|
||||
result[ext.oid.dotted_string] = {
|
||||
"critical": ext.critical,
|
||||
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||
}
|
||||
else:
|
||||
# Since cryptography will not give us the DER value for an extension
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
@@ -162,6 +162,9 @@ def cryptography_get_extensions_from_cert(
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||
# pylint: disable=protected-access
|
||||
|
||||
x509_obj = cert._x509 # type: ignore
|
||||
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||
@@ -175,10 +178,10 @@ def cryptography_get_extensions_from_cert(
|
||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||
backend.openssl_assert(data != backend._ffi.NULL)
|
||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der).decode("ascii"),
|
||||
)
|
||||
entry = {
|
||||
"critical": (crit == 1),
|
||||
"value": base64.b64encode(der).decode("ascii"),
|
||||
}
|
||||
try:
|
||||
oid = obj2txt(
|
||||
backend._lib,
|
||||
@@ -195,14 +198,14 @@ def cryptography_get_extensions_from_cert(
|
||||
def cryptography_get_extensions_from_csr(
|
||||
csr: x509.CertificateSigningRequest,
|
||||
) -> dict[str, dict[str, bool | str]]:
|
||||
result = dict()
|
||||
result = {}
|
||||
|
||||
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
||||
for ext in csr.extensions:
|
||||
result[ext.oid.dotted_string] = dict(
|
||||
critical=ext.critical,
|
||||
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||
)
|
||||
result[ext.oid.dotted_string] = {
|
||||
"critical": ext.critical,
|
||||
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||
}
|
||||
|
||||
else:
|
||||
# Since cryptography will not give us the DER value for an extension
|
||||
@@ -212,6 +215,9 @@ def cryptography_get_extensions_from_csr(
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||
# pylint: disable=protected-access
|
||||
|
||||
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req) # type: ignore
|
||||
extensions = backend._ffi.gc(
|
||||
extensions,
|
||||
@@ -235,10 +241,10 @@ def cryptography_get_extensions_from_csr(
|
||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||
backend.openssl_assert(data != backend._ffi.NULL)
|
||||
der: bytes = backend._ffi.buffer(data.data, data.length)[:] # type: ignore
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der).decode("ascii"),
|
||||
)
|
||||
entry = {
|
||||
"critical": (crit == 1),
|
||||
"value": base64.b64encode(der).decode("ascii"),
|
||||
}
|
||||
try:
|
||||
oid = obj2txt(
|
||||
backend._lib,
|
||||
@@ -269,13 +275,15 @@ def cryptography_oid_to_name(
|
||||
if names:
|
||||
name = names[0]
|
||||
else:
|
||||
name = oid._name
|
||||
if name == "Unknown OID":
|
||||
try:
|
||||
name = oid._name # pylint: disable=protected-access
|
||||
if name == "Unknown OID":
|
||||
name = dotted_string
|
||||
except AttributeError:
|
||||
name = dotted_string
|
||||
if short:
|
||||
return NORMALIZE_NAMES_SHORT.get(name, name)
|
||||
else:
|
||||
return NORMALIZE_NAMES.get(name, name)
|
||||
return NORMALIZE_NAMES.get(name, name)
|
||||
|
||||
|
||||
def _get_hex(bytesstr: bytes) -> str:
|
||||
@@ -393,7 +401,7 @@ def _parse_dn(name: bytes) -> list[x509.NameAttribute]:
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError(
|
||||
f"Error while parsing distinguished name {to_text(original_name)!r}: {e}"
|
||||
)
|
||||
) from e
|
||||
result.append(attribute)
|
||||
if name:
|
||||
if name[0:1] != sep or len(name) < 2:
|
||||
@@ -414,7 +422,7 @@ def cryptography_parse_relative_distinguished_name(
|
||||
except OpenSSLObjectError as e:
|
||||
raise OpenSSLObjectError(
|
||||
f"Error while parsing relative distinguished name {to_text(part)!r}: {e}"
|
||||
)
|
||||
) from e
|
||||
return cryptography.x509.RelativeDistinguishedName(names)
|
||||
|
||||
|
||||
@@ -468,7 +476,7 @@ def _adjust_idn(
|
||||
raise OpenSSLObjectError(
|
||||
f'Error while transforming part "{part}" of {what} DNS name "{value}" to {dest}.'
|
||||
f' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'
|
||||
)
|
||||
) from exc2003
|
||||
return ".".join(parts)
|
||||
|
||||
|
||||
@@ -561,7 +569,7 @@ def cryptography_get_name(
|
||||
x509.Name(reversed(_parse_dn(to_bytes(name[8:]))))
|
||||
)
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(f'Cannot parse {what} "{name}": {e}')
|
||||
raise OpenSSLObjectError(f'Cannot parse {what} "{name}": {e}') from e
|
||||
if ":" not in name:
|
||||
raise OpenSSLObjectError(
|
||||
f'Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'
|
||||
@@ -656,17 +664,17 @@ def cryptography_parse_key_usage_params(usages: t.Iterable[str]) -> dict[str, bo
|
||||
Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage().
|
||||
Raises an OpenSSLObjectError if an identifier is unknown.
|
||||
"""
|
||||
params = dict(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=False,
|
||||
crl_sign=False,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
params = {
|
||||
"digital_signature": False,
|
||||
"content_commitment": False,
|
||||
"key_encipherment": False,
|
||||
"data_encipherment": False,
|
||||
"key_agreement": False,
|
||||
"key_cert_sign": False,
|
||||
"crl_sign": False,
|
||||
"encipher_only": False,
|
||||
"decipher_only": False,
|
||||
}
|
||||
for usage in usages:
|
||||
params[_cryptography_get_keyusage(usage)] = True
|
||||
return params
|
||||
@@ -699,7 +707,7 @@ def cryptography_get_basic_constraints(
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(
|
||||
f'Cannot parse path length constraint "{v}" ({e})'
|
||||
)
|
||||
) from e
|
||||
else:
|
||||
raise OpenSSLObjectError(f'Unknown basic constraint "{constraint}"')
|
||||
return ca, path_length
|
||||
@@ -901,6 +909,9 @@ def _parse_pkcs12_35_0_0(
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||
# pylint: disable=protected-access
|
||||
|
||||
# This code basically does what load_key_and_certificates() does, but without error-checking.
|
||||
# Since load_key_and_certificates succeeded, it should not fail.
|
||||
pkcs12 = backend._ffi.gc(
|
||||
@@ -944,6 +955,9 @@ def _parse_pkcs12_legacy(
|
||||
pkcs12_bytes, passphrase
|
||||
)
|
||||
|
||||
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||
# pylint: disable=protected-access
|
||||
|
||||
friendly_name = None
|
||||
if certificate:
|
||||
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
||||
|
||||
@@ -109,7 +109,7 @@ class CertificateBackend(metaclass=abc.ABCMeta):
|
||||
result["can_parse_certificate"] = True
|
||||
return result
|
||||
except Exception:
|
||||
return dict(can_parse_certificate=False)
|
||||
return {"can_parse_certificate": False}
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_certificate(self) -> None:
|
||||
@@ -143,7 +143,7 @@ class CertificateBackend(metaclass=abc.ABCMeta):
|
||||
passphrase=self.privatekey_passphrase,
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise CertificateError(exc)
|
||||
raise CertificateError(exc) from exc
|
||||
|
||||
def _ensure_csr_loaded(self) -> None:
|
||||
"""Load the CSR into self.csr."""
|
||||
@@ -380,25 +380,28 @@ def select_backend(
|
||||
|
||||
def get_certificate_argument_spec() -> ArgumentSpec:
|
||||
return ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
provider=dict(
|
||||
type="str", choices=[]
|
||||
), # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
|
||||
force=dict(
|
||||
type="bool",
|
||||
default=False,
|
||||
),
|
||||
csr_path=dict(type="path"),
|
||||
csr_content=dict(type="str"),
|
||||
ignore_timestamps=dict(type="bool", default=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
argument_spec={
|
||||
"provider": {
|
||||
"type": "str",
|
||||
"choices": [],
|
||||
}, # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
|
||||
"force": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
},
|
||||
"csr_path": {"type": "path"},
|
||||
"csr_content": {"type": "str"},
|
||||
"ignore_timestamps": {"type": "bool", "default": True},
|
||||
"select_crypto_backend": {
|
||||
"type": "str",
|
||||
"default": "auto",
|
||||
"choices": ["auto", "cryptography"],
|
||||
},
|
||||
# General properties of a certificate
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
),
|
||||
"privatekey_path": {"type": "path"},
|
||||
"privatekey_content": {"type": "str", "no_log": True},
|
||||
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||
},
|
||||
mutually_exclusive=[
|
||||
["csr_path", "csr_content"],
|
||||
["privatekey_path", "privatekey_content"],
|
||||
|
||||
@@ -30,7 +30,7 @@ if t.TYPE_CHECKING:
|
||||
|
||||
class AcmeCertificateBackend(CertificateBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(AcmeCertificateBackend, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
self.accountkey_path: str = module.params["acme_accountkey_path"]
|
||||
self.challenge_path: str = module.params["acme_challenge_path"]
|
||||
self.use_chain: bool = module.params["acme_chain"]
|
||||
@@ -94,7 +94,7 @@ class AcmeCertificateBackend(CertificateBackend):
|
||||
self.module.run_command(command, check_rc=True)[1]
|
||||
)
|
||||
except OSError as exc:
|
||||
raise CertificateError(exc)
|
||||
raise CertificateError(exc) from exc
|
||||
|
||||
def get_certificate_data(self) -> bytes:
|
||||
"""Return bytes for self.cert."""
|
||||
@@ -103,9 +103,7 @@ class AcmeCertificateBackend(CertificateBackend):
|
||||
return self.cert_bytes
|
||||
|
||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||
result = super(AcmeCertificateBackend, self).dump(
|
||||
include_certificate=include_certificate
|
||||
)
|
||||
result = super().dump(include_certificate=include_certificate)
|
||||
result["accountkey"] = self.accountkey_path
|
||||
return result
|
||||
|
||||
@@ -128,14 +126,15 @@ class AcmeCertificateProvider(CertificateProvider):
|
||||
def add_acme_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||
argument_spec.argument_spec["provider"]["choices"].append("acme")
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
acme_accountkey_path=dict(type="path"),
|
||||
acme_challenge_path=dict(type="path"),
|
||||
acme_chain=dict(type="bool", default=False),
|
||||
acme_directory=dict(
|
||||
type="str", default="https://acme-v02.api.letsencrypt.org/directory"
|
||||
),
|
||||
)
|
||||
{
|
||||
"acme_accountkey_path": {"type": "path"},
|
||||
"acme_challenge_path": {"type": "path"},
|
||||
"acme_chain": {"type": "bool", "default": False},
|
||||
"acme_directory": {
|
||||
"type": "str",
|
||||
"default": "https://acme-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -51,13 +51,14 @@ except ImportError:
|
||||
|
||||
class EntrustCertificateBackend(CertificateBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(EntrustCertificateBackend, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
self.trackingId = None
|
||||
self.notAfter = get_relative_time_option(
|
||||
module.params["entrust_not_after"],
|
||||
input_name="entrust_not_after",
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||
)
|
||||
self.cert_bytes: bytes | None = None
|
||||
|
||||
if self.csr_content is None:
|
||||
if self.csr_path is None:
|
||||
@@ -119,7 +120,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
body["csr"] = to_native(self.csr_content)
|
||||
else:
|
||||
assert self.csr_path is not None
|
||||
with open(self.csr_path, "r") as csr_file:
|
||||
with open(self.csr_path, "r", encoding="utf-8") as csr_file:
|
||||
body["csr"] = csr_file.read()
|
||||
|
||||
body["certType"] = self.module.params["entrust_cert_type"]
|
||||
@@ -157,6 +158,8 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
|
||||
def get_certificate_data(self) -> bytes:
|
||||
"""Return bytes for self.cert."""
|
||||
if self.cert_bytes is None:
|
||||
raise AssertionError("Contract violation: cert_bytes not set")
|
||||
return self.cert_bytes
|
||||
|
||||
def needs_regeneration(
|
||||
@@ -165,7 +168,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
not_before: datetime.datetime | None = None,
|
||||
not_after: datetime.datetime | None = None,
|
||||
) -> bool:
|
||||
parent_check = super(EntrustCertificateBackend, self).needs_regeneration()
|
||||
parent_check = super().needs_regeneration()
|
||||
|
||||
try:
|
||||
cert_details = self._get_cert_details()
|
||||
@@ -176,7 +179,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
||||
|
||||
# Always issue a new certificate if the certificate is expired, suspended or revoked
|
||||
status = cert_details.get("status", False)
|
||||
if status == "EXPIRED" or status == "SUSPENDED" or status == "REVOKED":
|
||||
if status in ("EXPIRED", "SUSPENDED", "REVOKED"):
|
||||
return True
|
||||
|
||||
# If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed
|
||||
@@ -239,11 +242,11 @@ class EntrustCertificateProvider(CertificateProvider):
|
||||
def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||
argument_spec.argument_spec["provider"]["choices"].append("entrust")
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
entrust_cert_type=dict(
|
||||
type="str",
|
||||
default="STANDARD_SSL",
|
||||
choices=[
|
||||
{
|
||||
"entrust_cert_type": {
|
||||
"type": "str",
|
||||
"default": "STANDARD_SSL",
|
||||
"choices": [
|
||||
"STANDARD_SSL",
|
||||
"ADVANTAGE_SSL",
|
||||
"UC_SSL",
|
||||
@@ -255,20 +258,20 @@ def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||
"CDS_ENT_PRO",
|
||||
"SMIME_ENT",
|
||||
],
|
||||
),
|
||||
entrust_requester_email=dict(type="str"),
|
||||
entrust_requester_name=dict(type="str"),
|
||||
entrust_requester_phone=dict(type="str"),
|
||||
entrust_api_user=dict(type="str"),
|
||||
entrust_api_key=dict(type="str", no_log=True),
|
||||
entrust_api_client_cert_path=dict(type="path"),
|
||||
entrust_api_client_cert_key_path=dict(type="path", no_log=True),
|
||||
entrust_api_specification_path=dict(
|
||||
type="path",
|
||||
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||
),
|
||||
entrust_not_after=dict(type="str", default="+365d"),
|
||||
)
|
||||
},
|
||||
"entrust_requester_email": {"type": "str"},
|
||||
"entrust_requester_name": {"type": "str"},
|
||||
"entrust_requester_phone": {"type": "str"},
|
||||
"entrust_api_user": {"type": "str"},
|
||||
"entrust_api_key": {"type": "str", "no_log": True},
|
||||
"entrust_api_client_cert_path": {"type": "path"},
|
||||
"entrust_api_client_cert_key_path": {"type": "path", "no_log": True},
|
||||
"entrust_api_specification_path": {
|
||||
"type": "path",
|
||||
"default": "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||
},
|
||||
"entrust_not_after": {"type": "str", "default": "+365d"},
|
||||
}
|
||||
)
|
||||
argument_spec.required_if.append(
|
||||
(
|
||||
|
||||
@@ -70,6 +70,8 @@ TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
cert: x509.Certificate
|
||||
|
||||
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
||||
# content must be a bytes string
|
||||
self.module = module
|
||||
@@ -169,11 +171,11 @@ class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
result["signature_algorithm"] = self._get_signature_algorithm()
|
||||
subject = self._get_subject_ordered()
|
||||
issuer = self._get_issuer_ordered()
|
||||
result["subject"] = dict()
|
||||
result["subject"] = {}
|
||||
for k, v in subject:
|
||||
result["subject"][k] = v
|
||||
result["subject_ordered"] = subject
|
||||
result["issuer"] = dict()
|
||||
result["issuer"] = {}
|
||||
for k, v in issuer:
|
||||
result["issuer"][k] = v
|
||||
result["issuer_ordered"] = issuer
|
||||
@@ -249,9 +251,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
|
||||
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
||||
super(CertificateInfoRetrievalCryptography, self).__init__(
|
||||
module=module, content=content
|
||||
)
|
||||
super().__init__(module=module, content=content)
|
||||
self.name_encoding = module.params.get("name_encoding", "ignore")
|
||||
|
||||
def _get_der_bytes(self) -> bytes:
|
||||
@@ -289,36 +289,36 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
x509.KeyUsage
|
||||
)
|
||||
current_key_usage = current_key_ext.value
|
||||
key_usage = dict(
|
||||
digital_signature=current_key_usage.digital_signature,
|
||||
content_commitment=current_key_usage.content_commitment,
|
||||
key_encipherment=current_key_usage.key_encipherment,
|
||||
data_encipherment=current_key_usage.data_encipherment,
|
||||
key_agreement=current_key_usage.key_agreement,
|
||||
key_cert_sign=current_key_usage.key_cert_sign,
|
||||
crl_sign=current_key_usage.crl_sign,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
key_usage = {
|
||||
"digital_signature": current_key_usage.digital_signature,
|
||||
"content_commitment": current_key_usage.content_commitment,
|
||||
"key_encipherment": current_key_usage.key_encipherment,
|
||||
"data_encipherment": current_key_usage.data_encipherment,
|
||||
"key_agreement": current_key_usage.key_agreement,
|
||||
"key_cert_sign": current_key_usage.key_cert_sign,
|
||||
"crl_sign": current_key_usage.crl_sign,
|
||||
"encipher_only": False,
|
||||
"decipher_only": False,
|
||||
}
|
||||
if key_usage["key_agreement"]:
|
||||
key_usage.update(
|
||||
dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only,
|
||||
)
|
||||
{
|
||||
"encipher_only": current_key_usage.encipher_only,
|
||||
"decipher_only": current_key_usage.decipher_only,
|
||||
}
|
||||
)
|
||||
|
||||
key_usage_names = dict(
|
||||
digital_signature="Digital Signature",
|
||||
content_commitment="Non Repudiation",
|
||||
key_encipherment="Key Encipherment",
|
||||
data_encipherment="Data Encipherment",
|
||||
key_agreement="Key Agreement",
|
||||
key_cert_sign="Certificate Sign",
|
||||
crl_sign="CRL Sign",
|
||||
encipher_only="Encipher Only",
|
||||
decipher_only="Decipher Only",
|
||||
)
|
||||
key_usage_names = {
|
||||
"digital_signature": "Digital Signature",
|
||||
"content_commitment": "Non Repudiation",
|
||||
"key_encipherment": "Key Encipherment",
|
||||
"data_encipherment": "Data Encipherment",
|
||||
"key_agreement": "Key Agreement",
|
||||
"key_cert_sign": "Certificate Sign",
|
||||
"crl_sign": "CRL Sign",
|
||||
"encipher_only": "Encipher Only",
|
||||
"decipher_only": "Decipher Only",
|
||||
}
|
||||
return (
|
||||
sorted(
|
||||
[
|
||||
|
||||
@@ -63,7 +63,7 @@ except ImportError:
|
||||
|
||||
class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(OwnCACertificateBackendCryptography, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
self.create_subject_key_identifier: t.Literal[
|
||||
"create_if_not_provided", "always_create", "never_create"
|
||||
@@ -223,7 +223,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||
not_before: datetime.datetime | None = None,
|
||||
not_after: datetime.datetime | None = None,
|
||||
) -> bool:
|
||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(
|
||||
if super().needs_regeneration(
|
||||
not_before=self.notBefore, not_after=self.notAfter
|
||||
):
|
||||
return True
|
||||
@@ -272,9 +272,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||
return False
|
||||
|
||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||
result = super(OwnCACertificateBackendCryptography, self).dump(
|
||||
include_certificate=include_certificate
|
||||
)
|
||||
result = super().dump(include_certificate=include_certificate)
|
||||
result.update(
|
||||
{
|
||||
"ca_cert": self.ca_cert_path,
|
||||
@@ -343,23 +341,23 @@ class OwnCACertificateProvider(CertificateProvider):
|
||||
def add_ownca_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||
argument_spec.argument_spec["provider"]["choices"].append("ownca")
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
ownca_path=dict(type="path"),
|
||||
ownca_content=dict(type="str"),
|
||||
ownca_privatekey_path=dict(type="path"),
|
||||
ownca_privatekey_content=dict(type="str", no_log=True),
|
||||
ownca_privatekey_passphrase=dict(type="str", no_log=True),
|
||||
ownca_digest=dict(type="str", default="sha256"),
|
||||
ownca_version=dict(type="int", default=3, choices=[3]), # not used
|
||||
ownca_not_before=dict(type="str", default="+0s"),
|
||||
ownca_not_after=dict(type="str", default="+3650d"),
|
||||
ownca_create_subject_key_identifier=dict(
|
||||
type="str",
|
||||
default="create_if_not_provided",
|
||||
choices=["create_if_not_provided", "always_create", "never_create"],
|
||||
),
|
||||
ownca_create_authority_key_identifier=dict(type="bool", default=True),
|
||||
)
|
||||
{
|
||||
"ownca_path": {"type": "path"},
|
||||
"ownca_content": {"type": "str"},
|
||||
"ownca_privatekey_path": {"type": "path"},
|
||||
"ownca_privatekey_content": {"type": "str", "no_log": True},
|
||||
"ownca_privatekey_passphrase": {"type": "str", "no_log": True},
|
||||
"ownca_digest": {"type": "str", "default": "sha256"},
|
||||
"ownca_version": {"type": "int", "default": 3, "choices": [3]}, # not used
|
||||
"ownca_not_before": {"type": "str", "default": "+0s"},
|
||||
"ownca_not_after": {"type": "str", "default": "+3650d"},
|
||||
"ownca_create_subject_key_identifier": {
|
||||
"type": "str",
|
||||
"default": "create_if_not_provided",
|
||||
"choices": ["create_if_not_provided", "always_create", "never_create"],
|
||||
},
|
||||
"ownca_create_authority_key_identifier": {"type": "bool", "default": True},
|
||||
}
|
||||
)
|
||||
argument_spec.mutually_exclusive.extend(
|
||||
[
|
||||
|
||||
@@ -59,7 +59,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
privatekey: CertificateIssuerPrivateKeyTypes
|
||||
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(SelfSignedCertificateBackendCryptography, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
self.create_subject_key_identifier: t.Literal[
|
||||
"create_if_not_provided", "always_create", "never_create"
|
||||
@@ -144,7 +144,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
critical=False,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise CertificateError(str(e))
|
||||
raise CertificateError(str(e)) from e
|
||||
|
||||
certificate = cert_builder.sign(
|
||||
private_key=self.privatekey,
|
||||
@@ -167,7 +167,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
) -> bool:
|
||||
assert self.privatekey is not None
|
||||
|
||||
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(
|
||||
if super().needs_regeneration(
|
||||
not_before=self.notBefore, not_after=self.notAfter
|
||||
):
|
||||
return True
|
||||
@@ -185,9 +185,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
||||
return False
|
||||
|
||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||
result = super(SelfSignedCertificateBackendCryptography, self).dump(
|
||||
include_certificate=include_certificate
|
||||
)
|
||||
result = super().dump(include_certificate=include_certificate)
|
||||
|
||||
if self.module.check_mode:
|
||||
result.update(
|
||||
@@ -243,21 +241,29 @@ class SelfSignedCertificateProvider(CertificateProvider):
|
||||
def add_selfsigned_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
selfsigned_version=dict(type="int", default=3, choices=[3]), # not used
|
||||
selfsigned_digest=dict(type="str", default="sha256"),
|
||||
selfsigned_not_before=dict(
|
||||
type="str", default="+0s", aliases=["selfsigned_notBefore"]
|
||||
),
|
||||
selfsigned_not_after=dict(
|
||||
type="str", default="+3650d", aliases=["selfsigned_notAfter"]
|
||||
),
|
||||
selfsigned_create_subject_key_identifier=dict(
|
||||
type="str",
|
||||
default="create_if_not_provided",
|
||||
choices=["create_if_not_provided", "always_create", "never_create"],
|
||||
),
|
||||
)
|
||||
{
|
||||
"selfsigned_version": {
|
||||
"type": "int",
|
||||
"default": 3,
|
||||
"choices": [3],
|
||||
}, # not used
|
||||
"selfsigned_digest": {"type": "str", "default": "sha256"},
|
||||
"selfsigned_not_before": {
|
||||
"type": "str",
|
||||
"default": "+0s",
|
||||
"aliases": ["selfsigned_notBefore"],
|
||||
},
|
||||
"selfsigned_not_after": {
|
||||
"type": "str",
|
||||
"default": "+3650d",
|
||||
"aliases": ["selfsigned_notAfter"],
|
||||
},
|
||||
"selfsigned_create_subject_key_identifier": {
|
||||
"type": "str",
|
||||
"default": "create_if_not_provided",
|
||||
"choices": ["create_if_not_provided", "always_create", "never_create"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -67,18 +67,18 @@ class CRLInfoRetrieval:
|
||||
self.name_encoding = module.params.get("name_encoding", "ignore")
|
||||
|
||||
def get_info(self) -> dict[str, t.Any]:
|
||||
self.crl_pem = identify_pem_format(self.content)
|
||||
crl_pem = identify_pem_format(self.content)
|
||||
try:
|
||||
if self.crl_pem:
|
||||
self.crl = x509.load_pem_x509_crl(self.content)
|
||||
if crl_pem:
|
||||
crl = x509.load_pem_x509_crl(self.content)
|
||||
else:
|
||||
self.crl = x509.load_der_x509_crl(self.content)
|
||||
crl = x509.load_der_x509_crl(self.content)
|
||||
except ValueError as e:
|
||||
self.module.fail_json(msg=f"Error while decoding CRL: {e}")
|
||||
|
||||
result: dict[str, t.Any] = {
|
||||
"changed": False,
|
||||
"format": "pem" if self.crl_pem else "der",
|
||||
"format": "pem" if crl_pem else "der",
|
||||
"last_update": None,
|
||||
"next_update": None,
|
||||
"digest": None,
|
||||
@@ -86,25 +86,24 @@ class CRLInfoRetrieval:
|
||||
"issuer": None,
|
||||
}
|
||||
|
||||
result["last_update"] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result["last_update"] = crl.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result["next_update"] = (
|
||||
self.crl.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
if self.crl.next_update
|
||||
else None
|
||||
crl.next_update.strftime(TIMESTAMP_FORMAT) if crl.next_update else None
|
||||
)
|
||||
result["digest"] = cryptography_oid_to_name(
|
||||
cryptography_get_signature_algorithm_oid_from_crl(self.crl)
|
||||
cryptography_get_signature_algorithm_oid_from_crl(crl)
|
||||
)
|
||||
issuer = []
|
||||
for attribute in self.crl.issuer:
|
||||
for attribute in crl.issuer:
|
||||
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
result["issuer_ordered"] = issuer
|
||||
result["issuer"] = {}
|
||||
issuer_dict = {}
|
||||
for k, v in issuer:
|
||||
result["issuer"][k] = v
|
||||
issuer_dict[k] = v
|
||||
result["issuer"] = issuer_dict
|
||||
if self.list_revoked_certificates:
|
||||
result["revoked_certificates"] = []
|
||||
for cert in self.crl:
|
||||
for cert in crl:
|
||||
entry = cryptography_decode_revoked_certificate(cert)
|
||||
result["revoked_certificates"].append(
|
||||
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
|
||||
|
||||
@@ -170,7 +170,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
)
|
||||
self.ordered_subject = True
|
||||
except ValueError as exc:
|
||||
raise CertificateSigningRequestError(str(exc))
|
||||
raise CertificateSigningRequestError(str(exc)) from exc
|
||||
|
||||
self.using_common_name_for_san = False
|
||||
if not self.subjectAltName and module.params["use_common_name_for_san"]:
|
||||
@@ -189,7 +189,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
except Exception as e:
|
||||
raise CertificateSigningRequestError(
|
||||
f"Cannot parse subject_key_identifier: {e}"
|
||||
)
|
||||
) from e
|
||||
|
||||
self.authority_key_identifier: bytes | None = None
|
||||
if authority_key_identifier is not None:
|
||||
@@ -200,7 +200,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
except Exception as e:
|
||||
raise CertificateSigningRequestError(
|
||||
f"Cannot parse authority_key_identifier: {e}"
|
||||
)
|
||||
) from e
|
||||
|
||||
self.existing_csr: cryptography.x509.CertificateSigningRequest | None = None
|
||||
self.existing_csr_bytes: bytes | None = None
|
||||
@@ -221,7 +221,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
result["can_parse_csr"] = True
|
||||
return result
|
||||
except Exception:
|
||||
return dict(can_parse_csr=False)
|
||||
return {"can_parse_csr": False}
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_csr(self) -> None:
|
||||
@@ -253,7 +253,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
passphrase=self.privatekey_passphrase,
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise CertificateSigningRequestError(exc)
|
||||
raise CertificateSigningRequestError(exc) from exc
|
||||
|
||||
@abc.abstractmethod
|
||||
def _check_csr(self) -> bool:
|
||||
@@ -294,10 +294,10 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
||||
# Store result
|
||||
result["csr"] = csr_bytes.decode("utf-8") if csr_bytes else None
|
||||
|
||||
result["diff"] = dict(
|
||||
before=self.diff_before,
|
||||
after=self.diff_after,
|
||||
)
|
||||
result["diff"] = {
|
||||
"before": self.diff_before,
|
||||
"after": self.diff_after,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -347,16 +347,14 @@ def parse_crl_distribution_points(
|
||||
except (OpenSSLObjectError, ValueError) as e:
|
||||
raise OpenSSLObjectError(
|
||||
f"Error while parsing CRL distribution point #{index}: {e}"
|
||||
)
|
||||
) from e
|
||||
return result
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(CertificateSigningRequestCryptographyBackend, self).__init__(
|
||||
module=module
|
||||
)
|
||||
super().__init__(module=module)
|
||||
if self.version != 1:
|
||||
module.warn(
|
||||
"The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)"
|
||||
@@ -388,7 +386,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
)
|
||||
)
|
||||
except ValueError as e:
|
||||
raise CertificateSigningRequestError(e)
|
||||
raise CertificateSigningRequestError(e) from e
|
||||
|
||||
if self.subjectAltName:
|
||||
csr = csr.add_extension(
|
||||
@@ -451,7 +449,9 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
critical=self.name_constraints_critical,
|
||||
)
|
||||
except TypeError as e:
|
||||
raise OpenSSLObjectError(f"Error while parsing name constraint: {e}")
|
||||
raise OpenSSLObjectError(
|
||||
f"Error while parsing name constraint: {e}"
|
||||
) from e
|
||||
|
||||
if self.create_subject_key_identifier:
|
||||
if not is_potential_certificate_issuer_public_key(
|
||||
@@ -556,8 +556,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
||||
if self.ordered_subject:
|
||||
return subject == current_subject
|
||||
else:
|
||||
return set(subject) == set(current_subject)
|
||||
return set(subject) == set(current_subject)
|
||||
|
||||
def _find_extension(
|
||||
extensions: cryptography.x509.Extensions, exttype: type[_ET]
|
||||
@@ -596,15 +595,14 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
)
|
||||
if not self.keyUsage:
|
||||
return current_keyusage_ext is None
|
||||
elif current_keyusage_ext is None:
|
||||
if current_keyusage_ext is None:
|
||||
return False
|
||||
params = cryptography_parse_key_usage_params(self.keyUsage)
|
||||
for param in params:
|
||||
if getattr(current_keyusage_ext.value, "_" + param) != params[param]:
|
||||
for param, value in params.items():
|
||||
# TODO: check whether getattr() with '_' prepended is really needed
|
||||
if getattr(current_keyusage_ext.value, "_" + param) != value:
|
||||
return False
|
||||
if current_keyusage_ext.critical != self.keyUsage_critical:
|
||||
return False
|
||||
return True
|
||||
return current_keyusage_ext.critical == self.keyUsage_critical
|
||||
|
||||
def _check_extenededKeyUsage(extensions: cryptography.x509.Extensions) -> bool:
|
||||
current_usages_ext = _find_extension(
|
||||
@@ -647,8 +645,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
bc_ext is not None
|
||||
and bc_ext.critical == self.basicConstraints_critical
|
||||
)
|
||||
else:
|
||||
return bc_ext is None
|
||||
return bc_ext is None
|
||||
|
||||
def _check_ocspMustStaple(extensions: cryptography.x509.Extensions) -> bool:
|
||||
tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
|
||||
@@ -662,8 +659,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
cryptography.x509.TLSFeatureType.status_request
|
||||
in tlsfeature_ext.value
|
||||
)
|
||||
else:
|
||||
return tlsfeature_ext is None
|
||||
return tlsfeature_ext is None
|
||||
|
||||
def _check_nameConstraints(extensions: cryptography.x509.Extensions) -> bool:
|
||||
current_nc_ext = _find_extension(
|
||||
@@ -722,10 +718,8 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
self.privatekey.public_key()
|
||||
).digest
|
||||
return ext.value.digest == digest
|
||||
else:
|
||||
return ext.value.digest == self.subject_key_identifier
|
||||
else:
|
||||
return ext is None
|
||||
return ext.value.digest == self.subject_key_identifier
|
||||
return ext is None
|
||||
|
||||
def _check_authority_key_identifier(
|
||||
extensions: cryptography.x509.Extensions,
|
||||
@@ -753,8 +747,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
||||
and ext.value.authority_cert_serial_number
|
||||
== self.authority_cert_serial_number
|
||||
)
|
||||
else:
|
||||
return ext is None
|
||||
return ext is None
|
||||
|
||||
def _check_crl_distribution_points(
|
||||
extensions: cryptography.x509.Extensions,
|
||||
@@ -814,77 +807,97 @@ def select_backend(
|
||||
|
||||
def get_csr_argument_spec() -> ArgumentSpec:
|
||||
return ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
digest=dict(type="str", default="sha256"),
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
version=dict(type="int", default=1, choices=[1]),
|
||||
subject=dict(type="dict"),
|
||||
subject_ordered=dict(type="list", elements="dict"),
|
||||
country_name=dict(type="str", aliases=["C", "countryName"]),
|
||||
state_or_province_name=dict(
|
||||
type="str", aliases=["ST", "stateOrProvinceName"]
|
||||
),
|
||||
locality_name=dict(type="str", aliases=["L", "localityName"]),
|
||||
organization_name=dict(type="str", aliases=["O", "organizationName"]),
|
||||
organizational_unit_name=dict(
|
||||
type="str", aliases=["OU", "organizationalUnitName"]
|
||||
),
|
||||
common_name=dict(type="str", aliases=["CN", "commonName"]),
|
||||
email_address=dict(type="str", aliases=["E", "emailAddress"]),
|
||||
subject_alt_name=dict(
|
||||
type="list", elements="str", aliases=["subjectAltName"]
|
||||
),
|
||||
subject_alt_name_critical=dict(
|
||||
type="bool", default=False, aliases=["subjectAltName_critical"]
|
||||
),
|
||||
use_common_name_for_san=dict(
|
||||
type="bool", default=True, aliases=["useCommonNameForSAN"]
|
||||
),
|
||||
key_usage=dict(type="list", elements="str", aliases=["keyUsage"]),
|
||||
key_usage_critical=dict(
|
||||
type="bool", default=False, aliases=["keyUsage_critical"]
|
||||
),
|
||||
extended_key_usage=dict(
|
||||
type="list", elements="str", aliases=["extKeyUsage", "extendedKeyUsage"]
|
||||
),
|
||||
extended_key_usage_critical=dict(
|
||||
type="bool",
|
||||
default=False,
|
||||
aliases=["extKeyUsage_critical", "extendedKeyUsage_critical"],
|
||||
),
|
||||
basic_constraints=dict(
|
||||
type="list", elements="str", aliases=["basicConstraints"]
|
||||
),
|
||||
basic_constraints_critical=dict(
|
||||
type="bool", default=False, aliases=["basicConstraints_critical"]
|
||||
),
|
||||
ocsp_must_staple=dict(
|
||||
type="bool", default=False, aliases=["ocspMustStaple"]
|
||||
),
|
||||
ocsp_must_staple_critical=dict(
|
||||
type="bool", default=False, aliases=["ocspMustStaple_critical"]
|
||||
),
|
||||
name_constraints_permitted=dict(type="list", elements="str"),
|
||||
name_constraints_excluded=dict(type="list", elements="str"),
|
||||
name_constraints_critical=dict(type="bool", default=False),
|
||||
create_subject_key_identifier=dict(type="bool", default=False),
|
||||
subject_key_identifier=dict(type="str"),
|
||||
authority_key_identifier=dict(type="str"),
|
||||
authority_cert_issuer=dict(type="list", elements="str"),
|
||||
authority_cert_serial_number=dict(type="int"),
|
||||
crl_distribution_points=dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=dict(
|
||||
full_name=dict(type="list", elements="str"),
|
||||
relative_name=dict(type="list", elements="str"),
|
||||
crl_issuer=dict(type="list", elements="str"),
|
||||
reasons=dict(
|
||||
type="list",
|
||||
elements="str",
|
||||
choices=[
|
||||
argument_spec={
|
||||
"digest": {"type": "str", "default": "sha256"},
|
||||
"privatekey_path": {"type": "path"},
|
||||
"privatekey_content": {"type": "str", "no_log": True},
|
||||
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||
"version": {"type": "int", "default": 1, "choices": [1]},
|
||||
"subject": {"type": "dict"},
|
||||
"subject_ordered": {"type": "list", "elements": "dict"},
|
||||
"country_name": {"type": "str", "aliases": ["C", "countryName"]},
|
||||
"state_or_province_name": {
|
||||
"type": "str",
|
||||
"aliases": ["ST", "stateOrProvinceName"],
|
||||
},
|
||||
"locality_name": {"type": "str", "aliases": ["L", "localityName"]},
|
||||
"organization_name": {"type": "str", "aliases": ["O", "organizationName"]},
|
||||
"organizational_unit_name": {
|
||||
"type": "str",
|
||||
"aliases": ["OU", "organizationalUnitName"],
|
||||
},
|
||||
"common_name": {"type": "str", "aliases": ["CN", "commonName"]},
|
||||
"email_address": {"type": "str", "aliases": ["E", "emailAddress"]},
|
||||
"subject_alt_name": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
"aliases": ["subjectAltName"],
|
||||
},
|
||||
"subject_alt_name_critical": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["subjectAltName_critical"],
|
||||
},
|
||||
"use_common_name_for_san": {
|
||||
"type": "bool",
|
||||
"default": True,
|
||||
"aliases": ["useCommonNameForSAN"],
|
||||
},
|
||||
"key_usage": {"type": "list", "elements": "str", "aliases": ["keyUsage"]},
|
||||
"key_usage_critical": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["keyUsage_critical"],
|
||||
},
|
||||
"extended_key_usage": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
"aliases": ["extKeyUsage", "extendedKeyUsage"],
|
||||
},
|
||||
"extended_key_usage_critical": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["extKeyUsage_critical", "extendedKeyUsage_critical"],
|
||||
},
|
||||
"basic_constraints": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
"aliases": ["basicConstraints"],
|
||||
},
|
||||
"basic_constraints_critical": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["basicConstraints_critical"],
|
||||
},
|
||||
"ocsp_must_staple": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["ocspMustStaple"],
|
||||
},
|
||||
"ocsp_must_staple_critical": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
"aliases": ["ocspMustStaple_critical"],
|
||||
},
|
||||
"name_constraints_permitted": {"type": "list", "elements": "str"},
|
||||
"name_constraints_excluded": {"type": "list", "elements": "str"},
|
||||
"name_constraints_critical": {"type": "bool", "default": False},
|
||||
"create_subject_key_identifier": {"type": "bool", "default": False},
|
||||
"subject_key_identifier": {"type": "str"},
|
||||
"authority_key_identifier": {"type": "str"},
|
||||
"authority_cert_issuer": {"type": "list", "elements": "str"},
|
||||
"authority_cert_serial_number": {"type": "int"},
|
||||
"crl_distribution_points": {
|
||||
"type": "list",
|
||||
"elements": "dict",
|
||||
"options": {
|
||||
"full_name": {"type": "list", "elements": "str"},
|
||||
"relative_name": {"type": "list", "elements": "str"},
|
||||
"crl_issuer": {"type": "list", "elements": "str"},
|
||||
"reasons": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
"choices": [
|
||||
"key_compromise",
|
||||
"ca_compromise",
|
||||
"affiliation_changed",
|
||||
@@ -894,15 +907,17 @@ def get_csr_argument_spec() -> ArgumentSpec:
|
||||
"privilege_withdrawn",
|
||||
"aa_compromise",
|
||||
],
|
||||
),
|
||||
),
|
||||
mutually_exclusive=[("full_name", "relative_name")],
|
||||
required_one_of=[("full_name", "relative_name", "crl_issuer")],
|
||||
),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
"mutually_exclusive": [("full_name", "relative_name")],
|
||||
"required_one_of": [("full_name", "relative_name", "crl_issuer")],
|
||||
},
|
||||
"select_crypto_backend": {
|
||||
"type": "str",
|
||||
"default": "auto",
|
||||
"choices": ["auto", "cryptography"],
|
||||
},
|
||||
},
|
||||
required_together=[
|
||||
["authority_cert_issuer", "authority_cert_serial_number"],
|
||||
],
|
||||
|
||||
@@ -61,6 +61,8 @@ TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CSRInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
csr: x509.CertificateSigningRequest
|
||||
|
||||
def __init__(
|
||||
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
||||
) -> None:
|
||||
@@ -129,7 +131,7 @@ class CSRInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
)
|
||||
|
||||
subject = self._get_subject_ordered()
|
||||
result["subject"] = dict()
|
||||
result["subject"] = {}
|
||||
for k, v in subject:
|
||||
result["subject"][k] = v
|
||||
result["subject_ordered"] = subject
|
||||
@@ -197,7 +199,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
def __init__(
|
||||
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
||||
) -> None:
|
||||
super(CSRInfoRetrievalCryptography, self).__init__(
|
||||
super().__init__(
|
||||
module=module, content=content, validate_signature=validate_signature
|
||||
)
|
||||
self.name_encoding: t.Literal["ignore", "idna", "unicode"] = module.params.get(
|
||||
@@ -216,36 +218,36 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
||||
try:
|
||||
current_key_ext = self.csr.extensions.get_extension_for_class(x509.KeyUsage)
|
||||
current_key_usage = current_key_ext.value
|
||||
key_usage = dict(
|
||||
digital_signature=current_key_usage.digital_signature,
|
||||
content_commitment=current_key_usage.content_commitment,
|
||||
key_encipherment=current_key_usage.key_encipherment,
|
||||
data_encipherment=current_key_usage.data_encipherment,
|
||||
key_agreement=current_key_usage.key_agreement,
|
||||
key_cert_sign=current_key_usage.key_cert_sign,
|
||||
crl_sign=current_key_usage.crl_sign,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
key_usage = {
|
||||
"digital_signature": current_key_usage.digital_signature,
|
||||
"content_commitment": current_key_usage.content_commitment,
|
||||
"key_encipherment": current_key_usage.key_encipherment,
|
||||
"data_encipherment": current_key_usage.data_encipherment,
|
||||
"key_agreement": current_key_usage.key_agreement,
|
||||
"key_cert_sign": current_key_usage.key_cert_sign,
|
||||
"crl_sign": current_key_usage.crl_sign,
|
||||
"encipher_only": False,
|
||||
"decipher_only": False,
|
||||
}
|
||||
if key_usage["key_agreement"]:
|
||||
key_usage.update(
|
||||
dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only,
|
||||
)
|
||||
{
|
||||
"encipher_only": current_key_usage.encipher_only,
|
||||
"decipher_only": current_key_usage.decipher_only,
|
||||
}
|
||||
)
|
||||
|
||||
key_usage_names = dict(
|
||||
digital_signature="Digital Signature",
|
||||
content_commitment="Non Repudiation",
|
||||
key_encipherment="Key Encipherment",
|
||||
data_encipherment="Data Encipherment",
|
||||
key_agreement="Key Agreement",
|
||||
key_cert_sign="Certificate Sign",
|
||||
crl_sign="CRL Sign",
|
||||
encipher_only="Encipher Only",
|
||||
decipher_only="Decipher Only",
|
||||
)
|
||||
key_usage_names = {
|
||||
"digital_signature": "Digital Signature",
|
||||
"content_commitment": "Non Repudiation",
|
||||
"key_encipherment": "Key Encipherment",
|
||||
"data_encipherment": "Data Encipherment",
|
||||
"key_agreement": "Key Agreement",
|
||||
"key_cert_sign": "Certificate Sign",
|
||||
"crl_sign": "CRL Sign",
|
||||
"encipher_only": "Encipher Only",
|
||||
"decipher_only": "Decipher Only",
|
||||
}
|
||||
return (
|
||||
sorted(
|
||||
[
|
||||
|
||||
@@ -265,10 +265,10 @@ class PrivateKeyBackend(metaclass=abc.ABCMeta):
|
||||
else:
|
||||
result["privatekey"] = None
|
||||
|
||||
result["diff"] = dict(
|
||||
before=self.diff_before,
|
||||
after=self.diff_after,
|
||||
)
|
||||
result["diff"] = {
|
||||
"before": self.diff_before,
|
||||
"after": self.diff_after,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
self.curves[name] = _Curve(name=name, ectype=ectype, deprecated=deprecated)
|
||||
|
||||
def __init__(self, module: GeneralAnsibleModule) -> None:
|
||||
super(PrivateKeyCryptographyBackend, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
self.curves: dict[str, _Curve] = {}
|
||||
self._add_curve("secp224r1", "SECP224R1")
|
||||
@@ -351,8 +351,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
return self.format # type: ignore
|
||||
if self.type in ("X25519", "X448", "Ed25519", "Ed448"):
|
||||
return "pkcs8"
|
||||
else:
|
||||
return "pkcs1"
|
||||
return "pkcs1"
|
||||
|
||||
def generate_private_key(self) -> None:
|
||||
"""(Re-)Generate private key."""
|
||||
@@ -427,6 +426,9 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
export_encoding = (
|
||||
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
||||
)
|
||||
else:
|
||||
# pylint does not notice that all possible values for export_format_txt have been covered.
|
||||
raise AssertionError("Can never be reached") # pragma: no cover
|
||||
except AttributeError:
|
||||
self.module.fail_json(
|
||||
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
||||
@@ -469,8 +471,8 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
raise AssertionError("existing_private_key_bytes not set")
|
||||
try:
|
||||
# Interpret bytes depending on format.
|
||||
format = identify_private_key_format(data)
|
||||
if format == "raw":
|
||||
key_format = identify_private_key_format(data)
|
||||
if key_format == "raw":
|
||||
if len(data) == 56:
|
||||
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
||||
data
|
||||
@@ -497,15 +499,13 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
data
|
||||
)
|
||||
raise PrivateKeyError("Cannot load raw key")
|
||||
else:
|
||||
return (
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
data,
|
||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||
)
|
||||
)
|
||||
|
||||
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
data,
|
||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||
)
|
||||
except Exception as e:
|
||||
raise PrivateKeyError(e)
|
||||
raise PrivateKeyError(e) from e
|
||||
|
||||
def _ensure_existing_private_key_loaded(self) -> None:
|
||||
if self.existing_private_key is None and self.has_existing():
|
||||
@@ -515,21 +515,20 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
if self.existing_private_key_bytes is None:
|
||||
raise AssertionError("existing_private_key_bytes not set")
|
||||
try:
|
||||
format = identify_private_key_format(self.existing_private_key_bytes)
|
||||
if format == "raw":
|
||||
key_format = identify_private_key_format(self.existing_private_key_bytes)
|
||||
if key_format == "raw":
|
||||
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
|
||||
# actually load the key (and return False when this fails).
|
||||
self._load_privatekey()
|
||||
# Loading the key succeeded. Only return True when no passphrase was
|
||||
# provided.
|
||||
return self.passphrase is None
|
||||
else:
|
||||
return bool(
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
self.existing_private_key_bytes,
|
||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||
)
|
||||
return bool(
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
self.existing_private_key_bytes,
|
||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -588,8 +587,8 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
||||
if self.format == "auto_ignore":
|
||||
return True
|
||||
try:
|
||||
format = identify_private_key_format(self.existing_private_key_bytes)
|
||||
return format == self._get_wanted_format()
|
||||
key_format = identify_private_key_format(self.existing_private_key_bytes)
|
||||
return key_format == self._get_wanted_format()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -603,16 +602,16 @@ def select_backend(module: GeneralAnsibleModule) -> PrivateKeyBackend:
|
||||
|
||||
def get_privatekey_argument_spec() -> ArgumentSpec:
|
||||
return ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
size=dict(type="int", default=4096),
|
||||
type=dict(
|
||||
type="str",
|
||||
default="RSA",
|
||||
choices=["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
|
||||
),
|
||||
curve=dict(
|
||||
type="str",
|
||||
choices=[
|
||||
argument_spec={
|
||||
"size": {"type": "int", "default": 4096},
|
||||
"type": {
|
||||
"type": "str",
|
||||
"default": "RSA",
|
||||
"choices": ["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
|
||||
},
|
||||
"curve": {
|
||||
"type": "str",
|
||||
"choices": [
|
||||
"secp224r1",
|
||||
"secp256k1",
|
||||
"secp256r1",
|
||||
@@ -633,32 +632,36 @@ def get_privatekey_argument_spec() -> ArgumentSpec:
|
||||
"sect571k1",
|
||||
"sect571r1",
|
||||
],
|
||||
),
|
||||
passphrase=dict(type="str", no_log=True),
|
||||
cipher=dict(type="str", default="auto"),
|
||||
format=dict(
|
||||
type="str",
|
||||
default="auto_ignore",
|
||||
choices=["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
|
||||
),
|
||||
format_mismatch=dict(
|
||||
type="str", default="regenerate", choices=["regenerate", "convert"]
|
||||
),
|
||||
select_crypto_backend=dict(
|
||||
type="str", choices=["auto", "cryptography"], default="auto"
|
||||
),
|
||||
regenerate=dict(
|
||||
type="str",
|
||||
default="full_idempotence",
|
||||
choices=[
|
||||
},
|
||||
"passphrase": {"type": "str", "no_log": True},
|
||||
"cipher": {"type": "str", "default": "auto"},
|
||||
"format": {
|
||||
"type": "str",
|
||||
"default": "auto_ignore",
|
||||
"choices": ["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
|
||||
},
|
||||
"format_mismatch": {
|
||||
"type": "str",
|
||||
"default": "regenerate",
|
||||
"choices": ["regenerate", "convert"],
|
||||
},
|
||||
"select_crypto_backend": {
|
||||
"type": "str",
|
||||
"choices": ["auto", "cryptography"],
|
||||
"default": "auto",
|
||||
},
|
||||
"regenerate": {
|
||||
"type": "str",
|
||||
"default": "full_idempotence",
|
||||
"choices": [
|
||||
"never",
|
||||
"fail",
|
||||
"partial_idempotence",
|
||||
"full_idempotence",
|
||||
"always",
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
required_if=[
|
||||
("type", "ECC", ["curve"]),
|
||||
],
|
||||
|
||||
@@ -121,7 +121,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
||||
assert self.dest_private_key_bytes is not None
|
||||
|
||||
try:
|
||||
format, self.dest_private_key = self._load_private_key(
|
||||
key_format, self.dest_private_key = self._load_private_key(
|
||||
data=self.dest_private_key_bytes,
|
||||
passphrase=self.dest_passphrase,
|
||||
current_hint=self.src_private_key,
|
||||
@@ -129,7 +129,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
return format != self.format or not cryptography_compare_private_keys(
|
||||
return key_format != self.format or not cryptography_compare_private_keys(
|
||||
self.dest_private_key, self.src_private_key
|
||||
)
|
||||
|
||||
@@ -141,7 +141,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
||||
# Implementation with using cryptography
|
||||
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
def get_private_key_data(self) -> bytes:
|
||||
"""Return bytes for self.src_private_key in output format"""
|
||||
@@ -166,6 +166,9 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
export_encoding = (
|
||||
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
||||
)
|
||||
else:
|
||||
# pylint does not notice that all possible values for self.format have been covered.
|
||||
raise AssertionError("Can never be reached") # pragma: no cover
|
||||
except AttributeError:
|
||||
self.module.fail_json(
|
||||
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
||||
@@ -208,20 +211,20 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
) -> tuple[str, PrivateKeyTypes]:
|
||||
try:
|
||||
# Interpret bytes depending on format.
|
||||
format = identify_private_key_format(data)
|
||||
if format == "raw":
|
||||
key_format = identify_private_key_format(data)
|
||||
if key_format == "raw":
|
||||
if passphrase is not None:
|
||||
raise PrivateKeyError("Cannot load raw key with passphrase")
|
||||
if len(data) == 56:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
)
|
||||
if len(data) == 57:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
@@ -233,14 +236,14 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
):
|
||||
try:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
@@ -248,29 +251,29 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||
else:
|
||||
try:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
return (
|
||||
format,
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
||||
data
|
||||
),
|
||||
)
|
||||
raise PrivateKeyError("Cannot load raw key")
|
||||
else:
|
||||
return (
|
||||
format,
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
data,
|
||||
None if passphrase is None else to_bytes(passphrase),
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
key_format,
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
data,
|
||||
None if passphrase is None else to_bytes(passphrase),
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
raise PrivateKeyError(e)
|
||||
raise PrivateKeyError(e) from e
|
||||
|
||||
|
||||
def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
|
||||
@@ -282,13 +285,17 @@ def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
|
||||
|
||||
def get_privatekey_argument_spec() -> ArgumentSpec:
|
||||
return ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
src_path=dict(type="path"),
|
||||
src_content=dict(type="str"),
|
||||
src_passphrase=dict(type="str", no_log=True),
|
||||
dest_passphrase=dict(type="str", no_log=True),
|
||||
format=dict(type="str", required=True, choices=["pkcs1", "pkcs8", "raw"]),
|
||||
),
|
||||
argument_spec={
|
||||
"src_path": {"type": "path"},
|
||||
"src_content": {"type": "str"},
|
||||
"src_passphrase": {"type": "str", "no_log": True},
|
||||
"dest_passphrase": {"type": "str", "no_log": True},
|
||||
"format": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"choices": ["pkcs1", "pkcs8", "raw"],
|
||||
},
|
||||
},
|
||||
mutually_exclusive=[
|
||||
["src_path", "src_content"],
|
||||
],
|
||||
|
||||
@@ -134,7 +134,7 @@ def _is_cryptography_key_consistent(
|
||||
# key._backend was removed in cryptography 42.0.0
|
||||
backend = getattr(key, "_backend", None)
|
||||
if backend is not None:
|
||||
return bool(backend._lib.RSA_check_key(key._rsa_cdata)) # type: ignore
|
||||
return bool(backend._lib.RSA_check_key(key._rsa_cdata)) # type: ignore # pylint: disable=protected-access
|
||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
result = _check_dsa_consistency(
|
||||
key_public_data=key_public_data, key_private_data=key_private_data
|
||||
@@ -195,19 +195,21 @@ def _is_cryptography_key_consistent(
|
||||
|
||||
class PrivateKeyConsistencyError(OpenSSLObjectError):
|
||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||
super(PrivateKeyConsistencyError, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
self.error_message = msg
|
||||
self.result = result
|
||||
|
||||
|
||||
class PrivateKeyParseError(OpenSSLObjectError):
|
||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||
super(PrivateKeyParseError, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
self.error_message = msg
|
||||
self.result = result
|
||||
|
||||
|
||||
class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
key: PrivateKeyTypes
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -257,14 +259,14 @@ class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
)
|
||||
result["can_parse_key"] = True
|
||||
except OpenSSLObjectError as exc:
|
||||
raise PrivateKeyParseError(str(exc), result=result)
|
||||
raise PrivateKeyParseError(str(exc), result=result) from exc
|
||||
|
||||
result["public_key"] = to_native(self._get_public_key(binary=False))
|
||||
pk = self._get_public_key(binary=True)
|
||||
result["public_key_fingerprints"] = (
|
||||
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
||||
if pk is not None
|
||||
else dict()
|
||||
else {}
|
||||
)
|
||||
|
||||
key_type, key_public_data, key_private_data = self._get_key_info(
|
||||
@@ -295,9 +297,7 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
|
||||
def __init__(
|
||||
self, *, module: GeneralAnsibleModule, content: bytes, **kwargs
|
||||
) -> None:
|
||||
super(PrivateKeyInfoRetrievalCryptography, self).__init__(
|
||||
module=module, content=content, **kwargs
|
||||
)
|
||||
super().__init__(module=module, content=content, **kwargs)
|
||||
|
||||
def _get_public_key(self, *, binary: bool) -> bytes:
|
||||
return self.key.public_key().public_bytes(
|
||||
|
||||
@@ -100,7 +100,7 @@ def _get_cryptography_public_key_info(
|
||||
|
||||
class PublicKeyParseError(OpenSSLObjectError):
|
||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||
super(PublicKeyParseError, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
self.error_message = msg
|
||||
self.result = result
|
||||
|
||||
@@ -132,13 +132,13 @@ class PublicKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
||||
try:
|
||||
self.key = load_publickey(content=self.content)
|
||||
except OpenSSLObjectError as e:
|
||||
raise PublicKeyParseError(str(e), result={})
|
||||
raise PublicKeyParseError(str(e), result={}) from e
|
||||
|
||||
pk = self._get_public_key(binary=True)
|
||||
result["fingerprints"] = (
|
||||
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
||||
if pk is not None
|
||||
else dict()
|
||||
else {}
|
||||
)
|
||||
|
||||
key_type, key_public_data = self._get_key_info()
|
||||
@@ -157,9 +157,7 @@ class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
|
||||
content: bytes | None = None,
|
||||
key: PublicKeyTypes | None = None,
|
||||
) -> None:
|
||||
super(PublicKeyInfoRetrievalCryptography, self).__init__(
|
||||
module=module, content=content, key=key
|
||||
)
|
||||
super().__init__(module=module, content=content, key=key)
|
||||
|
||||
def _get_public_key(self, binary: bool) -> bytes:
|
||||
if self.key is None:
|
||||
|
||||
@@ -161,19 +161,21 @@ def load_privatekey(
|
||||
else:
|
||||
priv_key_detail = content
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
|
||||
try:
|
||||
return load_pem_private_key(
|
||||
priv_key_detail,
|
||||
None if passphrase is None else to_bytes(passphrase),
|
||||
)
|
||||
except TypeError:
|
||||
except TypeError as exc:
|
||||
raise OpenSSLBadPassphraseError(
|
||||
"Wrong or empty passphrase provided for private key"
|
||||
)
|
||||
except ValueError:
|
||||
raise OpenSSLBadPassphraseError("Wrong passphrase provided for private key")
|
||||
) from exc
|
||||
except ValueError as exc:
|
||||
raise OpenSSLBadPassphraseError(
|
||||
"Wrong passphrase provided for private key"
|
||||
) from exc
|
||||
|
||||
|
||||
def load_certificate_privatekey(
|
||||
@@ -232,12 +234,12 @@ def load_publickey(
|
||||
with open(path, "rb") as b_priv_key_fh:
|
||||
content = b_priv_key_fh.read()
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
|
||||
try:
|
||||
return serialization.load_pem_public_key(content)
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(f"Error while deserializing key: {e}")
|
||||
raise OpenSSLObjectError(f"Error while deserializing key: {e}") from e
|
||||
|
||||
|
||||
def load_certificate(
|
||||
@@ -257,17 +259,17 @@ def load_certificate(
|
||||
else:
|
||||
cert_content = content
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
if der_support_enabled is False or identify_pem_format(cert_content):
|
||||
try:
|
||||
return x509.load_pem_x509_certificate(cert_content)
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
elif der_support_enabled:
|
||||
try:
|
||||
return x509.load_der_x509_certificate(cert_content)
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(f"Cannot parse DER certificate: {exc}")
|
||||
raise OpenSSLObjectError(f"Cannot parse DER certificate: {exc}") from exc
|
||||
|
||||
|
||||
def load_certificate_request(
|
||||
@@ -283,11 +285,11 @@ def load_certificate_request(
|
||||
else:
|
||||
csr_content = content
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
try:
|
||||
return x509.load_pem_x509_csr(csr_content)
|
||||
except ValueError as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
|
||||
|
||||
def parse_name_field(
|
||||
@@ -344,7 +346,7 @@ def parse_ordered_name_field(
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(
|
||||
f"Error while processing entry #{index + 1} in {name_field_name}: {exc}"
|
||||
)
|
||||
) from exc
|
||||
return result
|
||||
|
||||
|
||||
@@ -425,9 +427,7 @@ class OpenSSLObject(metaclass=abc.ABCMeta):
|
||||
self.changed = True
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise OpenSSLObjectError(exc)
|
||||
else:
|
||||
pass
|
||||
raise OpenSSLObjectError(exc) from exc
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
||||
@@ -43,16 +43,20 @@ valid_file_format = re.compile(r".*(\.)(yml|yaml|json)$")
|
||||
|
||||
|
||||
def ecs_client_argument_spec() -> dict[str, t.Any]:
|
||||
return dict(
|
||||
entrust_api_user=dict(type="str", required=True),
|
||||
entrust_api_key=dict(type="str", required=True, no_log=True),
|
||||
entrust_api_client_cert_path=dict(type="path", required=True),
|
||||
entrust_api_client_cert_key_path=dict(type="path", required=True, no_log=True),
|
||||
entrust_api_specification_path=dict(
|
||||
type="path",
|
||||
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||
),
|
||||
)
|
||||
return {
|
||||
"entrust_api_user": {"type": "str", "required": True},
|
||||
"entrust_api_key": {"type": "str", "required": True, "no_log": True},
|
||||
"entrust_api_client_cert_path": {"type": "path", "required": True},
|
||||
"entrust_api_client_cert_key_path": {
|
||||
"type": "path",
|
||||
"required": True,
|
||||
"no_log": True,
|
||||
},
|
||||
"entrust_api_specification_path": {
|
||||
"type": "path",
|
||||
"default": "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class SessionConfigurationException(Exception):
|
||||
@@ -182,8 +186,7 @@ class RestOperation:
|
||||
if result or result == {}:
|
||||
if result_code and result_code < 400:
|
||||
return result
|
||||
else:
|
||||
raise RestOperationException(result)
|
||||
raise RestOperationException(result)
|
||||
|
||||
# Raise a generic RestOperationException if this fails
|
||||
raise RestOperationException(
|
||||
@@ -323,9 +326,9 @@ class ECSSession:
|
||||
except HTTPError as e:
|
||||
raise SessionConfigurationException(
|
||||
f"Error downloading specification from address '{entrust_api_specification_path}', received error code '{e.getcode()}'"
|
||||
)
|
||||
) from e
|
||||
else:
|
||||
with open(entrust_api_specification_path) as f:
|
||||
with open(entrust_api_specification_path, "rb") as f:
|
||||
if ".json" in entrust_api_specification_path:
|
||||
self._spec = json.load(f)
|
||||
elif (
|
||||
|
||||
@@ -45,8 +45,7 @@ def restore_on_failure(
|
||||
if backup_file is not None:
|
||||
module.atomic_move(os.path.abspath(backup_file), os.path.abspath(path))
|
||||
raise
|
||||
else:
|
||||
module.add_cleanup_file(backup_file)
|
||||
module.add_cleanup_file(backup_file)
|
||||
|
||||
return backup_and_restore
|
||||
|
||||
@@ -91,9 +90,8 @@ def _restore_all_on_failure(
|
||||
os.path.abspath(backup), os.path.abspath(destination)
|
||||
)
|
||||
raise
|
||||
else:
|
||||
for destination, backup in backups:
|
||||
self.module.add_cleanup_file(backup)
|
||||
for destination, backup in backups:
|
||||
self.module.add_cleanup_file(backup)
|
||||
|
||||
return backup_and_restore
|
||||
|
||||
@@ -126,7 +124,7 @@ class OpensshModule(metaclass=abc.ABCMeta):
|
||||
|
||||
result["changed"] = self.changed
|
||||
|
||||
if self.module._diff:
|
||||
if self.module._diff: # pylint: disable=protected-access
|
||||
result["diff"] = self.diff
|
||||
|
||||
return result
|
||||
@@ -219,7 +217,7 @@ class KeygenCommand:
|
||||
serial_number: int | None,
|
||||
signature_algorithm: str | None,
|
||||
signing_key_path: str,
|
||||
type: t.Literal["host", "user"] | None,
|
||||
cert_type: t.Literal["host", "user"] | None,
|
||||
time_parameters: OpensshCertificateTimeParameters,
|
||||
use_agent: bool,
|
||||
**kwargs,
|
||||
@@ -235,7 +233,7 @@ class KeygenCommand:
|
||||
args.extend(["-n", ",".join(principals)])
|
||||
if serial_number is not None:
|
||||
args.extend(["-z", str(serial_number)])
|
||||
if type == "host":
|
||||
if cert_type == "host":
|
||||
args.extend(["-h"])
|
||||
if use_agent:
|
||||
args.extend(["-U"])
|
||||
@@ -252,7 +250,7 @@ class KeygenCommand:
|
||||
*,
|
||||
private_key_path: str,
|
||||
size: int,
|
||||
type: str,
|
||||
key_type: str,
|
||||
comment: str | None,
|
||||
**kwargs,
|
||||
) -> tuple[int, str, str]:
|
||||
@@ -264,7 +262,7 @@ class KeygenCommand:
|
||||
"-b",
|
||||
str(size),
|
||||
"-t",
|
||||
type,
|
||||
key_type,
|
||||
"-f",
|
||||
private_key_path,
|
||||
"-C",
|
||||
@@ -313,7 +311,7 @@ class KeygenCommand:
|
||||
except (IOError, OSError) as e:
|
||||
raise ValueError(
|
||||
f"The private key at {private_key_path} is not writeable preventing a comment update ({e})"
|
||||
)
|
||||
) from e
|
||||
|
||||
command = [self._bin_path, "-q"]
|
||||
if force_new_format:
|
||||
@@ -327,12 +325,12 @@ _PrivateKey = t.TypeVar("_PrivateKey", bound="PrivateKey")
|
||||
|
||||
class PrivateKey:
|
||||
def __init__(
|
||||
self, *, size: int, key_type: str, fingerprint: str, format: str = ""
|
||||
self, *, size: int, key_type: str, fingerprint: str, key_format: str = ""
|
||||
) -> None:
|
||||
self._size = size
|
||||
self._type = key_type
|
||||
self._fingerprint = fingerprint
|
||||
self._format = format
|
||||
self._format = key_format
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
@@ -428,11 +426,8 @@ class PublicKey:
|
||||
|
||||
@classmethod
|
||||
def load(cls: t.Type[_PublicKey], path: str | os.PathLike) -> _PublicKey | None:
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
properties = f.read().strip(" \n").split(" ", 2)
|
||||
except (IOError, OSError):
|
||||
raise
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
properties = f.read().strip(" \n").split(" ", 2)
|
||||
|
||||
if len(properties) < 2:
|
||||
return None
|
||||
@@ -454,14 +449,14 @@ def parse_private_key_format(
|
||||
*,
|
||||
path: str | os.PathLike,
|
||||
) -> t.Literal["SSH", "PKCS8", "PKCS1", ""]:
|
||||
with open(path, "r") as file:
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
header = file.readline().strip()
|
||||
|
||||
if header == "-----BEGIN OPENSSH PRIVATE KEY-----":
|
||||
return "SSH"
|
||||
elif header == "-----BEGIN PRIVATE KEY-----":
|
||||
if header == "-----BEGIN PRIVATE KEY-----":
|
||||
return "PKCS8"
|
||||
elif header == "-----BEGIN RSA PRIVATE KEY-----":
|
||||
if header == "-----BEGIN RSA PRIVATE KEY-----":
|
||||
return "PKCS1"
|
||||
|
||||
return ""
|
||||
|
||||
@@ -54,7 +54,7 @@ if t.TYPE_CHECKING:
|
||||
class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
||||
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(KeypairBackend, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
self.comment: str | None = self.module.params["comment"]
|
||||
self.private_key_path: str = self.module.params["path"]
|
||||
@@ -189,9 +189,9 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
||||
def _should_generate(self) -> bool:
|
||||
if self.original_private_key is None:
|
||||
return True
|
||||
elif self.regenerate == "never":
|
||||
if self.regenerate == "never":
|
||||
return False
|
||||
elif self.regenerate == "fail":
|
||||
if self.regenerate == "fail":
|
||||
if not self._private_key_valid():
|
||||
self.module.fail_json(
|
||||
msg="Key has wrong type and/or size. Will not proceed. "
|
||||
@@ -199,10 +199,9 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
||||
+ "`partial_idempotence`, `full_idempotence` or `always`, or with `force=true`."
|
||||
)
|
||||
return False
|
||||
elif self.regenerate in ("partial_idempotence", "full_idempotence"):
|
||||
if self.regenerate in ("partial_idempotence", "full_idempotence"):
|
||||
return not self._private_key_valid()
|
||||
else:
|
||||
return True
|
||||
return True
|
||||
|
||||
def _private_key_valid(self) -> bool:
|
||||
if self.original_private_key is None:
|
||||
@@ -358,7 +357,7 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
||||
|
||||
class KeypairBackendOpensshBin(KeypairBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(KeypairBackendOpensshBin, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
if self.module.params["private_key_format"] != "auto":
|
||||
self.module.fail_json(
|
||||
@@ -371,7 +370,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
||||
self.ssh_keygen.generate_keypair(
|
||||
private_key_path=private_key_path,
|
||||
size=self.size,
|
||||
type=self.type,
|
||||
key_type=self.type,
|
||||
comment=self.comment,
|
||||
check_rc=True,
|
||||
)
|
||||
@@ -391,7 +390,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
||||
return PublicKey.from_string(public_key_content)
|
||||
|
||||
def _private_key_readable(self) -> bool:
|
||||
rc, stdout, stderr = self.ssh_keygen.get_matching_public_key(
|
||||
rc, _stdout, stderr = self.ssh_keygen.get_matching_public_key(
|
||||
private_key_path=self.private_key_path, check_rc=False
|
||||
)
|
||||
return not (
|
||||
@@ -425,7 +424,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
||||
|
||||
class KeypairBackendCryptography(KeypairBackend):
|
||||
def __init__(self, *, module: AnsibleModule) -> None:
|
||||
super(KeypairBackendCryptography, self).__init__(module=module)
|
||||
super().__init__(module=module)
|
||||
|
||||
if self.type == "rsa1":
|
||||
self.module.fail_json(
|
||||
@@ -489,7 +488,7 @@ class KeypairBackendCryptography(KeypairBackend):
|
||||
size=keypair.size,
|
||||
key_type=keypair.key_type,
|
||||
fingerprint=keypair.fingerprint,
|
||||
format=parse_private_key_format(path=self.private_key_path),
|
||||
key_format=parse_private_key_format(path=self.private_key_path),
|
||||
)
|
||||
|
||||
def _get_public_key(self) -> PublicKey | t.Literal[""]:
|
||||
@@ -522,10 +521,9 @@ class KeypairBackendCryptography(KeypairBackend):
|
||||
OpensshKeypair.load(
|
||||
path=self.private_key_path, passphrase=None, no_public_key=True
|
||||
)
|
||||
return False
|
||||
except (InvalidPrivateKeyFileError, InvalidPassphraseError):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -124,11 +124,9 @@ class OpensshCertificateTimeParameters:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, type(self)):
|
||||
return NotImplemented
|
||||
else:
|
||||
return (
|
||||
self._valid_from == other._valid_from
|
||||
and self._valid_to == other._valid_to
|
||||
)
|
||||
return (
|
||||
self._valid_from == other._valid_from and self._valid_to == other._valid_to
|
||||
)
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return not self == other
|
||||
@@ -188,12 +186,11 @@ class OpensshCertificateTimeParameters:
|
||||
return "always"
|
||||
if dt == _FOREVER:
|
||||
return "forever"
|
||||
else:
|
||||
return (
|
||||
dt.isoformat().replace("+00:00", "")
|
||||
if date_format == "human_readable"
|
||||
else dt.strftime("%Y%m%d%H%M%S")
|
||||
)
|
||||
return (
|
||||
dt.isoformat().replace("+00:00", "")
|
||||
if date_format == "human_readable"
|
||||
else dt.strftime("%Y%m%d%H%M%S")
|
||||
)
|
||||
if date_format == "timestamp":
|
||||
td = dt - _ALWAYS
|
||||
return int(
|
||||
@@ -203,22 +200,17 @@ class OpensshCertificateTimeParameters:
|
||||
|
||||
@staticmethod
|
||||
def to_datetime(time_string_or_timestamp: str | bytes | int) -> datetime:
|
||||
try:
|
||||
if isinstance(time_string_or_timestamp, (str, bytes)):
|
||||
result = OpensshCertificateTimeParameters._time_string_to_datetime(
|
||||
to_text(time_string_or_timestamp.strip())
|
||||
)
|
||||
elif isinstance(time_string_or_timestamp, int):
|
||||
result = OpensshCertificateTimeParameters._timestamp_to_datetime(
|
||||
time_string_or_timestamp
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
|
||||
)
|
||||
except ValueError:
|
||||
raise
|
||||
return result
|
||||
if isinstance(time_string_or_timestamp, (str, bytes)):
|
||||
return OpensshCertificateTimeParameters._time_string_to_datetime(
|
||||
to_text(time_string_or_timestamp.strip())
|
||||
)
|
||||
if isinstance(time_string_or_timestamp, int):
|
||||
return OpensshCertificateTimeParameters._timestamp_to_datetime(
|
||||
time_string_or_timestamp
|
||||
)
|
||||
raise ValueError(
|
||||
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _timestamp_to_datetime(timestamp: int) -> datetime:
|
||||
@@ -228,8 +220,8 @@ class OpensshCertificateTimeParameters:
|
||||
return _FOREVER
|
||||
try:
|
||||
return datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
|
||||
except OverflowError:
|
||||
raise ValueError
|
||||
except OverflowError as e:
|
||||
raise ValueError from e
|
||||
|
||||
@staticmethod
|
||||
def _time_string_to_datetime(time_string: str) -> datetime:
|
||||
@@ -382,16 +374,15 @@ class OpensshCertificateInfo(metaclass=abc.ABCMeta):
|
||||
def cert_type(self) -> t.Literal["user", "host", ""]:
|
||||
if self._cert_type == _USER_TYPE:
|
||||
return "user"
|
||||
elif self._cert_type == _HOST_TYPE:
|
||||
if self._cert_type == _HOST_TYPE:
|
||||
return "host"
|
||||
else:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
@cert_type.setter
|
||||
def cert_type(self, cert_type: t.Literal["user", "host"] | int) -> None:
|
||||
if cert_type == "user" or cert_type == _USER_TYPE:
|
||||
if cert_type in ("user", _USER_TYPE):
|
||||
self._cert_type = _USER_TYPE
|
||||
elif cert_type == "host" or cert_type == _HOST_TYPE:
|
||||
elif cert_type in ("host", _HOST_TYPE):
|
||||
self._cert_type = _HOST_TYPE
|
||||
else:
|
||||
raise ValueError(f"{cert_type} is not a valid certificate type")
|
||||
@@ -412,7 +403,7 @@ class OpensshCertificateInfo(metaclass=abc.ABCMeta):
|
||||
|
||||
class OpensshRSACertificateInfo(OpensshCertificateInfo):
|
||||
def __init__(self, *, e: int | None = None, n: int | None = None, **kwargs) -> None:
|
||||
super(OpensshRSACertificateInfo, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.type_string = _SSH_TYPE_STRINGS["rsa"] + _CERT_SUFFIX_V01
|
||||
self.e = e
|
||||
self.n = n
|
||||
@@ -444,7 +435,7 @@ class OpensshDSACertificateInfo(OpensshCertificateInfo):
|
||||
y: int | None = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super(OpensshDSACertificateInfo, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.type_string = _SSH_TYPE_STRINGS["dsa"] + _CERT_SUFFIX_V01
|
||||
self.p = p
|
||||
self.q = q
|
||||
@@ -476,7 +467,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
|
||||
def __init__(
|
||||
self, *, curve: bytes | None = None, public_key: bytes | None = None, **kwargs
|
||||
):
|
||||
super(OpensshECDSACertificateInfo, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self._curve = None
|
||||
if curve is not None:
|
||||
self.curve = curve
|
||||
@@ -519,7 +510,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
|
||||
|
||||
class OpensshED25519CertificateInfo(OpensshCertificateInfo):
|
||||
def __init__(self, *, pk: bytes | None = None, **kwargs) -> None:
|
||||
super(OpensshED25519CertificateInfo, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.type_string = _SSH_TYPE_STRINGS["ed25519"] + _CERT_SUFFIX_V01
|
||||
self.pk = pk
|
||||
|
||||
@@ -559,13 +550,13 @@ class OpensshCertificate:
|
||||
with open(path, "rb") as cert_file:
|
||||
data = cert_file.read()
|
||||
except (IOError, OSError) as e:
|
||||
raise ValueError(f"{path} cannot be opened for reading: {e}")
|
||||
raise ValueError(f"{path} cannot be opened for reading: {e}") from e
|
||||
|
||||
try:
|
||||
format_identifier, b64_cert = data.split(b" ")[:2]
|
||||
cert = binascii.a2b_base64(b64_cert)
|
||||
except (binascii.Error, ValueError):
|
||||
raise ValueError("Certificate not in OpenSSH format")
|
||||
except (binascii.Error, ValueError) as e:
|
||||
raise ValueError("Certificate not in OpenSSH format") from e
|
||||
|
||||
for key_type, string in _SSH_TYPE_STRINGS.items():
|
||||
if format_identifier == string + _CERT_SUFFIX_V01:
|
||||
@@ -585,7 +576,7 @@ class OpensshCertificate:
|
||||
cert_info = cls._parse_cert_info(pub_key_type, parser)
|
||||
signature = parser.string()
|
||||
except (TypeError, ValueError) as e:
|
||||
raise ValueError(f"Invalid certificate data: {e}")
|
||||
raise ValueError(f"Invalid certificate data: {e}") from e
|
||||
|
||||
if parser.remaining_bytes():
|
||||
raise ValueError(
|
||||
@@ -751,10 +742,9 @@ def apply_directives(directives: t.Iterable[str]) -> list[OpensshCertificateOpti
|
||||
|
||||
if "clear" in directives:
|
||||
return []
|
||||
else:
|
||||
return list(
|
||||
set(default_options()) - set(directive_to_option[d] for d in directives)
|
||||
)
|
||||
return list(
|
||||
set(default_options()) - set(directive_to_option[d] for d in directives)
|
||||
)
|
||||
|
||||
|
||||
def default_options() -> list[OpensshCertificateOption]:
|
||||
|
||||
@@ -19,6 +19,7 @@ try:
|
||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding, rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
||||
Ed25519PrivateKey,
|
||||
Ed25519PublicKey,
|
||||
@@ -68,6 +69,10 @@ except ImportError:
|
||||
CRYPTOGRAPHY_VERSION = "0.0"
|
||||
_ALGORITHM_PARAMETERS = {}
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils._crypto.cryptography_support import (
|
||||
is_potential_certificate_issuer_private_key,
|
||||
)
|
||||
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
KeyFormat = t.Literal["SSH", "PKCS8", "PKCS1"]
|
||||
@@ -309,10 +314,10 @@ class AsymmetricKeypair:
|
||||
|
||||
try:
|
||||
self.verify(signature=self.sign(b"message"), data=b"message")
|
||||
except InvalidSignatureError:
|
||||
except InvalidSignatureError as e:
|
||||
raise InvalidPublicKeyFileError(
|
||||
"The private key and public key of this keypair do not match"
|
||||
)
|
||||
) from e
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, AsymmetricKeypair):
|
||||
@@ -368,7 +373,7 @@ class AsymmetricKeypair:
|
||||
data, **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] # type: ignore
|
||||
)
|
||||
except TypeError as e:
|
||||
raise InvalidDataError(e)
|
||||
raise InvalidDataError(e) from e
|
||||
|
||||
def verify(self, *, signature: bytes, data: bytes) -> None:
|
||||
"""Verifies that the signature associated with the provided data was signed
|
||||
@@ -383,8 +388,8 @@ class AsymmetricKeypair:
|
||||
data,
|
||||
**_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"], # type: ignore
|
||||
)
|
||||
except InvalidSignature:
|
||||
raise InvalidSignatureError
|
||||
except InvalidSignature as e:
|
||||
raise InvalidSignatureError from e
|
||||
|
||||
def update_passphrase(self, passphrase: bytes | None = None) -> None:
|
||||
"""Updates the encryption algorithm of this key pair
|
||||
@@ -661,10 +666,10 @@ def load_privatekey(
|
||||
|
||||
try:
|
||||
privatekey_loader = privatekey_loaders[key_format]
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
raise InvalidKeyFormatError(
|
||||
f"{key_format} is not a valid key format ({','.join(privatekey_loaders)})"
|
||||
)
|
||||
) from e
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise InvalidPrivateKeyFileError(f"No file was found at {path}")
|
||||
@@ -673,32 +678,33 @@ def load_privatekey(
|
||||
with open(path, "rb") as f:
|
||||
content = f.read()
|
||||
|
||||
privatekey = privatekey_loader( # type: ignore
|
||||
try:
|
||||
privatekey = privatekey_loader(
|
||||
data=content,
|
||||
password=passphrase,
|
||||
)
|
||||
|
||||
except ValueError as exc:
|
||||
# Revert to PEM if key could not be loaded in SSH format
|
||||
if key_format == "SSH":
|
||||
try:
|
||||
privatekey = privatekey_loaders["PEM"]( # type: ignore
|
||||
except ValueError as exc:
|
||||
# Revert to PEM if key could not be loaded in SSH format
|
||||
if key_format == "SSH":
|
||||
privatekey = privatekey_loaders["PEM"](
|
||||
data=content,
|
||||
password=passphrase,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise InvalidPrivateKeyFileError(e)
|
||||
except TypeError as e:
|
||||
raise InvalidPassphraseError(e)
|
||||
except UnsupportedAlgorithm as e:
|
||||
raise InvalidAlgorithmError(e)
|
||||
else:
|
||||
raise InvalidPrivateKeyFileError(exc)
|
||||
else:
|
||||
raise InvalidPrivateKeyFileError(exc) from exc
|
||||
except ValueError as e:
|
||||
raise InvalidPrivateKeyFileError(e) from e
|
||||
except TypeError as e:
|
||||
raise InvalidPassphraseError(e)
|
||||
raise InvalidPassphraseError(e) from e
|
||||
except UnsupportedAlgorithm as e:
|
||||
raise InvalidAlgorithmError(e)
|
||||
raise InvalidAlgorithmError(e) from e
|
||||
|
||||
if not is_potential_certificate_issuer_private_key(privatekey) or isinstance(
|
||||
privatekey, Ed448PrivateKey
|
||||
):
|
||||
raise InvalidPrivateKeyFileError(
|
||||
f"{privatekey} is not a supported private key type"
|
||||
)
|
||||
return privatekey
|
||||
|
||||
|
||||
@@ -713,10 +719,10 @@ def load_publickey(
|
||||
|
||||
try:
|
||||
publickey_loader = publickey_loaders[key_format]
|
||||
except KeyError:
|
||||
except KeyError as e:
|
||||
raise InvalidKeyFormatError(
|
||||
f"{key_format} is not a valid key format ({','.join(publickey_loaders)})"
|
||||
)
|
||||
) from e
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise InvalidPublicKeyFileError(f"No file was found at {path}")
|
||||
@@ -729,9 +735,9 @@ def load_publickey(
|
||||
data=content,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise InvalidPublicKeyFileError(e)
|
||||
raise InvalidPublicKeyFileError(e) from e
|
||||
except UnsupportedAlgorithm as e:
|
||||
raise InvalidAlgorithmError(e)
|
||||
raise InvalidAlgorithmError(e) from e
|
||||
|
||||
return publickey
|
||||
|
||||
@@ -749,8 +755,7 @@ def compare_publickeys(pk1: PublicKeyTypes, pk2: PublicKeyTypes) -> bool:
|
||||
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
||||
)
|
||||
return a_bytes == b_bytes
|
||||
else:
|
||||
return pk1.public_numbers() == pk2.public_numbers() # type: ignore
|
||||
return pk1.public_numbers() == pk2.public_numbers() # type: ignore
|
||||
|
||||
|
||||
def compare_encryption_algorithms(
|
||||
@@ -761,12 +766,11 @@ def compare_encryption_algorithms(
|
||||
ea2, serialization.NoEncryption
|
||||
):
|
||||
return True
|
||||
elif isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
|
||||
if isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
|
||||
ea2, serialization.BestAvailableEncryption
|
||||
):
|
||||
return ea1.password == ea2.password
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def get_encryption_algorithm(
|
||||
@@ -775,7 +779,7 @@ def get_encryption_algorithm(
|
||||
try:
|
||||
return serialization.BestAvailableEncryption(passphrase)
|
||||
except ValueError as e:
|
||||
raise InvalidPassphraseError(e)
|
||||
raise InvalidPassphraseError(e) from e
|
||||
|
||||
|
||||
def validate_comment(comment: str) -> None:
|
||||
@@ -796,7 +800,7 @@ def extract_comment(path: str | os.PathLike) -> str:
|
||||
else:
|
||||
comment = ""
|
||||
except (IOError, OSError) as e:
|
||||
raise InvalidPublicKeyFileError(e)
|
||||
raise InvalidPublicKeyFileError(e) from e
|
||||
|
||||
return comment
|
||||
|
||||
|
||||
@@ -177,10 +177,9 @@ class OpensshParser:
|
||||
def _check_position(self, offset: int) -> int:
|
||||
if self._pos + offset > len(self._data):
|
||||
raise ValueError(f"Insufficient data remaining at position: {self._pos}")
|
||||
elif self._pos + offset < 0:
|
||||
if self._pos + offset < 0:
|
||||
raise ValueError("Position cannot be less than zero.")
|
||||
else:
|
||||
return self._pos + offset
|
||||
return self._pos + offset
|
||||
|
||||
@classmethod
|
||||
def signature_data(cls, *, signature_string: bytes) -> dict[str, bytes | int]:
|
||||
@@ -306,7 +305,9 @@ class _OpensshWriter:
|
||||
try:
|
||||
self.string(",".join(value).encode("ASCII"))
|
||||
except UnicodeEncodeError as e:
|
||||
raise ValueError(f"Name-list's must consist of US-ASCII characters: {e}")
|
||||
raise ValueError(
|
||||
f"Name-list's must consist of US-ASCII characters: {e}"
|
||||
) from e
|
||||
|
||||
return self
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def parse_serial(value: str | bytes) -> int:
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
f"The {i + 1}{th(i + 1)} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}"
|
||||
)
|
||||
) from exc
|
||||
result = (result << 8) | part_value
|
||||
return result
|
||||
|
||||
|
||||
@@ -102,8 +102,7 @@ def convert_relative_to_datetime(
|
||||
|
||||
if parsed_result.group("prefix") == "+":
|
||||
return now + offset
|
||||
else:
|
||||
return now - offset
|
||||
return now - offset
|
||||
|
||||
|
||||
def get_relative_time_option(
|
||||
|
||||
Reference in New Issue
Block a user