Reformat everything with black.

I had to undo the u string prefix removals to not drop Python 2 compatibility.
That's why black isn't enabled in antsibull-nox.toml yet.
This commit is contained in:
Felix Fontein
2025-04-28 09:51:33 +02:00
parent 04a0d38e3b
commit aec1826c34
118 changed files with 11780 additions and 7565 deletions

View File

@@ -41,13 +41,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.6"
CRYPTOGRAPHY_IMP_ERR = None
CRYPTOGRAPHY_VERSION = None
try:
import cryptography
from cryptography import x509
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -66,21 +67,21 @@ class CertificateBackend(object):
self.module = module
self.backend = backend
self.force = module.params['force']
self.ignore_timestamps = module.params['ignore_timestamps']
self.privatekey_path = module.params['privatekey_path']
self.privatekey_content = module.params['privatekey_content']
self.force = module.params["force"]
self.ignore_timestamps = module.params["ignore_timestamps"]
self.privatekey_path = module.params["privatekey_path"]
self.privatekey_content = module.params["privatekey_content"]
if self.privatekey_content is not None:
self.privatekey_content = self.privatekey_content.encode('utf-8')
self.privatekey_passphrase = module.params['privatekey_passphrase']
self.csr_path = module.params['csr_path']
self.csr_content = module.params['csr_content']
self.privatekey_content = self.privatekey_content.encode("utf-8")
self.privatekey_passphrase = module.params["privatekey_passphrase"]
self.csr_path = module.params["csr_path"]
self.csr_content = module.params["csr_content"]
if self.csr_content is not None:
self.csr_content = self.csr_content.encode('utf-8')
self.csr_content = self.csr_content.encode("utf-8")
# The following are default values which make sure check() works as
# before if providers do not explicitly change these properties.
self.create_subject_key_identifier = 'never_create'
self.create_subject_key_identifier = "never_create"
self.create_authority_key_identifier = False
self.privatekey = None
@@ -99,8 +100,10 @@ class CertificateBackend(object):
if data is None:
return dict()
try:
result = get_certificate_info(self.module, self.backend, data, prefer_one_fingerprint=True)
result['can_parse_certificate'] = True
result = get_certificate_info(
self.module, self.backend, data, prefer_one_fingerprint=True
)
result["can_parse_certificate"] = True
return result
except Exception:
return dict(can_parse_certificate=False)
@@ -118,7 +121,9 @@ class CertificateBackend(object):
def set_existing(self, certificate_bytes):
"""Set existing certificate bytes. None indicates that the key does not exist."""
self.existing_certificate_bytes = certificate_bytes
self.diff_after = self.diff_before = self._get_info(self.existing_certificate_bytes)
self.diff_after = self.diff_before = self._get_info(
self.existing_certificate_bytes
)
def has_existing(self):
"""Query whether an existing certificate is/has been there."""
@@ -166,33 +171,60 @@ class CertificateBackend(object):
def _check_privatekey(self):
"""Check whether provided parameters match, assuming self.existing_certificate and self.privatekey have been populated."""
if self.backend == 'cryptography':
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
if self.backend == "cryptography":
return cryptography_compare_public_keys(
self.existing_certificate.public_key(), self.privatekey.public_key()
)
def _check_csr(self):
"""Check whether provided parameters match, assuming self.existing_certificate and self.csr have been populated."""
if self.backend == 'cryptography':
if self.backend == "cryptography":
# Verify that CSR is signed by certificate's private key
if not self.csr.is_signature_valid:
return False
if not cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key()):
if not cryptography_compare_public_keys(
self.csr.public_key(), self.existing_certificate.public_key()
):
return False
# Check subject
if self.check_csr_subject and self.csr.subject != self.existing_certificate.subject:
if (
self.check_csr_subject
and self.csr.subject != self.existing_certificate.subject
):
return False
# Check extensions
if not self.check_csr_extensions:
return True
cert_exts = list(self.existing_certificate.extensions)
csr_exts = list(self.csr.extensions)
if self.create_subject_key_identifier != 'never_create':
if self.create_subject_key_identifier != "never_create":
# Filter out SubjectKeyIdentifier extension before comparison
cert_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), cert_exts))
csr_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), csr_exts))
cert_exts = list(
filter(
lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier),
cert_exts,
)
)
csr_exts = list(
filter(
lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier),
csr_exts,
)
)
if self.create_authority_key_identifier:
# Filter out AuthorityKeyIdentifier extension before comparison
cert_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), cert_exts))
csr_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), csr_exts))
cert_exts = list(
filter(
lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier),
cert_exts,
)
)
csr_exts = list(
filter(
lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier),
csr_exts,
)
)
if len(cert_exts) != len(csr_exts):
return False
for cert_ext in cert_exts:
@@ -208,19 +240,28 @@ class CertificateBackend(object):
"""Check whether Subject Key Identifier matches, assuming self.existing_certificate has been populated."""
# Get hold of certificate's SKI
try:
ext = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
ext = self.existing_certificate.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier
)
except cryptography.x509.ExtensionNotFound:
return False
# Get hold of CSR's SKI for 'create_if_not_provided'
csr_ext = None
if self.create_subject_key_identifier == 'create_if_not_provided':
if self.create_subject_key_identifier == "create_if_not_provided":
try:
csr_ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
csr_ext = self.csr.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier
)
except cryptography.x509.ExtensionNotFound:
pass
if csr_ext is None:
# If CSR had no SKI, or we chose to ignore it ('always_create'), compare with created SKI
if ext.value.digest != x509.SubjectKeyIdentifier.from_public_key(self.existing_certificate.public_key()).digest:
if (
ext.value.digest
!= x509.SubjectKeyIdentifier.from_public_key(
self.existing_certificate.public_key()
).digest
):
return False
else:
# If CSR had SKI and we did not ignore it ('create_if_not_provided'), compare SKIs
@@ -249,7 +290,10 @@ class CertificateBackend(object):
return True
# Check SubjectKeyIdentifier
if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier():
if (
self.create_subject_key_identifier != "never_create"
and not self._check_subject_key_identifier()
):
return True
# Check not before
@@ -265,10 +309,7 @@ class CertificateBackend(object):
def dump(self, include_certificate):
"""Serialize the object into a dictionary."""
result = {
'privatekey': self.privatekey_path,
'csr': self.csr_path
}
result = {"privatekey": self.privatekey_path, "csr": self.csr_path}
# Get hold of certificate bytes
certificate_bytes = self.existing_certificate_bytes
if self.cert is not None:
@@ -276,9 +317,11 @@ class CertificateBackend(object):
self.diff_after = self._get_info(certificate_bytes)
if include_certificate:
# Store result
result['certificate'] = certificate_bytes.decode('utf-8') if certificate_bytes else None
result["certificate"] = (
certificate_bytes.decode("utf-8") if certificate_bytes else None
)
result['diff'] = dict(
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
@@ -311,26 +354,38 @@ def select_backend(module, backend, provider):
"""
provider.validate_module_args(module)
backend = module.params['select_crypto_backend']
if backend == 'auto':
backend = module.params["select_crypto_backend"]
if backend == "auto":
# Detect what backend we can use
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# If cryptography is available we'll use it
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Fail if no backend has been found
if backend == 'auto':
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect the required Python library " "cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
if provider.needs_version_two_certs(module):
module.fail_json(msg='The cryptography backend does not support v2 certificates')
module.fail_json(
msg="The cryptography backend does not support v2 certificates"
)
return provider.create_backend(module, backend)
@@ -338,20 +393,26 @@ def select_backend(module, backend, provider):
def get_certificate_argument_spec():
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']),
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"]
),
# 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=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
privatekey_passphrase=dict(type="str", no_log=True),
),
mutually_exclusive=[
['csr_path', 'csr_content'],
['privatekey_path', 'privatekey_content'],
["csr_path", "csr_content"],
["privatekey_path", "privatekey_content"],
],
)

View File

@@ -26,44 +26,44 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
class AcmeCertificateBackend(CertificateBackend):
def __init__(self, module, backend):
super(AcmeCertificateBackend, self).__init__(module, backend)
self.accountkey_path = module.params['acme_accountkey_path']
self.challenge_path = module.params['acme_challenge_path']
self.use_chain = module.params['acme_chain']
self.acme_directory = module.params['acme_directory']
self.accountkey_path = module.params["acme_accountkey_path"]
self.challenge_path = module.params["acme_challenge_path"]
self.use_chain = module.params["acme_chain"]
self.acme_directory = module.params["acme_directory"]
if self.csr_content is None and self.csr_path is None:
raise CertificateError(
'csr_path or csr_content is required for ownca provider'
"csr_path or csr_content is required for ownca provider"
)
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file %s does not exist' % self.csr_path
"The certificate signing request file %s does not exist" % self.csr_path
)
if not os.path.exists(self.accountkey_path):
raise CertificateError(
'The account key %s does not exist' % self.accountkey_path
"The account key %s does not exist" % self.accountkey_path
)
if not os.path.exists(self.challenge_path):
raise CertificateError(
'The challenge path %s does not exist' % self.challenge_path
"The challenge path %s does not exist" % self.challenge_path
)
self.acme_tiny_path = self.module.get_bin_path('acme-tiny', required=True)
self.acme_tiny_path = self.module.get_bin_path("acme-tiny", required=True)
def generate_certificate(self):
"""(Re-)Generate certificate."""
command = [self.acme_tiny_path]
if self.use_chain:
command.append('--chain')
command.extend(['--account-key', self.accountkey_path])
command.append("--chain")
command.extend(["--account-key", self.accountkey_path])
if self.csr_content is not None:
# We need to temporarily write the CSR to disk
fd, tmpsrc = tempfile.mkstemp()
self.module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit
f = os.fdopen(fd, 'wb')
f = os.fdopen(fd, "wb")
try:
f.write(self.csr_content)
except Exception as err:
@@ -73,14 +73,14 @@ class AcmeCertificateBackend(CertificateBackend):
pass
self.module.fail_json(
msg="failed to create temporary CSR file: %s" % to_native(err),
exception=traceback.format_exc()
exception=traceback.format_exc(),
)
f.close()
command.extend(['--csr', tmpsrc])
command.extend(["--csr", tmpsrc])
else:
command.extend(['--csr', self.csr_path])
command.extend(['--acme-dir', self.challenge_path])
command.extend(['--directory-url', self.acme_directory])
command.extend(["--csr", self.csr_path])
command.extend(["--acme-dir", self.challenge_path])
command.extend(["--directory-url", self.acme_directory])
try:
self.cert = to_bytes(self.module.run_command(command, check_rc=True)[1])
@@ -93,16 +93,20 @@ class AcmeCertificateBackend(CertificateBackend):
def dump(self, include_certificate):
result = super(AcmeCertificateBackend, self).dump(include_certificate)
result['accountkey'] = self.accountkey_path
result["accountkey"] = self.accountkey_path
return result
class AcmeCertificateProvider(CertificateProvider):
def validate_module_args(self, module):
if module.params['acme_accountkey_path'] is None:
module.fail_json(msg='The acme_accountkey_path option must be specified for the acme provider.')
if module.params['acme_challenge_path'] is None:
module.fail_json(msg='The acme_challenge_path option must be specified for the acme provider.')
if module.params["acme_accountkey_path"] is None:
module.fail_json(
msg="The acme_accountkey_path option must be specified for the acme provider."
)
if module.params["acme_challenge_path"] is None:
module.fail_json(
msg="The acme_challenge_path option must be specified for the acme provider."
)
def needs_version_two_certs(self, module):
return False
@@ -112,10 +116,14 @@ class AcmeCertificateProvider(CertificateProvider):
def add_acme_provider_to_argument_spec(argument_spec):
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"),
))
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"
),
)
)

View File

@@ -50,19 +50,21 @@ class EntrustCertificateBackend(CertificateBackend):
super(EntrustCertificateBackend, self).__init__(module, backend)
self.trackingId = None
self.notAfter = get_relative_time_option(
module.params['entrust_not_after'],
'entrust_not_after',
module.params["entrust_not_after"],
"entrust_not_after",
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
if self.csr_content is None and self.csr_path is None:
raise CertificateError(
'csr_path or csr_content is required for entrust provider'
"csr_path or csr_content is required for entrust provider"
)
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
"The certificate signing request file {0} does not exist".format(
self.csr_path
)
)
self._ensure_csr_loaded()
@@ -71,28 +73,42 @@ class EntrustCertificateBackend(CertificateBackend):
# We want to always force behavior of trying to use the organization provided in the CSR.
# To that end we need to parse out the organization from the CSR.
self.csr_org = None
if self.backend == 'cryptography':
csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
if self.backend == "cryptography":
csr_subject_orgs = self.csr.subject.get_attributes_for_oid(
NameOID.ORGANIZATION_NAME
)
if len(csr_subject_orgs) == 1:
self.csr_org = csr_subject_orgs[0].value
elif len(csr_subject_orgs) > 1:
self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations found in "
"Subject DN: '{0}'. ".format(self.csr.subject)))
self.module.fail_json(
msg=(
"Entrust provider does not currently support multiple validated organizations. Multiple organizations found in "
"Subject DN: '{0}'. ".format(self.csr.subject)
)
)
# If no organization in the CSR, explicitly tell ECS that it should be blank in issued cert, not defaulted to
# organization tied to the account.
if self.csr_org is None:
self.csr_org = ''
self.csr_org = ""
try:
self.ecs_client = ECSClient(
entrust_api_user=self.module.params['entrust_api_user'],
entrust_api_key=self.module.params['entrust_api_key'],
entrust_api_cert=self.module.params['entrust_api_client_cert_path'],
entrust_api_cert_key=self.module.params['entrust_api_client_cert_key_path'],
entrust_api_specification_path=self.module.params['entrust_api_specification_path']
entrust_api_user=self.module.params["entrust_api_user"],
entrust_api_key=self.module.params["entrust_api_key"],
entrust_api_cert=self.module.params["entrust_api_client_cert_path"],
entrust_api_cert_key=self.module.params[
"entrust_api_client_cert_key_path"
],
entrust_api_specification_path=self.module.params[
"entrust_api_specification_path"
],
)
except SessionConfigurationException as e:
module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e.message)))
module.fail_json(
msg="Failed to initialize Entrust Provider: {0}".format(
to_native(e.message)
)
)
def generate_certificate(self):
"""(Re-)Generate certificate."""
@@ -101,12 +117,12 @@ class EntrustCertificateBackend(CertificateBackend):
# Read the CSR that was generated for us
if self.csr_content is not None:
# csr_content contains bytes
body['csr'] = to_native(self.csr_content)
body["csr"] = to_native(self.csr_content)
else:
with open(self.csr_path, 'r') as csr_file:
body['csr'] = csr_file.read()
with open(self.csr_path, "r") as csr_file:
body["csr"] = csr_file.read()
body['certType'] = self.module.params['entrust_cert_type']
body["certType"] = self.module.params["entrust_cert_type"]
# Handle expiration (30 days if not specified)
expiry = self.notAfter
@@ -115,22 +131,28 @@ class EntrustCertificateBackend(CertificateBackend):
expiry = gmt_now + datetime.timedelta(days=365)
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
body['certExpiryDate'] = expiry_iso3339
body['org'] = self.csr_org
body['tracking'] = {
'requesterName': self.module.params['entrust_requester_name'],
'requesterEmail': self.module.params['entrust_requester_email'],
'requesterPhone': self.module.params['entrust_requester_phone'],
body["certExpiryDate"] = expiry_iso3339
body["org"] = self.csr_org
body["tracking"] = {
"requesterName": self.module.params["entrust_requester_name"],
"requesterEmail": self.module.params["entrust_requester_email"],
"requesterPhone": self.module.params["entrust_requester_phone"],
}
try:
result = self.ecs_client.NewCertRequest(Body=body)
self.trackingId = result.get('trackingId')
self.trackingId = result.get("trackingId")
except RestOperationException as e:
self.module.fail_json(msg='Failed to request new certificate from Entrust Certificate Services (ECS): {0}'.format(to_native(e.message)))
self.module.fail_json(
msg="Failed to request new certificate from Entrust Certificate Services (ECS): {0}".format(
to_native(e.message)
)
)
self.cert_bytes = to_bytes(result.get('endEntityCert'))
self.cert = load_certificate(path=None, content=self.cert_bytes, backend=self.backend)
self.cert_bytes = to_bytes(result.get("endEntityCert"))
self.cert = load_certificate(
path=None, content=self.cert_bytes, backend=self.backend
)
def get_certificate_data(self):
"""Return bytes for self.cert."""
@@ -142,15 +164,23 @@ class EntrustCertificateBackend(CertificateBackend):
try:
cert_details = self._get_cert_details()
except RestOperationException as e:
self.module.fail_json(msg='Failed to get status of existing certificate from Entrust Certificate Services (ECS): {0}.'.format(to_native(e.message)))
self.module.fail_json(
msg="Failed to get status of existing certificate from Entrust Certificate Services (ECS): {0}.".format(
to_native(e.message)
)
)
# 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':
status = cert_details.get("status", False)
if status == "EXPIRED" or status == "SUSPENDED" or status == "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
if self.module.params['entrust_cert_type'] and cert_details.get('certType') and self.module.params['entrust_cert_type'] != cert_details.get('certType'):
if (
self.module.params["entrust_cert_type"]
and cert_details.get("certType")
and self.module.params["entrust_cert_type"] != cert_details.get("certType")
):
return True
return parent_check
@@ -164,27 +194,33 @@ class EntrustCertificateBackend(CertificateBackend):
if self.existing_certificate:
serial_number = None
expiry = None
if self.backend == 'cryptography':
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
if self.backend == "cryptography":
serial_number = "{0:X}".format(
cryptography_serial_number_of_cert(self.existing_certificate)
)
expiry = get_not_valid_after(self.existing_certificate)
# get some information about the expiry of this certificate
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
cert_details['expiresAfter'] = expiry_iso3339
cert_details["expiresAfter"] = expiry_iso3339
# If a trackingId is not already defined (from the result of a generate)
# use the serial number to identify the tracking Id
if self.trackingId is None and serial_number is not None:
cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {})
cert_results = self.ecs_client.GetCertificates(
serialNumber=serial_number
).get("certificates", {})
# Finding 0 or more than 1 result is a very unlikely use case, it simply means we cannot perform additional checks
# on the 'state' as returned by Entrust Certificate Services (ECS). The general certificate validity is
# still checked as it is in the rest of the module.
if len(cert_results) == 1:
self.trackingId = cert_results[0].get('trackingId')
self.trackingId = cert_results[0].get("trackingId")
if self.trackingId is not None:
cert_details.update(self.ecs_client.GetCertificate(trackingId=self.trackingId))
cert_details.update(
self.ecs_client.GetCertificate(trackingId=self.trackingId)
)
return cert_details
@@ -201,23 +237,51 @@ class EntrustCertificateProvider(CertificateProvider):
def add_entrust_provider_to_argument_spec(argument_spec):
argument_spec.argument_spec['provider']['choices'].append('entrust')
argument_spec.argument_spec.update(dict(
entrust_cert_type=dict(type='str', default='STANDARD_SSL',
choices=['STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL',
'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', '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'),
))
argument_spec.required_if.append(
['provider', 'entrust', ['entrust_requester_email', 'entrust_requester_name', 'entrust_requester_phone',
'entrust_api_user', 'entrust_api_key', 'entrust_api_client_cert_path',
'entrust_api_client_cert_key_path']]
argument_spec.argument_spec["provider"]["choices"].append("entrust")
argument_spec.argument_spec.update(
dict(
entrust_cert_type=dict(
type="str",
default="STANDARD_SSL",
choices=[
"STANDARD_SSL",
"ADVANTAGE_SSL",
"UC_SSL",
"EV_SSL",
"WILDCARD_SSL",
"PRIVATE_SSL",
"PD_SSL",
"CDS_ENT_LITE",
"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"),
)
)
argument_spec.required_if.append(
[
"provider",
"entrust",
[
"entrust_requester_email",
"entrust_requester_name",
"entrust_requester_phone",
"entrust_api_user",
"entrust_api_key",
"entrust_api_client_cert_path",
"entrust_api_client_cert_key_path",
],
]
)

View File

@@ -43,13 +43,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.6"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -151,75 +152,97 @@ class CertificateInfoRetrieval(object):
def get_info(self, prefer_one_fingerprint=False, der_support_enabled=False):
result = dict()
self.cert = load_certificate(None, content=self.content, backend=self.backend, der_support_enabled=der_support_enabled)
self.cert = load_certificate(
None,
content=self.content,
backend=self.backend,
der_support_enabled=der_support_enabled,
)
result['signature_algorithm'] = self._get_signature_algorithm()
result["signature_algorithm"] = self._get_signature_algorithm()
subject = self._get_subject_ordered()
issuer = self._get_issuer_ordered()
result['subject'] = dict()
result["subject"] = dict()
for k, v in subject:
result['subject'][k] = v
result['subject_ordered'] = subject
result['issuer'] = dict()
result["subject"][k] = v
result["subject_ordered"] = subject
result["issuer"] = dict()
for k, v in issuer:
result['issuer'][k] = v
result['issuer_ordered'] = issuer
result['version'] = self._get_version()
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
result["issuer"][k] = v
result["issuer_ordered"] = issuer
result["version"] = self._get_version()
result["key_usage"], result["key_usage_critical"] = self._get_key_usage()
result["extended_key_usage"], result["extended_key_usage_critical"] = (
self._get_extended_key_usage()
)
result["basic_constraints"], result["basic_constraints_critical"] = (
self._get_basic_constraints()
)
result["ocsp_must_staple"], result["ocsp_must_staple_critical"] = (
self._get_ocsp_must_staple()
)
result["subject_alt_name"], result["subject_alt_name_critical"] = (
self._get_subject_alt_name()
)
not_before = self.get_not_before()
not_after = self.get_not_after()
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
result['expired'] = not_after < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
result["not_before"] = not_before.strftime(TIMESTAMP_FORMAT)
result["not_after"] = not_after.strftime(TIMESTAMP_FORMAT)
result["expired"] = not_after < get_now_datetime(
with_timezone=CRYPTOGRAPHY_TIMEZONE
)
result['public_key'] = to_native(self._get_public_key_pem())
result["public_key"] = to_native(self._get_public_key_pem())
public_key_info = get_publickey_info(
self.module,
self.backend,
key=self._get_public_key_object(),
prefer_one_fingerprint=prefer_one_fingerprint)
result.update({
'public_key_type': public_key_info['type'],
'public_key_data': public_key_info['public_data'],
'public_key_fingerprints': public_key_info['fingerprints'],
})
prefer_one_fingerprint=prefer_one_fingerprint,
)
result.update(
{
"public_key_type": public_key_info["type"],
"public_key_data": public_key_info["public_data"],
"public_key_fingerprints": public_key_info["fingerprints"],
}
)
result['fingerprints'] = get_fingerprint_of_bytes(
self._get_der_bytes(), prefer_one=prefer_one_fingerprint)
result["fingerprints"] = get_fingerprint_of_bytes(
self._get_der_bytes(), prefer_one=prefer_one_fingerprint
)
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
ski = ":".join([ski[i : i + 2] for i in range(0, len(ski), 2)])
result["subject_key_identifier"] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
aki = ":".join([aki[i : i + 2] for i in range(0, len(aki), 2)])
result["authority_key_identifier"] = aki
result["authority_cert_issuer"] = aci
result["authority_cert_serial_number"] = acsn
result['serial_number'] = self._get_serial_number()
result['extensions_by_oid'] = self._get_all_extensions()
result['ocsp_uri'] = self._get_ocsp_uri()
result['issuer_uri'] = self._get_issuer_uri()
result["serial_number"] = self._get_serial_number()
result["extensions_by_oid"] = self._get_all_extensions()
result["ocsp_uri"] = self._get_ocsp_uri()
result["issuer_uri"] = self._get_issuer_uri()
return result
class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, module, content):
super(CertificateInfoRetrievalCryptography, self).__init__(module, 'cryptography', content)
self.name_encoding = module.params.get('name_encoding', 'ignore')
super(CertificateInfoRetrievalCryptography, self).__init__(
module, "cryptography", content
)
self.name_encoding = module.params.get("name_encoding", "ignore")
def _get_der_bytes(self):
return self.cert.public_bytes(serialization.Encoding.DER)
@@ -248,7 +271,9 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_key_usage(self):
try:
current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage)
current_key_ext = self.cert.extensions.get_extension_for_class(
x509.KeyUsage
)
current_key_usage = current_key_ext.value
key_usage = dict(
digital_signature=current_key_usage.digital_signature,
@@ -261,45 +286,63 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
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
))
if key_usage["key_agreement"]:
key_usage.update(
dict(
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',
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(
[
key_usage_names[name]
for name, value in key_usage.items()
if value
]
),
current_key_ext.critical,
)
return sorted([
key_usage_names[name] for name, value in key_usage.items() if value
]), current_key_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_extended_key_usage(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
return sorted([
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
]), ext_keyusage_ext.critical
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(
x509.ExtendedKeyUsage
)
return (
sorted(
[cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value]
),
ext_keyusage_ext.critical,
)
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_basic_constraints(self):
try:
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints)
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(
x509.BasicConstraints
)
result = []
result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE'))
result.append(
"CA:{0}".format("TRUE" if ext_keyusage_ext.value.ca else "FALSE")
)
if ext_keyusage_ext.value.path_length is not None:
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
result.append("pathlen:{0}".format(ext_keyusage_ext.value.path_length))
return sorted(result), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -308,8 +351,13 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
try:
try:
# This only works with cryptography >= 2.1
tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature)
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
tlsfeature_ext = self.cert.extensions.get_extension_for_class(
x509.TLSFeature
)
value = (
cryptography.x509.TLSFeatureType.status_request
in tlsfeature_ext.value
)
except AttributeError:
# Fallback for cryptography < 2.1
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
@@ -321,8 +369,13 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_subject_alt_name(self):
try:
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
san_ext = self.cert.extensions.get_extension_for_class(
x509.SubjectAlternativeName
)
result = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in san_ext.value
]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -344,18 +397,29 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_subject_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
ext = self.cert.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier
)
return ext.value.digest
except cryptography.x509.ExtensionNotFound:
return None
def _get_authority_key_identifier(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
ext = self.cert.extensions.get_extension_for_class(
x509.AuthorityKeyIdentifier
)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
issuer = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in ext.value.authority_cert_issuer
]
return (
ext.value.key_identifier,
issuer,
ext.value.authority_cert_serial_number,
)
except cryptography.x509.ExtensionNotFound:
return None, None, None
@@ -367,7 +431,9 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_ocsp_uri(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
ext = self.cert.extensions.get_extension_for_class(
x509.AuthorityInformationAccess
)
for desc in ext.value:
if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP:
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
@@ -378,9 +444,14 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_issuer_uri(self):
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
ext = self.cert.extensions.get_extension_for_class(
x509.AuthorityInformationAccess
)
for desc in ext.value:
if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS:
if (
desc.access_method
== x509.oid.AuthorityInformationAccessOID.CA_ISSUERS
):
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
return desc.access_location.value
except x509.ExtensionNotFound:
@@ -389,29 +460,40 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def get_certificate_info(module, backend, content, prefer_one_fingerprint=False):
if backend == 'cryptography':
if backend == "cryptography":
info = CertificateInfoRetrievalCryptography(module, content)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
def select_backend(module, backend, content):
if backend == 'auto':
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect any of the required Python libraries "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect any of the required Python libraries "
"cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, CertificateInfoRetrievalCryptography(module, content)
else:
raise ValueError('Unsupported value for backend: {0}'.format(backend))
raise ValueError("Unsupported value for backend: {0}".format(backend))

View File

@@ -58,75 +58,90 @@ except ImportError:
class OwnCACertificateBackendCryptography(CertificateBackend):
def __init__(self, module):
super(OwnCACertificateBackendCryptography, self).__init__(module, 'cryptography')
super(OwnCACertificateBackendCryptography, self).__init__(
module, "cryptography"
)
self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier']
self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier']
self.create_subject_key_identifier = module.params[
"ownca_create_subject_key_identifier"
]
self.create_authority_key_identifier = module.params[
"ownca_create_authority_key_identifier"
]
self.notBefore = get_relative_time_option(
module.params['ownca_not_before'],
'ownca_not_before',
module.params["ownca_not_before"],
"ownca_not_before",
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.notAfter = get_relative_time_option(
module.params['ownca_not_after'],
'ownca_not_after',
module.params["ownca_not_after"],
"ownca_not_after",
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.digest = select_message_digest(module.params['ownca_digest'])
self.version = module.params['ownca_version']
self.digest = select_message_digest(module.params["ownca_digest"])
self.version = module.params["ownca_version"]
self.serial_number = x509.random_serial_number()
self.ca_cert_path = module.params['ownca_path']
self.ca_cert_content = module.params['ownca_content']
self.ca_cert_path = module.params["ownca_path"]
self.ca_cert_content = module.params["ownca_content"]
if self.ca_cert_content is not None:
self.ca_cert_content = self.ca_cert_content.encode('utf-8')
self.ca_privatekey_path = module.params['ownca_privatekey_path']
self.ca_privatekey_content = module.params['ownca_privatekey_content']
self.ca_cert_content = self.ca_cert_content.encode("utf-8")
self.ca_privatekey_path = module.params["ownca_privatekey_path"]
self.ca_privatekey_content = module.params["ownca_privatekey_content"]
if self.ca_privatekey_content is not None:
self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8')
self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase']
self.ca_privatekey_content = self.ca_privatekey_content.encode("utf-8")
self.ca_privatekey_passphrase = module.params["ownca_privatekey_passphrase"]
if self.csr_content is None and self.csr_path is None:
raise CertificateError(
'csr_path or csr_content is required for ownca provider'
"csr_path or csr_content is required for ownca provider"
)
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
"The certificate signing request file {0} does not exist".format(
self.csr_path
)
)
if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path):
raise CertificateError(
'The CA certificate file {0} does not exist'.format(self.ca_cert_path)
"The CA certificate file {0} does not exist".format(self.ca_cert_path)
)
if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path):
if self.ca_privatekey_content is None and not os.path.exists(
self.ca_privatekey_path
):
raise CertificateError(
'The CA private key file {0} does not exist'.format(self.ca_privatekey_path)
"The CA private key file {0} does not exist".format(
self.ca_privatekey_path
)
)
self._ensure_csr_loaded()
self.ca_cert = load_certificate(
path=self.ca_cert_path,
content=self.ca_cert_content,
backend=self.backend
path=self.ca_cert_path, content=self.ca_cert_content, backend=self.backend
)
try:
self.ca_private_key = load_privatekey(
path=self.ca_privatekey_path,
content=self.ca_privatekey_content,
passphrase=self.ca_privatekey_passphrase,
backend=self.backend
backend=self.backend,
)
except OpenSSLBadPassphraseError as exc:
module.fail_json(msg=str(exc))
if not cryptography_compare_public_keys(self.ca_cert.public_key(), self.ca_private_key.public_key()):
raise CertificateError('The CA private key does not belong to the CA certificate')
if not cryptography_compare_public_keys(
self.ca_cert.public_key(), self.ca_private_key.public_key()
):
raise CertificateError(
"The CA private key does not belong to the CA certificate"
)
if cryptography_key_needs_digest_for_signing(self.ca_private_key):
if self.digest is None:
raise CertificateError(
'The digest %s is not supported with the cryptography backend' % module.params['ownca_digest']
"The digest %s is not supported with the cryptography backend"
% module.params["ownca_digest"]
)
else:
self.digest = None
@@ -143,40 +158,60 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
has_ski = False
for extension in self.csr.extensions:
if isinstance(extension.value, x509.SubjectKeyIdentifier):
if self.create_subject_key_identifier == 'always_create':
if self.create_subject_key_identifier == "always_create":
continue
has_ski = True
if self.create_authority_key_identifier and isinstance(extension.value, x509.AuthorityKeyIdentifier):
if self.create_authority_key_identifier and isinstance(
extension.value, x509.AuthorityKeyIdentifier
):
continue
cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical)
if not has_ski and self.create_subject_key_identifier != 'never_create':
cert_builder = cert_builder.add_extension(
extension.value, critical=extension.critical
)
if not has_ski and self.create_subject_key_identifier != "never_create":
cert_builder = cert_builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(self.csr.public_key()),
critical=False
critical=False,
)
if self.create_authority_key_identifier:
try:
ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
ext = self.ca_cert.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier
)
cert_builder = cert_builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value)
if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext),
critical=False
(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
ext.value
)
if CRYPTOGRAPHY_VERSION >= LooseVersion("2.7")
else x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
ext
)
),
critical=False,
)
except cryptography.x509.ExtensionNotFound:
cert_builder = cert_builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()),
critical=False
x509.AuthorityKeyIdentifier.from_issuer_public_key(
self.ca_cert.public_key()
),
critical=False,
)
try:
certificate = cert_builder.sign(
private_key=self.ca_private_key, algorithm=self.digest,
backend=default_backend()
private_key=self.ca_private_key,
algorithm=self.digest,
backend=default_backend(),
)
except TypeError as e:
if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None:
self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
if (
str(e) == "Algorithm must be a registered hash algorithm."
and self.digest is None
):
self.module.fail_json(
msg="Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer."
)
raise
self.cert = certificate
@@ -186,13 +221,17 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
return self.cert.public_bytes(Encoding.PEM)
def needs_regeneration(self):
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(
not_before=self.notBefore, not_after=self.notAfter
):
return True
self._ensure_existing_certificate_loaded()
# Check whether certificate is signed by CA certificate
if not cryptography_verify_certificate_signature(self.existing_certificate, self.ca_cert.public_key()):
if not cryptography_verify_certificate_signature(
self.existing_certificate, self.ca_cert.public_key()
):
return True
# Check subject
@@ -202,17 +241,27 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
# Check AuthorityKeyIdentifier
if self.create_authority_key_identifier:
try:
ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
ext = self.ca_cert.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier
)
expected_ext = (
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value)
if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext)
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
ext.value
)
if CRYPTOGRAPHY_VERSION >= LooseVersion("2.7")
else x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
ext
)
)
except cryptography.x509.ExtensionNotFound:
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(
self.ca_cert.public_key()
)
try:
ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
ext = self.existing_certificate.extensions.get_extension_for_class(
x509.AuthorityKeyIdentifier
)
if ext.value != expected_ext:
return True
except cryptography.x509.ExtensionNotFound:
@@ -221,26 +270,38 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
return False
def dump(self, include_certificate):
result = super(OwnCACertificateBackendCryptography, self).dump(include_certificate)
result.update({
'ca_cert': self.ca_cert_path,
'ca_privatekey': self.ca_privatekey_path,
})
result = super(OwnCACertificateBackendCryptography, self).dump(
include_certificate
)
result.update(
{
"ca_cert": self.ca_cert_path,
"ca_privatekey": self.ca_privatekey_path,
}
)
if self.module.check_mode:
result.update({
'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"),
'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"),
'serial_number': self.serial_number,
})
result.update(
{
"notBefore": self.notBefore.strftime("%Y%m%d%H%M%SZ"),
"notAfter": self.notAfter.strftime("%Y%m%d%H%M%SZ"),
"serial_number": self.serial_number,
}
)
else:
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
'serial_number': cryptography_serial_number_of_cert(self.cert),
})
result.update(
{
"notBefore": get_not_valid_before(self.cert).strftime(
"%Y%m%d%H%M%SZ"
),
"notAfter": get_not_valid_after(self.cert).strftime(
"%Y%m%d%H%M%SZ"
),
"serial_number": cryptography_serial_number_of_cert(self.cert),
}
)
return result
@@ -255,39 +316,53 @@ def generate_serial_number():
class OwnCACertificateProvider(CertificateProvider):
def validate_module_args(self, module):
if module.params['ownca_path'] is None and module.params['ownca_content'] is None:
module.fail_json(msg='One of ownca_path and ownca_content must be specified for the ownca provider.')
if module.params['ownca_privatekey_path'] is None and module.params['ownca_privatekey_content'] is None:
module.fail_json(msg='One of ownca_privatekey_path and ownca_privatekey_content must be specified for the ownca provider.')
if (
module.params["ownca_path"] is None
and module.params["ownca_content"] is None
):
module.fail_json(
msg="One of ownca_path and ownca_content must be specified for the ownca provider."
)
if (
module.params["ownca_privatekey_path"] is None
and module.params["ownca_privatekey_content"] is None
):
module.fail_json(
msg="One of ownca_privatekey_path and ownca_privatekey_content must be specified for the ownca provider."
)
def needs_version_two_certs(self, module):
return module.params['ownca_version'] == 2
return module.params["ownca_version"] == 2
def create_backend(self, module, backend):
if backend == 'cryptography':
if backend == "cryptography":
return OwnCACertificateBackendCryptography(module)
def add_ownca_provider_to_argument_spec(argument_spec):
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),
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),
))
argument_spec.mutually_exclusive.extend([
['ownca_path', 'ownca_content'],
['ownca_privatekey_path', 'ownca_privatekey_content'],
])
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),
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),
)
)
argument_spec.mutually_exclusive.extend(
[
["ownca_path", "ownca_content"],
["ownca_privatekey_path", "ownca_privatekey_content"],
]
)

View File

@@ -48,32 +48,38 @@ except ImportError:
class SelfSignedCertificateBackendCryptography(CertificateBackend):
def __init__(self, module):
super(SelfSignedCertificateBackendCryptography, self).__init__(module, 'cryptography')
super(SelfSignedCertificateBackendCryptography, self).__init__(
module, "cryptography"
)
self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier']
self.create_subject_key_identifier = module.params[
"selfsigned_create_subject_key_identifier"
]
self.notBefore = get_relative_time_option(
module.params['selfsigned_not_before'],
'selfsigned_not_before',
module.params["selfsigned_not_before"],
"selfsigned_not_before",
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.notAfter = get_relative_time_option(
module.params['selfsigned_not_after'],
'selfsigned_not_after',
module.params["selfsigned_not_after"],
"selfsigned_not_after",
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.digest = select_message_digest(module.params['selfsigned_digest'])
self.version = module.params['selfsigned_version']
self.digest = select_message_digest(module.params["selfsigned_digest"])
self.version = module.params["selfsigned_version"]
self.serial_number = x509.random_serial_number()
if self.csr_path is not None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
"The certificate signing request file {0} does not exist".format(
self.csr_path
)
)
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
raise CertificateError(
'The private key file {0} does not exist'.format(self.privatekey_path)
"The private key file {0} does not exist".format(self.privatekey_path)
)
self._module = module
@@ -89,18 +95,28 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
if cryptography_key_needs_digest_for_signing(self.privatekey):
digest = self.digest
if digest is None:
self.module.fail_json(msg='Unsupported digest "{0}"'.format(module.params['selfsigned_digest']))
self.module.fail_json(
msg='Unsupported digest "{0}"'.format(
module.params["selfsigned_digest"]
)
)
try:
self.csr = csr.sign(self.privatekey, digest, default_backend())
except TypeError as e:
if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None:
self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
if (
str(e) == "Algorithm must be a registered hash algorithm."
and digest is None
):
self.module.fail_json(
msg="Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer."
)
raise
if cryptography_key_needs_digest_for_signing(self.privatekey):
if self.digest is None:
raise CertificateError(
'The digest %s is not supported with the cryptography backend' % module.params['selfsigned_digest']
"The digest %s is not supported with the cryptography backend"
% module.params["selfsigned_digest"]
)
else:
self.digest = None
@@ -118,26 +134,36 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
has_ski = False
for extension in self.csr.extensions:
if isinstance(extension.value, x509.SubjectKeyIdentifier):
if self.create_subject_key_identifier == 'always_create':
if self.create_subject_key_identifier == "always_create":
continue
has_ski = True
cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical)
if not has_ski and self.create_subject_key_identifier != 'never_create':
cert_builder = cert_builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()),
critical=False
extension.value, critical=extension.critical
)
if not has_ski and self.create_subject_key_identifier != "never_create":
cert_builder = cert_builder.add_extension(
x509.SubjectKeyIdentifier.from_public_key(
self.privatekey.public_key()
),
critical=False,
)
except ValueError as e:
raise CertificateError(str(e))
try:
certificate = cert_builder.sign(
private_key=self.privatekey, algorithm=self.digest,
backend=default_backend()
private_key=self.privatekey,
algorithm=self.digest,
backend=default_backend(),
)
except TypeError as e:
if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None:
self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
if (
str(e) == "Algorithm must be a registered hash algorithm."
and self.digest is None
):
self.module.fail_json(
msg="Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer."
)
raise
self.cert = certificate
@@ -147,34 +173,48 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
return self.cert.public_bytes(Encoding.PEM)
def needs_regeneration(self):
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(
not_before=self.notBefore, not_after=self.notAfter
):
return True
self._ensure_existing_certificate_loaded()
# Check whether certificate is signed by private key
if not cryptography_verify_certificate_signature(self.existing_certificate, self.privatekey.public_key()):
if not cryptography_verify_certificate_signature(
self.existing_certificate, self.privatekey.public_key()
):
return True
return False
def dump(self, include_certificate):
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)
result = super(SelfSignedCertificateBackendCryptography, self).dump(
include_certificate
)
if self.module.check_mode:
result.update({
'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"),
'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"),
'serial_number': self.serial_number,
})
result.update(
{
"notBefore": self.notBefore.strftime("%Y%m%d%H%M%SZ"),
"notAfter": self.notAfter.strftime("%Y%m%d%H%M%SZ"),
"serial_number": self.serial_number,
}
)
else:
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
'serial_number': cryptography_serial_number_of_cert(self.cert),
})
result.update(
{
"notBefore": get_not_valid_before(self.cert).strftime(
"%Y%m%d%H%M%SZ"
),
"notAfter": get_not_valid_after(self.cert).strftime(
"%Y%m%d%H%M%SZ"
),
"serial_number": cryptography_serial_number_of_cert(self.cert),
}
)
return result
@@ -189,27 +229,38 @@ def generate_serial_number():
class SelfSignedCertificateProvider(CertificateProvider):
def validate_module_args(self, module):
if module.params['privatekey_path'] is None and module.params['privatekey_content'] is None:
module.fail_json(msg='One of privatekey_path and privatekey_content must be specified for the selfsigned provider.')
if (
module.params["privatekey_path"] is None
and module.params["privatekey_content"] is None
):
module.fail_json(
msg="One of privatekey_path and privatekey_content must be specified for the selfsigned provider."
)
def needs_version_two_certs(self, module):
return module.params['selfsigned_version'] == 2
return module.params["selfsigned_version"] == 2
def create_backend(self, module, backend):
if backend == 'cryptography':
if backend == "cryptography":
return SelfSignedCertificateBackendCryptography(module)
def add_selfsigned_provider_to_argument_spec(argument_spec):
argument_spec.argument_spec['provider']['choices'].append('selfsigned')
argument_spec.argument_spec.update(dict(
selfsigned_version=dict(type='int', default=3),
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']
),
))
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
argument_spec.argument_spec.update(
dict(
selfsigned_version=dict(type="int", default=3),
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"],
),
)
)

View File

@@ -18,14 +18,16 @@ from ansible_collections.community.crypto.plugins.module_utils.argspec import (
class ArgumentSpec(_ArgumentSpec):
def create_ansible_module_helper(self, clazz, args, **kwargs):
result = super(ArgumentSpec, self).create_ansible_module_helper(clazz, args, **kwargs)
result = super(ArgumentSpec, self).create_ansible_module_helper(
clazz, args, **kwargs
)
result.deprecate(
"The crypto.module_backends.common module utils is deprecated and will be removed from community.crypto 3.0.0."
" Use the argspec module utils from community.crypto instead.",
version='3.0.0',
collection_name='community.crypto',
version="3.0.0",
collection_name="community.crypto",
)
return result
__all__ = ('AnsibleModule', 'ArgumentSpec')
__all__ = ("AnsibleModule", "ArgumentSpec")

View File

@@ -32,13 +32,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
# crypto_utils
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.backends import default_backend
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -53,7 +54,7 @@ class CRLInfoRetrieval(object):
self.module = module
self.content = content
self.list_revoked_certificates = list_revoked_certificates
self.name_encoding = module.params.get('name_encoding', 'ignore')
self.name_encoding = module.params.get("name_encoding", "ignore")
def get_info(self):
self.crl_pem = identify_pem_format(self.content)
@@ -63,41 +64,51 @@ class CRLInfoRetrieval(object):
else:
self.crl = x509.load_der_x509_crl(self.content, default_backend())
except ValueError as e:
self.module.fail_json(msg='Error while decoding CRL: {0}'.format(e))
self.module.fail_json(msg="Error while decoding CRL: {0}".format(e))
result = {
'changed': False,
'format': 'pem' if self.crl_pem else 'der',
'last_update': None,
'next_update': None,
'digest': None,
'issuer_ordered': None,
'issuer': None,
"changed": False,
"format": "pem" if self.crl_pem else "der",
"last_update": None,
"next_update": None,
"digest": None,
"issuer_ordered": None,
"issuer": None,
}
result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
result["last_update"] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
result["next_update"] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
result["digest"] = cryptography_oid_to_name(
cryptography_get_signature_algorithm_oid_from_crl(self.crl)
)
issuer = []
for attribute in self.crl.issuer:
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
result['issuer_ordered'] = issuer
result['issuer'] = {}
result["issuer_ordered"] = issuer
result["issuer"] = {}
for k, v in issuer:
result['issuer'][k] = v
result["issuer"][k] = v
if self.list_revoked_certificates:
result['revoked_certificates'] = []
result["revoked_certificates"] = []
for cert in self.crl:
entry = cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding))
result["revoked_certificates"].append(
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
)
return result
def get_crl_info(module, content, list_revoked_certificates=True):
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
info = CRLInfoRetrieval(module, content, list_revoked_certificates=list_revoked_certificates)
info = CRLInfoRetrieval(
module, content, list_revoked_certificates=list_revoked_certificates
)
return info.get_info()

View File

@@ -51,7 +51,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -62,13 +62,16 @@ try:
import cryptography.hazmat.primitives.serialization
import cryptography.x509
import cryptography.x509.oid
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
CRYPTOGRAPHY_MUST_STAPLE_NAME = cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
CRYPTOGRAPHY_MUST_STAPLE_NAME = cryptography.x509.oid.ObjectIdentifier(
"1.3.6.1.5.5.7.1.24"
)
CRYPTOGRAPHY_MUST_STAPLE_VALUE = b"\x30\x03\x02\x01\x05"
@@ -88,80 +91,107 @@ class CertificateSigningRequestBackend(object):
def __init__(self, module, backend):
self.module = module
self.backend = backend
self.digest = module.params['digest']
self.privatekey_path = module.params['privatekey_path']
self.privatekey_content = module.params['privatekey_content']
self.digest = module.params["digest"]
self.privatekey_path = module.params["privatekey_path"]
self.privatekey_content = module.params["privatekey_content"]
if self.privatekey_content is not None:
self.privatekey_content = self.privatekey_content.encode('utf-8')
self.privatekey_passphrase = module.params['privatekey_passphrase']
self.version = module.params['version']
self.subjectAltName = module.params['subject_alt_name']
self.subjectAltName_critical = module.params['subject_alt_name_critical']
self.keyUsage = module.params['key_usage']
self.keyUsage_critical = module.params['key_usage_critical']
self.extendedKeyUsage = module.params['extended_key_usage']
self.extendedKeyUsage_critical = module.params['extended_key_usage_critical']
self.basicConstraints = module.params['basic_constraints']
self.basicConstraints_critical = module.params['basic_constraints_critical']
self.ocspMustStaple = module.params['ocsp_must_staple']
self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical']
self.name_constraints_permitted = module.params['name_constraints_permitted'] or []
self.name_constraints_excluded = module.params['name_constraints_excluded'] or []
self.name_constraints_critical = module.params['name_constraints_critical']
self.create_subject_key_identifier = module.params['create_subject_key_identifier']
self.subject_key_identifier = module.params['subject_key_identifier']
self.authority_key_identifier = module.params['authority_key_identifier']
self.authority_cert_issuer = module.params['authority_cert_issuer']
self.authority_cert_serial_number = module.params['authority_cert_serial_number']
self.crl_distribution_points = module.params['crl_distribution_points']
self.privatekey_content = self.privatekey_content.encode("utf-8")
self.privatekey_passphrase = module.params["privatekey_passphrase"]
self.version = module.params["version"]
self.subjectAltName = module.params["subject_alt_name"]
self.subjectAltName_critical = module.params["subject_alt_name_critical"]
self.keyUsage = module.params["key_usage"]
self.keyUsage_critical = module.params["key_usage_critical"]
self.extendedKeyUsage = module.params["extended_key_usage"]
self.extendedKeyUsage_critical = module.params["extended_key_usage_critical"]
self.basicConstraints = module.params["basic_constraints"]
self.basicConstraints_critical = module.params["basic_constraints_critical"]
self.ocspMustStaple = module.params["ocsp_must_staple"]
self.ocspMustStaple_critical = module.params["ocsp_must_staple_critical"]
self.name_constraints_permitted = (
module.params["name_constraints_permitted"] or []
)
self.name_constraints_excluded = (
module.params["name_constraints_excluded"] or []
)
self.name_constraints_critical = module.params["name_constraints_critical"]
self.create_subject_key_identifier = module.params[
"create_subject_key_identifier"
]
self.subject_key_identifier = module.params["subject_key_identifier"]
self.authority_key_identifier = module.params["authority_key_identifier"]
self.authority_cert_issuer = module.params["authority_cert_issuer"]
self.authority_cert_serial_number = module.params[
"authority_cert_serial_number"
]
self.crl_distribution_points = module.params["crl_distribution_points"]
self.csr = None
self.privatekey = None
if self.create_subject_key_identifier and self.subject_key_identifier is not None:
module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true')
if (
self.create_subject_key_identifier
and self.subject_key_identifier is not None
):
module.fail_json(
msg="subject_key_identifier cannot be specified if create_subject_key_identifier is true"
)
self.ordered_subject = False
self.subject = [
('C', module.params['country_name']),
('ST', module.params['state_or_province_name']),
('L', module.params['locality_name']),
('O', module.params['organization_name']),
('OU', module.params['organizational_unit_name']),
('CN', module.params['common_name']),
('emailAddress', module.params['email_address']),
("C", module.params["country_name"]),
("ST", module.params["state_or_province_name"]),
("L", module.params["locality_name"]),
("O", module.params["organization_name"]),
("OU", module.params["organizational_unit_name"]),
("CN", module.params["common_name"]),
("emailAddress", module.params["email_address"]),
]
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
try:
if module.params['subject']:
self.subject = self.subject + parse_name_field(module.params['subject'], 'subject')
if module.params['subject_ordered']:
if module.params["subject"]:
self.subject = self.subject + parse_name_field(
module.params["subject"], "subject"
)
if module.params["subject_ordered"]:
if self.subject:
raise CertificateSigningRequestError('subject_ordered cannot be combined with any other subject field')
self.subject = parse_ordered_name_field(module.params['subject_ordered'], 'subject_ordered')
raise CertificateSigningRequestError(
"subject_ordered cannot be combined with any other subject field"
)
self.subject = parse_ordered_name_field(
module.params["subject_ordered"], "subject_ordered"
)
self.ordered_subject = True
except ValueError as exc:
raise CertificateSigningRequestError(to_native(exc))
self.using_common_name_for_san = False
if not self.subjectAltName and module.params['use_common_name_for_san']:
if not self.subjectAltName and module.params["use_common_name_for_san"]:
for sub in self.subject:
if sub[0] in ('commonName', 'CN'):
self.subjectAltName = ['DNS:%s' % sub[1]]
if sub[0] in ("commonName", "CN"):
self.subjectAltName = ["DNS:%s" % sub[1]]
self.using_common_name_for_san = True
break
if self.subject_key_identifier is not None:
try:
self.subject_key_identifier = binascii.unhexlify(self.subject_key_identifier.replace(':', ''))
self.subject_key_identifier = binascii.unhexlify(
self.subject_key_identifier.replace(":", "")
)
except Exception as e:
raise CertificateSigningRequestError('Cannot parse subject_key_identifier: {0}'.format(e))
raise CertificateSigningRequestError(
"Cannot parse subject_key_identifier: {0}".format(e)
)
if self.authority_key_identifier is not None:
try:
self.authority_key_identifier = binascii.unhexlify(self.authority_key_identifier.replace(':', ''))
self.authority_key_identifier = binascii.unhexlify(
self.authority_key_identifier.replace(":", "")
)
except Exception as e:
raise CertificateSigningRequestError('Cannot parse authority_key_identifier: {0}'.format(e))
raise CertificateSigningRequestError(
"Cannot parse authority_key_identifier: {0}".format(e)
)
self.existing_csr = None
self.existing_csr_bytes = None
@@ -174,8 +204,13 @@ class CertificateSigningRequestBackend(object):
return dict()
try:
result = get_csr_info(
self.module, self.backend, data, validate_signature=False, prefer_one_fingerprint=True)
result['can_parse_csr'] = True
self.module,
self.backend,
data,
validate_signature=False,
prefer_one_fingerprint=True,
)
result["can_parse_csr"] = True
return result
except Exception:
return dict(can_parse_csr=False)
@@ -223,7 +258,9 @@ class CertificateSigningRequestBackend(object):
if self.existing_csr_bytes is None:
return True
try:
self.existing_csr = load_certificate_request(None, content=self.existing_csr_bytes, backend=self.backend)
self.existing_csr = load_certificate_request(
None, content=self.existing_csr_bytes, backend=self.backend
)
except Exception:
return True
self._ensure_private_key_loaded()
@@ -232,15 +269,15 @@ class CertificateSigningRequestBackend(object):
def dump(self, include_csr):
"""Serialize the object into a dictionary."""
result = {
'privatekey': self.privatekey_path,
'subject': self.subject,
'subjectAltName': self.subjectAltName,
'keyUsage': self.keyUsage,
'extendedKeyUsage': self.extendedKeyUsage,
'basicConstraints': self.basicConstraints,
'ocspMustStaple': self.ocspMustStaple,
'name_constraints_permitted': self.name_constraints_permitted,
'name_constraints_excluded': self.name_constraints_excluded,
"privatekey": self.privatekey_path,
"subject": self.subject,
"subjectAltName": self.subjectAltName,
"keyUsage": self.keyUsage,
"extendedKeyUsage": self.extendedKeyUsage,
"basicConstraints": self.basicConstraints,
"ocspMustStaple": self.ocspMustStaple,
"name_constraints_permitted": self.name_constraints_permitted,
"name_constraints_excluded": self.name_constraints_excluded,
}
# Get hold of CSR bytes
csr_bytes = self.existing_csr_bytes
@@ -249,9 +286,9 @@ class CertificateSigningRequestBackend(object):
self.diff_after = self._get_info(csr_bytes)
if include_csr:
# Store result
result['csr'] = csr_bytes.decode('utf-8') if csr_bytes else None
result["csr"] = csr_bytes.decode("utf-8") if csr_bytes else None
result['diff'] = dict(
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
@@ -268,45 +305,67 @@ def parse_crl_distribution_points(module, crl_distribution_points):
crl_issuer=None,
reasons=None,
)
if parse_crl_distribution_point['full_name'] is not None:
if not parse_crl_distribution_point['full_name']:
raise OpenSSLObjectError('full_name must not be empty')
params['full_name'] = [cryptography_get_name(name, 'full name') for name in parse_crl_distribution_point['full_name']]
if parse_crl_distribution_point['relative_name'] is not None:
if not parse_crl_distribution_point['relative_name']:
raise OpenSSLObjectError('relative_name must not be empty')
if parse_crl_distribution_point["full_name"] is not None:
if not parse_crl_distribution_point["full_name"]:
raise OpenSSLObjectError("full_name must not be empty")
params["full_name"] = [
cryptography_get_name(name, "full name")
for name in parse_crl_distribution_point["full_name"]
]
if parse_crl_distribution_point["relative_name"] is not None:
if not parse_crl_distribution_point["relative_name"]:
raise OpenSSLObjectError("relative_name must not be empty")
try:
params['relative_name'] = cryptography_parse_relative_distinguished_name(parse_crl_distribution_point['relative_name'])
params["relative_name"] = (
cryptography_parse_relative_distinguished_name(
parse_crl_distribution_point["relative_name"]
)
)
except Exception:
# If cryptography's version is < 1.6, the error is probably caused by that
if CRYPTOGRAPHY_VERSION < LooseVersion('1.6'):
raise OpenSSLObjectError('Cannot specify relative_name for cryptography < 1.6')
if CRYPTOGRAPHY_VERSION < LooseVersion("1.6"):
raise OpenSSLObjectError(
"Cannot specify relative_name for cryptography < 1.6"
)
raise
if parse_crl_distribution_point['crl_issuer'] is not None:
if not parse_crl_distribution_point['crl_issuer']:
raise OpenSSLObjectError('crl_issuer must not be empty')
params['crl_issuer'] = [cryptography_get_name(name, 'CRL issuer') for name in parse_crl_distribution_point['crl_issuer']]
if parse_crl_distribution_point['reasons'] is not None:
if parse_crl_distribution_point["crl_issuer"] is not None:
if not parse_crl_distribution_point["crl_issuer"]:
raise OpenSSLObjectError("crl_issuer must not be empty")
params["crl_issuer"] = [
cryptography_get_name(name, "CRL issuer")
for name in parse_crl_distribution_point["crl_issuer"]
]
if parse_crl_distribution_point["reasons"] is not None:
reasons = []
for reason in parse_crl_distribution_point['reasons']:
for reason in parse_crl_distribution_point["reasons"]:
reasons.append(REVOCATION_REASON_MAP[reason])
params['reasons'] = frozenset(reasons)
params["reasons"] = frozenset(reasons)
result.append(cryptography.x509.DistributionPoint(**params))
except (OpenSSLObjectError, ValueError) as e:
raise OpenSSLObjectError('Error while parsing CRL distribution point #{index}: {error}'.format(index=index, error=e))
raise OpenSSLObjectError(
"Error while parsing CRL distribution point #{index}: {error}".format(
index=index, error=e
)
)
return result
# Implementation with using cryptography
class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend):
def __init__(self, module):
super(CertificateSigningRequestCryptographyBackend, self).__init__(module, 'cryptography')
super(CertificateSigningRequestCryptographyBackend, self).__init__(
module, "cryptography"
)
self.cryptography_backend = cryptography.hazmat.backends.default_backend()
if self.version != 1:
module.warn('The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)')
module.warn(
"The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)"
)
if self.crl_distribution_points:
self.crl_distribution_points = parse_crl_distribution_points(module, self.crl_distribution_points)
self.crl_distribution_points = parse_crl_distribution_points(
module, self.crl_distribution_points
)
def generate_csr(self):
"""(Re-)Generate CSR."""
@@ -314,82 +373,145 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
csr = cryptography.x509.CertificateSigningRequestBuilder()
try:
csr = csr.subject_name(cryptography.x509.Name([
cryptography.x509.NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
]))
csr = csr.subject_name(
cryptography.x509.Name(
[
cryptography.x509.NameAttribute(
cryptography_name_to_oid(entry[0]), to_text(entry[1])
)
for entry in self.subject
]
)
)
except ValueError as e:
raise CertificateSigningRequestError(e)
if self.subjectAltName:
csr = csr.add_extension(cryptography.x509.SubjectAlternativeName([
cryptography_get_name(name) for name in self.subjectAltName
]), critical=self.subjectAltName_critical)
csr = csr.add_extension(
cryptography.x509.SubjectAlternativeName(
[cryptography_get_name(name) for name in self.subjectAltName]
),
critical=self.subjectAltName_critical,
)
if self.keyUsage:
params = cryptography_parse_key_usage_params(self.keyUsage)
csr = csr.add_extension(cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical)
csr = csr.add_extension(
cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical
)
if self.extendedKeyUsage:
usages = [cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages), critical=self.extendedKeyUsage_critical)
usages = [
cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage
]
csr = csr.add_extension(
cryptography.x509.ExtendedKeyUsage(usages),
critical=self.extendedKeyUsage_critical,
)
if self.basicConstraints:
params = {}
ca, path_length = cryptography_get_basic_constraints(self.basicConstraints)
csr = csr.add_extension(cryptography.x509.BasicConstraints(ca, path_length), critical=self.basicConstraints_critical)
csr = csr.add_extension(
cryptography.x509.BasicConstraints(ca, path_length),
critical=self.basicConstraints_critical,
)
if self.ocspMustStaple:
try:
# This only works with cryptography >= 2.1
csr = csr.add_extension(cryptography.x509.TLSFeature([cryptography.x509.TLSFeatureType.status_request]), critical=self.ocspMustStaple_critical)
csr = csr.add_extension(
cryptography.x509.TLSFeature(
[cryptography.x509.TLSFeatureType.status_request]
),
critical=self.ocspMustStaple_critical,
)
except AttributeError:
csr = csr.add_extension(
cryptography.x509.UnrecognizedExtension(CRYPTOGRAPHY_MUST_STAPLE_NAME, CRYPTOGRAPHY_MUST_STAPLE_VALUE),
critical=self.ocspMustStaple_critical
cryptography.x509.UnrecognizedExtension(
CRYPTOGRAPHY_MUST_STAPLE_NAME, CRYPTOGRAPHY_MUST_STAPLE_VALUE
),
critical=self.ocspMustStaple_critical,
)
if self.name_constraints_permitted or self.name_constraints_excluded:
try:
csr = csr.add_extension(cryptography.x509.NameConstraints(
[cryptography_get_name(name, 'name constraints permitted') for name in self.name_constraints_permitted] or None,
[cryptography_get_name(name, 'name constraints excluded') for name in self.name_constraints_excluded] or None,
), critical=self.name_constraints_critical)
csr = csr.add_extension(
cryptography.x509.NameConstraints(
[
cryptography_get_name(name, "name constraints permitted")
for name in self.name_constraints_permitted
]
or None,
[
cryptography_get_name(name, "name constraints excluded")
for name in self.name_constraints_excluded
]
or None,
),
critical=self.name_constraints_critical,
)
except TypeError as e:
raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e))
raise OpenSSLObjectError(
"Error while parsing name constraint: {0}".format(e)
)
if self.create_subject_key_identifier:
csr = csr.add_extension(
cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()),
critical=False
cryptography.x509.SubjectKeyIdentifier.from_public_key(
self.privatekey.public_key()
),
critical=False,
)
elif self.subject_key_identifier is not None:
csr = csr.add_extension(cryptography.x509.SubjectKeyIdentifier(self.subject_key_identifier), critical=False)
csr = csr.add_extension(
cryptography.x509.SubjectKeyIdentifier(self.subject_key_identifier),
critical=False,
)
if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
if (
self.authority_key_identifier is not None
or self.authority_cert_issuer is not None
or self.authority_cert_serial_number is not None
):
issuers = None
if self.authority_cert_issuer is not None:
issuers = [cryptography_get_name(n, 'authority cert issuer') for n in self.authority_cert_issuer]
issuers = [
cryptography_get_name(n, "authority cert issuer")
for n in self.authority_cert_issuer
]
csr = csr.add_extension(
cryptography.x509.AuthorityKeyIdentifier(self.authority_key_identifier, issuers, self.authority_cert_serial_number),
critical=False
cryptography.x509.AuthorityKeyIdentifier(
self.authority_key_identifier,
issuers,
self.authority_cert_serial_number,
),
critical=False,
)
if self.crl_distribution_points:
csr = csr.add_extension(
cryptography.x509.CRLDistributionPoints(self.crl_distribution_points),
critical=False
critical=False,
)
digest = None
if cryptography_key_needs_digest_for_signing(self.privatekey):
digest = select_message_digest(self.digest)
if digest is None:
raise CertificateSigningRequestError('Unsupported digest "{0}"'.format(self.digest))
raise CertificateSigningRequestError(
'Unsupported digest "{0}"'.format(self.digest)
)
try:
self.csr = csr.sign(self.privatekey, digest, self.cryptography_backend)
except TypeError as e:
if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None:
self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
if (
str(e) == "Algorithm must be a registered hash algorithm."
and digest is None
):
self.module.fail_json(
msg="Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer."
)
raise
except UnicodeError as e:
# This catches IDNAErrors, which happens when a bad name is passed as a SAN
@@ -402,20 +524,32 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
# https://github.com/kjd/idna/commit/ebefacd3134d0f5da4745878620a6a1cba86d130
# and then
# https://github.com/kjd/idna/commit/ea03c7b5db7d2a99af082e0239da2b68aeea702a).
msg = 'Error while creating CSR: {0}\n'.format(e)
msg = "Error while creating CSR: {0}\n".format(e)
if self.using_common_name_for_san:
self.module.fail_json(msg=msg + 'This is probably caused because the Common Name is used as a SAN.'
' Specifying use_common_name_for_san=false might fix this.')
self.module.fail_json(msg=msg + 'This is probably caused by an invalid Subject Alternative DNS Name.')
self.module.fail_json(
msg=msg
+ "This is probably caused because the Common Name is used as a SAN."
" Specifying use_common_name_for_san=false might fix this."
)
self.module.fail_json(
msg=msg
+ "This is probably caused by an invalid Subject Alternative DNS Name."
)
def get_csr_data(self):
"""Return bytes for self.csr."""
return self.csr.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM)
return self.csr.public_bytes(
cryptography.hazmat.primitives.serialization.Encoding.PEM
)
def _check_csr(self):
"""Check whether provided parameters, assuming self.existing_csr and self.privatekey have been populated."""
def _check_subject(csr):
subject = [(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject]
subject = [
(cryptography_name_to_oid(entry[0]), to_text(entry[1]))
for entry in self.subject
]
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
if self.ordered_subject:
return subject == current_subject
@@ -424,14 +558,26 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _find_extension(extensions, exttype):
return next(
(ext for ext in extensions if isinstance(ext.value, exttype)),
None
(ext for ext in extensions if isinstance(ext.value, exttype)), None
)
def _check_subjectAltName(extensions):
current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName)
current_altnames = [to_text(altname) for altname in current_altnames_ext.value] if current_altnames_ext else []
altnames = [to_text(cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
current_altnames_ext = _find_extension(
extensions, cryptography.x509.SubjectAlternativeName
)
current_altnames = (
[to_text(altname) for altname in current_altnames_ext.value]
if current_altnames_ext
else []
)
altnames = (
[
to_text(cryptography_get_name(altname))
for altname in self.subjectAltName
]
if self.subjectAltName
else []
)
if set(altnames) != set(current_altnames):
return False
if altnames:
@@ -440,23 +586,38 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
return True
def _check_keyUsage(extensions):
current_keyusage_ext = _find_extension(extensions, cryptography.x509.KeyUsage)
current_keyusage_ext = _find_extension(
extensions, cryptography.x509.KeyUsage
)
if not self.keyUsage:
return current_keyusage_ext is None
elif 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]:
if getattr(current_keyusage_ext.value, "_" + param) != params[param]:
return False
if current_keyusage_ext.critical != self.keyUsage_critical:
return False
return True
def _check_extenededKeyUsage(extensions):
current_usages_ext = _find_extension(extensions, cryptography.x509.ExtendedKeyUsage)
current_usages = [str(usage) for usage in current_usages_ext.value] if current_usages_ext else []
usages = [str(cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
current_usages_ext = _find_extension(
extensions, cryptography.x509.ExtendedKeyUsage
)
current_usages = (
[str(usage) for usage in current_usages_ext.value]
if current_usages_ext
else []
)
usages = (
[
str(cryptography_name_to_oid(usage))
for usage in self.extendedKeyUsage
]
if self.extendedKeyUsage
else []
)
if set(current_usages) != set(usages):
return False
if usages:
@@ -477,38 +638,77 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
return False
# Check criticality
if self.basicConstraints:
return bc_ext is not None and bc_ext.critical == self.basicConstraints_critical
return (
bc_ext is not None
and bc_ext.critical == self.basicConstraints_critical
)
else:
return bc_ext is None
def _check_ocspMustStaple(extensions):
try:
# This only works with cryptography >= 2.1
tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
tlsfeature_ext = _find_extension(
extensions, cryptography.x509.TLSFeature
)
has_tlsfeature = True
except AttributeError:
tlsfeature_ext = next(
(ext for ext in extensions if ext.value.oid == CRYPTOGRAPHY_MUST_STAPLE_NAME),
None
(
ext
for ext in extensions
if ext.value.oid == CRYPTOGRAPHY_MUST_STAPLE_NAME
),
None,
)
has_tlsfeature = False
if self.ocspMustStaple:
if not tlsfeature_ext or tlsfeature_ext.critical != self.ocspMustStaple_critical:
if (
not tlsfeature_ext
or tlsfeature_ext.critical != self.ocspMustStaple_critical
):
return False
if has_tlsfeature:
return cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
return (
cryptography.x509.TLSFeatureType.status_request
in tlsfeature_ext.value
)
else:
return tlsfeature_ext.value.value == CRYPTOGRAPHY_MUST_STAPLE_VALUE
else:
return tlsfeature_ext is None
def _check_nameConstraints(extensions):
current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints)
current_nc_perm = [to_text(altname) for altname in current_nc_ext.value.permitted_subtrees or []] if current_nc_ext else []
current_nc_excl = [to_text(altname) for altname in current_nc_ext.value.excluded_subtrees or []] if current_nc_ext else []
nc_perm = [to_text(cryptography_get_name(altname, 'name constraints permitted')) for altname in self.name_constraints_permitted]
nc_excl = [to_text(cryptography_get_name(altname, 'name constraints excluded')) for altname in self.name_constraints_excluded]
if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl):
current_nc_ext = _find_extension(
extensions, cryptography.x509.NameConstraints
)
current_nc_perm = (
[
to_text(altname)
for altname in current_nc_ext.value.permitted_subtrees or []
]
if current_nc_ext
else []
)
current_nc_excl = (
[
to_text(altname)
for altname in current_nc_ext.value.excluded_subtrees or []
]
if current_nc_ext
else []
)
nc_perm = [
to_text(cryptography_get_name(altname, "name constraints permitted"))
for altname in self.name_constraints_permitted
]
nc_excl = [
to_text(cryptography_get_name(altname, "name constraints excluded"))
for altname in self.name_constraints_excluded
]
if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(
current_nc_excl
):
return False
if nc_perm or nc_excl:
if current_nc_ext.critical != self.name_constraints_critical:
@@ -517,11 +717,16 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _check_subject_key_identifier(extensions):
ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier)
if self.create_subject_key_identifier or self.subject_key_identifier is not None:
if (
self.create_subject_key_identifier
or self.subject_key_identifier is not None
):
if not ext or ext.critical:
return False
if self.create_subject_key_identifier:
digest = cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()).digest
digest = cryptography.x509.SubjectKeyIdentifier.from_public_key(
self.privatekey.public_key()
).digest
return ext.value.digest == digest
else:
return ext.value.digest == self.subject_key_identifier
@@ -530,18 +735,28 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _check_authority_key_identifier(extensions):
ext = _find_extension(extensions, cryptography.x509.AuthorityKeyIdentifier)
if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
if (
self.authority_key_identifier is not None
or self.authority_cert_issuer is not None
or self.authority_cert_serial_number is not None
):
if not ext or ext.critical:
return False
aci = None
csr_aci = None
if self.authority_cert_issuer is not None:
aci = [to_text(cryptography_get_name(n, 'authority cert issuer')) for n in self.authority_cert_issuer]
aci = [
to_text(cryptography_get_name(n, "authority cert issuer"))
for n in self.authority_cert_issuer
]
if ext.value.authority_cert_issuer is not None:
csr_aci = [to_text(n) for n in ext.value.authority_cert_issuer]
return (ext.value.key_identifier == self.authority_key_identifier
and csr_aci == aci
and ext.value.authority_cert_serial_number == self.authority_cert_serial_number)
return (
ext.value.key_identifier == self.authority_key_identifier
and csr_aci == aci
and ext.value.authority_cert_serial_number
== self.authority_cert_serial_number
)
else:
return ext is None
@@ -555,11 +770,17 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _check_extensions(csr):
extensions = csr.extensions
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
_check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and
_check_authority_key_identifier(extensions) and _check_nameConstraints(extensions) and
_check_crl_distribution_points(extensions))
return (
_check_subjectAltName(extensions)
and _check_keyUsage(extensions)
and _check_extenededKeyUsage(extensions)
and _check_basicConstraints(extensions)
and _check_ocspMustStaple(extensions)
and _check_subject_key_identifier(extensions)
and _check_authority_key_identifier(extensions)
and _check_nameConstraints(extensions)
and _check_crl_distribution_points(extensions)
)
def _check_signature(csr):
if not csr.is_signature_valid:
@@ -568,107 +789,154 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
# encode both public keys and compare PEMs.
key_a = csr.public_key().public_bytes(
cryptography.hazmat.primitives.serialization.Encoding.PEM,
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo,
)
key_b = self.privatekey.public_key().public_bytes(
cryptography.hazmat.primitives.serialization.Encoding.PEM,
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo,
)
return key_a == key_b
return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr)
return (
_check_subject(self.existing_csr)
and _check_extensions(self.existing_csr)
and _check_signature(self.existing_csr)
)
def select_backend(module, backend):
if backend == 'auto':
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect any of the required Python libraries "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect any of the required Python libraries "
"cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, CertificateSigningRequestCryptographyBackend(module)
else:
raise Exception('Unsupported value for backend: {0}'.format(backend))
raise Exception("Unsupported value for backend: {0}".format(backend))
def get_csr_argument_spec():
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=[
'key_compromise',
'ca_compromise',
'affiliation_changed',
'superseded',
'cessation_of_operation',
'certificate_hold',
'privilege_withdrawn',
'aa_compromise',
]),
),
mutually_exclusive=[('full_name', 'relative_name')],
required_one_of=[('full_name', 'relative_name', 'crl_issuer')],
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=[
"key_compromise",
"ca_compromise",
"affiliation_changed",
"superseded",
"cessation_of_operation",
"certificate_hold",
"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"]
),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
),
required_together=[
['authority_cert_issuer', 'authority_cert_serial_number'],
["authority_cert_issuer", "authority_cert_serial_number"],
],
mutually_exclusive=[
['privatekey_path', 'privatekey_content'],
['subject', 'subject_ordered'],
["privatekey_path", "privatekey_content"],
["subject", "subject_ordered"],
],
required_one_of=[
['privatekey_path', 'privatekey_content'],
["privatekey_path", "privatekey_content"],
],
)

View File

@@ -35,13 +35,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography import x509
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -116,67 +117,80 @@ class CSRInfoRetrieval(object):
def get_info(self, prefer_one_fingerprint=False):
result = dict()
self.csr = load_certificate_request(None, content=self.content, backend=self.backend)
self.csr = load_certificate_request(
None, content=self.content, backend=self.backend
)
subject = self._get_subject_ordered()
result['subject'] = dict()
result["subject"] = dict()
for k, v in subject:
result['subject'][k] = v
result['subject_ordered'] = subject
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
result["subject"][k] = v
result["subject_ordered"] = subject
result["key_usage"], result["key_usage_critical"] = self._get_key_usage()
result["extended_key_usage"], result["extended_key_usage_critical"] = (
self._get_extended_key_usage()
)
result["basic_constraints"], result["basic_constraints_critical"] = (
self._get_basic_constraints()
)
result["ocsp_must_staple"], result["ocsp_must_staple_critical"] = (
self._get_ocsp_must_staple()
)
result["subject_alt_name"], result["subject_alt_name_critical"] = (
self._get_subject_alt_name()
)
(
result['name_constraints_permitted'],
result['name_constraints_excluded'],
result['name_constraints_critical'],
result["name_constraints_permitted"],
result["name_constraints_excluded"],
result["name_constraints_critical"],
) = self._get_name_constraints()
result['public_key'] = to_native(self._get_public_key_pem())
result["public_key"] = to_native(self._get_public_key_pem())
public_key_info = get_publickey_info(
self.module,
self.backend,
key=self._get_public_key_object(),
prefer_one_fingerprint=prefer_one_fingerprint)
result.update({
'public_key_type': public_key_info['type'],
'public_key_data': public_key_info['public_data'],
'public_key_fingerprints': public_key_info['fingerprints'],
})
prefer_one_fingerprint=prefer_one_fingerprint,
)
result.update(
{
"public_key_type": public_key_info["type"],
"public_key_data": public_key_info["public_data"],
"public_key_fingerprints": public_key_info["fingerprints"],
}
)
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
ski = ":".join([ski[i : i + 2] for i in range(0, len(ski), 2)])
result["subject_key_identifier"] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
aki = ":".join([aki[i : i + 2] for i in range(0, len(aki), 2)])
result["authority_key_identifier"] = aki
result["authority_cert_issuer"] = aci
result["authority_cert_serial_number"] = acsn
result['extensions_by_oid'] = self._get_all_extensions()
result["extensions_by_oid"] = self._get_all_extensions()
result['signature_valid'] = self._is_signature_valid()
if self.validate_signature and not result['signature_valid']:
self.module.fail_json(
msg='CSR signature is invalid!',
**result
)
result["signature_valid"] = self._is_signature_valid()
if self.validate_signature and not result["signature_valid"]:
self.module.fail_json(msg="CSR signature is invalid!", **result)
return result
class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
"""Validate the supplied CSR, using the cryptography backend"""
def __init__(self, module, content, validate_signature):
super(CSRInfoRetrievalCryptography, self).__init__(module, 'cryptography', content, validate_signature)
self.name_encoding = module.params.get('name_encoding', 'ignore')
super(CSRInfoRetrievalCryptography, self).__init__(
module, "cryptography", content, validate_signature
)
self.name_encoding = module.params.get("name_encoding", "ignore")
def _get_subject_ordered(self):
result = []
@@ -199,44 +213,60 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
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
))
if key_usage["key_agreement"]:
key_usage.update(
dict(
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',
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(
[
key_usage_names[name]
for name, value in key_usage.items()
if value
]
),
current_key_ext.critical,
)
return sorted([
key_usage_names[name] for name, value in key_usage.items() if value
]), current_key_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_extended_key_usage(self):
try:
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
return sorted([
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
]), ext_keyusage_ext.critical
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(
x509.ExtendedKeyUsage
)
return (
sorted(
[cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value]
),
ext_keyusage_ext.critical,
)
except cryptography.x509.ExtensionNotFound:
return None, False
def _get_basic_constraints(self):
try:
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.BasicConstraints)
result = ['CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE')]
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(
x509.BasicConstraints
)
result = ["CA:{0}".format("TRUE" if ext_keyusage_ext.value.ca else "FALSE")]
if ext_keyusage_ext.value.path_length is not None:
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
result.append("pathlen:{0}".format(ext_keyusage_ext.value.path_length))
return sorted(result), ext_keyusage_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -245,8 +275,13 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
try:
try:
# This only works with cryptography >= 2.1
tlsfeature_ext = self.csr.extensions.get_extension_for_class(x509.TLSFeature)
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
tlsfeature_ext = self.csr.extensions.get_extension_for_class(
x509.TLSFeature
)
value = (
cryptography.x509.TLSFeatureType.status_request
in tlsfeature_ext.value
)
except AttributeError:
# Fallback for cryptography < 2.1
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
@@ -258,8 +293,13 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def _get_subject_alt_name(self):
try:
san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
san_ext = self.csr.extensions.get_extension_for_class(
x509.SubjectAlternativeName
)
result = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in san_ext.value
]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -267,8 +307,14 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def _get_name_constraints(self):
try:
nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints)
permitted = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.permitted_subtrees or []]
excluded = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.excluded_subtrees or []]
permitted = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in nc_ext.value.permitted_subtrees or []
]
excluded = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in nc_ext.value.excluded_subtrees or []
]
return permitted, excluded, nc_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, None, False
@@ -291,11 +337,20 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def _get_authority_key_identifier(self):
try:
ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
ext = self.csr.extensions.get_extension_for_class(
x509.AuthorityKeyIdentifier
)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
issuer = [
cryptography_decode_name(san, idn_rewrite=self.name_encoding)
for san in ext.value.authority_cert_issuer
]
return (
ext.value.key_identifier,
issuer,
ext.value.authority_cert_serial_number,
)
except cryptography.x509.ExtensionNotFound:
return None, None, None
@@ -306,30 +361,46 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
return self.csr.is_signature_valid
def get_csr_info(module, backend, content, validate_signature=True, prefer_one_fingerprint=False):
if backend == 'cryptography':
info = CSRInfoRetrievalCryptography(module, content, validate_signature=validate_signature)
def get_csr_info(
module, backend, content, validate_signature=True, prefer_one_fingerprint=False
):
if backend == "cryptography":
info = CSRInfoRetrievalCryptography(
module, content, validate_signature=validate_signature
)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
def select_backend(module, backend, content, validate_signature=True):
if backend == 'auto':
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect the required Python library " "cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
return backend, CSRInfoRetrievalCryptography(module, content, validate_signature=validate_signature)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, CSRInfoRetrievalCryptography(
module, content, validate_signature=validate_signature
)
else:
raise ValueError('Unsupported value for backend: {0}'.format(backend))
raise ValueError("Unsupported value for backend: {0}".format(backend))

View File

@@ -45,7 +45,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -57,6 +57,7 @@ try:
import cryptography.hazmat.primitives.asymmetric.rsa
import cryptography.hazmat.primitives.asymmetric.utils
import cryptography.hazmat.primitives.serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -80,14 +81,14 @@ class PrivateKeyError(OpenSSLObjectError):
class PrivateKeyBackend:
def __init__(self, module, backend):
self.module = module
self.type = module.params['type']
self.size = module.params['size']
self.curve = module.params['curve']
self.passphrase = module.params['passphrase']
self.cipher = module.params['cipher']
self.format = module.params['format']
self.format_mismatch = module.params.get('format_mismatch', 'regenerate')
self.regenerate = module.params.get('regenerate', 'full_idempotence')
self.type = module.params["type"]
self.size = module.params["size"]
self.curve = module.params["curve"]
self.passphrase = module.params["passphrase"]
self.cipher = module.params["cipher"]
self.format = module.params["format"]
self.format_mismatch = module.params.get("format_mismatch", "regenerate")
self.regenerate = module.params.get("regenerate", "full_idempotence")
self.backend = backend
self.private_key = None
@@ -103,9 +104,16 @@ class PrivateKeyBackend:
return dict()
result = dict(can_parse_key=False)
try:
result.update(get_privatekey_info(
self.module, self.backend, data, passphrase=self.passphrase,
return_private_key_data=False, prefer_one_fingerprint=True))
result.update(
get_privatekey_info(
self.module,
self.backend,
data,
passphrase=self.passphrase,
return_private_key_data=False,
prefer_one_fingerprint=True,
)
)
except PrivateKeyConsistencyError as exc:
result.update(exc.result)
except PrivateKeyParseError as exc:
@@ -137,7 +145,9 @@ class PrivateKeyBackend:
def set_existing(self, privatekey_bytes):
"""Set existing private key bytes. None indicates that the key does not exist."""
self.existing_private_key_bytes = privatekey_bytes
self.diff_after = self.diff_before = self._get_info(self.existing_private_key_bytes)
self.diff_after = self.diff_before = self._get_info(
self.existing_private_key_bytes
)
def has_existing(self):
"""Query whether an existing private key is/has been there."""
@@ -165,41 +175,51 @@ class PrivateKeyBackend:
def needs_regeneration(self):
"""Check whether a regeneration is necessary."""
if self.regenerate == 'always':
if self.regenerate == "always":
return True
if not self.has_existing():
# key does not exist
return True
if not self._check_passphrase():
if self.regenerate == 'full_idempotence':
if self.regenerate == "full_idempotence":
return True
self.module.fail_json(msg='Unable to read the key. The key is protected with a another passphrase / no passphrase or broken.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `full_idempotence` or `always`, or with `force=true`.')
self.module.fail_json(
msg="Unable to read the key. The key is protected with a another passphrase / no passphrase or broken."
" Will not proceed. To force regeneration, call the module with `generate`"
" set to `full_idempotence` or `always`, or with `force=true`."
)
self._ensure_existing_private_key_loaded()
if self.regenerate != 'never':
if self.regenerate != "never":
if not self._check_size_and_type():
if self.regenerate in ('partial_idempotence', 'full_idempotence'):
if self.regenerate in ("partial_idempotence", "full_idempotence"):
return True
self.module.fail_json(msg='Key has wrong type and/or size.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.')
self.module.fail_json(
msg="Key has wrong type and/or size."
" Will not proceed. To force regeneration, call the module with `generate`"
" set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`."
)
# During generation step, regenerate if format does not match and format_mismatch == 'regenerate'
if self.format_mismatch == 'regenerate' and self.regenerate != 'never':
if self.format_mismatch == "regenerate" and self.regenerate != "never":
if not self._check_format():
if self.regenerate in ('partial_idempotence', 'full_idempotence'):
if self.regenerate in ("partial_idempotence", "full_idempotence"):
return True
self.module.fail_json(msg='Key has wrong format.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.'
' To convert the key, set `format_mismatch` to `convert`.')
self.module.fail_json(
msg="Key has wrong format."
" Will not proceed. To force regeneration, call the module with `generate`"
" set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`."
" To convert the key, set `format_mismatch` to `convert`."
)
return False
def needs_conversion(self):
"""Check whether a conversion is necessary. Must only be called if needs_regeneration() returned False."""
# During conversion step, convert if format does not match and format_mismatch == 'convert'
self._ensure_existing_private_key_loaded()
return self.has_existing() and self.format_mismatch == 'convert' and not self._check_format()
return (
self.has_existing()
and self.format_mismatch == "convert"
and not self._check_format()
)
def _get_fingerprint(self):
if self.private_key:
@@ -210,7 +230,9 @@ class PrivateKeyBackend:
# Ignore errors
pass
if self.existing_private_key:
return get_fingerprint_of_privatekey(self.existing_private_key, backend=self.backend)
return get_fingerprint_of_privatekey(
self.existing_private_key, backend=self.backend
)
def dump(self, include_key):
"""Serialize the object into a dictionary."""
@@ -222,12 +244,12 @@ class PrivateKeyBackend:
# Ignore errors
pass
result = {
'type': self.type,
'size': self.size,
'fingerprint': self._get_fingerprint(),
"type": self.type,
"size": self.size,
"fingerprint": self._get_fingerprint(),
}
if self.type == 'ECC':
result['curve'] = self.curve
if self.type == "ECC":
result["curve"] = self.curve
# Get hold of private key bytes
pk_bytes = self.existing_private_key_bytes
if self.private_key is not None:
@@ -236,14 +258,14 @@ class PrivateKeyBackend:
if include_key:
# Store result
if pk_bytes:
if identify_private_key_format(pk_bytes) == 'raw':
result['privatekey'] = base64.b64encode(pk_bytes)
if identify_private_key_format(pk_bytes) == "raw":
result["privatekey"] = base64.b64encode(pk_bytes)
else:
result['privatekey'] = pk_bytes.decode('utf-8')
result["privatekey"] = pk_bytes.decode("utf-8")
else:
result['privatekey'] = None
result["privatekey"] = None
result['diff'] = dict(
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
@@ -256,7 +278,9 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
def _get_ec_class(self, ectype):
ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(ectype)
if ecclass is None:
self.module.fail_json(msg='Your cryptography version does not support {0}'.format(ectype))
self.module.fail_json(
msg="Your cryptography version does not support {0}".format(ectype)
)
return ecclass
def _add_curve(self, name, ectype, deprecated=False):
@@ -266,90 +290,123 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
def verify(privatekey):
ecclass = self._get_ec_class(ectype)
return isinstance(privatekey.private_numbers().public_numbers.curve, ecclass)
return isinstance(
privatekey.private_numbers().public_numbers.curve, ecclass
)
self.curves[name] = {
'create': create,
'verify': verify,
'deprecated': deprecated,
"create": create,
"verify": verify,
"deprecated": deprecated,
}
def __init__(self, module):
super(PrivateKeyCryptographyBackend, self).__init__(module=module, backend='cryptography')
super(PrivateKeyCryptographyBackend, self).__init__(
module=module, backend="cryptography"
)
self.curves = dict()
self._add_curve('secp224r1', 'SECP224R1')
self._add_curve('secp256k1', 'SECP256K1')
self._add_curve('secp256r1', 'SECP256R1')
self._add_curve('secp384r1', 'SECP384R1')
self._add_curve('secp521r1', 'SECP521R1')
self._add_curve('secp192r1', 'SECP192R1', deprecated=True)
self._add_curve('sect163k1', 'SECT163K1', deprecated=True)
self._add_curve('sect163r2', 'SECT163R2', deprecated=True)
self._add_curve('sect233k1', 'SECT233K1', deprecated=True)
self._add_curve('sect233r1', 'SECT233R1', deprecated=True)
self._add_curve('sect283k1', 'SECT283K1', deprecated=True)
self._add_curve('sect283r1', 'SECT283R1', deprecated=True)
self._add_curve('sect409k1', 'SECT409K1', deprecated=True)
self._add_curve('sect409r1', 'SECT409R1', deprecated=True)
self._add_curve('sect571k1', 'SECT571K1', deprecated=True)
self._add_curve('sect571r1', 'SECT571R1', deprecated=True)
self._add_curve('brainpoolP256r1', 'BrainpoolP256R1', deprecated=True)
self._add_curve('brainpoolP384r1', 'BrainpoolP384R1', deprecated=True)
self._add_curve('brainpoolP512r1', 'BrainpoolP512R1', deprecated=True)
self._add_curve("secp224r1", "SECP224R1")
self._add_curve("secp256k1", "SECP256K1")
self._add_curve("secp256r1", "SECP256R1")
self._add_curve("secp384r1", "SECP384R1")
self._add_curve("secp521r1", "SECP521R1")
self._add_curve("secp192r1", "SECP192R1", deprecated=True)
self._add_curve("sect163k1", "SECT163K1", deprecated=True)
self._add_curve("sect163r2", "SECT163R2", deprecated=True)
self._add_curve("sect233k1", "SECT233K1", deprecated=True)
self._add_curve("sect233r1", "SECT233R1", deprecated=True)
self._add_curve("sect283k1", "SECT283K1", deprecated=True)
self._add_curve("sect283r1", "SECT283R1", deprecated=True)
self._add_curve("sect409k1", "SECT409K1", deprecated=True)
self._add_curve("sect409r1", "SECT409R1", deprecated=True)
self._add_curve("sect571k1", "SECT571K1", deprecated=True)
self._add_curve("sect571r1", "SECT571R1", deprecated=True)
self._add_curve("brainpoolP256r1", "BrainpoolP256R1", deprecated=True)
self._add_curve("brainpoolP384r1", "BrainpoolP384R1", deprecated=True)
self._add_curve("brainpoolP512r1", "BrainpoolP512R1", deprecated=True)
self.cryptography_backend = cryptography.hazmat.backends.default_backend()
if not CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519':
self.module.fail_json(msg='Your cryptography version does not support X25519')
if not CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519':
self.module.fail_json(msg='Your cryptography version does not support X25519 serialization')
if not CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
self.module.fail_json(msg='Your cryptography version does not support X448')
if not CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
self.module.fail_json(msg='Your cryptography version does not support Ed25519')
if not CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
self.module.fail_json(msg='Your cryptography version does not support Ed448')
if not CRYPTOGRAPHY_HAS_X25519 and self.type == "X25519":
self.module.fail_json(
msg="Your cryptography version does not support X25519"
)
if not CRYPTOGRAPHY_HAS_X25519_FULL and self.type == "X25519":
self.module.fail_json(
msg="Your cryptography version does not support X25519 serialization"
)
if not CRYPTOGRAPHY_HAS_X448 and self.type == "X448":
self.module.fail_json(msg="Your cryptography version does not support X448")
if not CRYPTOGRAPHY_HAS_ED25519 and self.type == "Ed25519":
self.module.fail_json(
msg="Your cryptography version does not support Ed25519"
)
if not CRYPTOGRAPHY_HAS_ED448 and self.type == "Ed448":
self.module.fail_json(
msg="Your cryptography version does not support Ed448"
)
def _get_wanted_format(self):
if self.format not in ('auto', 'auto_ignore'):
if self.format not in ("auto", "auto_ignore"):
return self.format
if self.type in ('X25519', 'X448', 'Ed25519', 'Ed448'):
return 'pkcs8'
if self.type in ("X25519", "X448", "Ed25519", "Ed448"):
return "pkcs8"
else:
return 'pkcs1'
return "pkcs1"
def generate_private_key(self):
"""(Re-)Generate private key."""
try:
if self.type == 'RSA':
self.private_key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
public_exponent=65537, # OpenSSL always uses this
key_size=self.size,
backend=self.cryptography_backend
if self.type == "RSA":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
public_exponent=65537, # OpenSSL always uses this
key_size=self.size,
backend=self.cryptography_backend,
)
)
if self.type == 'DSA':
self.private_key = cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key(
key_size=self.size,
backend=self.cryptography_backend
if self.type == "DSA":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key(
key_size=self.size, backend=self.cryptography_backend
)
)
if CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519':
self.private_key = cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448':
self.private_key = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519':
self.private_key = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448':
self.private_key = cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate()
if self.type == 'ECC' and self.curve in self.curves:
if self.curves[self.curve]['deprecated']:
self.module.warn('Elliptic curves of type {0} should not be used for new keys!'.format(self.curve))
self.private_key = cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(
curve=self.curves[self.curve]['create'](self.size),
backend=self.cryptography_backend
if CRYPTOGRAPHY_HAS_X25519_FULL and self.type == "X25519":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
)
if CRYPTOGRAPHY_HAS_X448 and self.type == "X448":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate()
)
if CRYPTOGRAPHY_HAS_ED25519 and self.type == "Ed25519":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate()
)
if CRYPTOGRAPHY_HAS_ED448 and self.type == "Ed448":
self.private_key = (
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate()
)
if self.type == "ECC" and self.curve in self.curves:
if self.curves[self.curve]["deprecated"]:
self.module.warn(
"Elliptic curves of type {0} should not be used for new keys!".format(
self.curve
)
)
self.private_key = (
cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(
curve=self.curves[self.curve]["create"](self.size),
backend=self.cryptography_backend,
)
)
except cryptography.exceptions.UnsupportedAlgorithm:
self.module.fail_json(msg='Cryptography backend does not support the algorithm required for {0}'.format(self.type))
self.module.fail_json(
msg="Cryptography backend does not support the algorithm required for {0}".format(
self.type
)
)
def get_private_key_data(self):
"""Return bytes for self.private_key"""
@@ -357,40 +414,62 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
try:
export_format = self._get_wanted_format()
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.PEM
if export_format == 'pkcs1':
if export_format == "pkcs1":
# "TraditionalOpenSSL" format is PKCS1
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
elif export_format == 'pkcs8':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
elif export_format == 'raw':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.Raw
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
)
elif export_format == "pkcs8":
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
)
elif export_format == "raw":
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
)
export_encoding = (
cryptography.hazmat.primitives.serialization.Encoding.Raw
)
except AttributeError:
self.module.fail_json(msg='Cryptography backend does not support the selected output format "{0}"'.format(self.format))
self.module.fail_json(
msg='Cryptography backend does not support the selected output format "{0}"'.format(
self.format
)
)
# Select key encryption
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
encryption_algorithm = (
cryptography.hazmat.primitives.serialization.NoEncryption()
)
if self.cipher and self.passphrase:
if self.cipher == 'auto':
encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(to_bytes(self.passphrase))
if self.cipher == "auto":
encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(
to_bytes(self.passphrase)
)
else:
self.module.fail_json(msg='Cryptography backend can only use "auto" for cipher option.')
self.module.fail_json(
msg='Cryptography backend can only use "auto" for cipher option.'
)
# Serialize key
try:
return self.private_key.private_bytes(
encoding=export_encoding,
format=export_format,
encryption_algorithm=encryption_algorithm
encryption_algorithm=encryption_algorithm,
)
except ValueError:
self.module.fail_json(
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(self.format)
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(
self.format
)
)
except Exception:
self.module.fail_json(
msg='Error while serializing the private key in the required format "{0}"'.format(self.format),
exception=traceback.format_exc()
msg='Error while serializing the private key in the required format "{0}"'.format(
self.format
),
exception=traceback.format_exc(),
)
def _load_privatekey(self):
@@ -398,27 +477,45 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
try:
# Interpret bytes depending on format.
format = identify_private_key_format(data)
if format == 'raw':
if format == "raw":
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
data
)
if len(data) == 57 and CRYPTOGRAPHY_HAS_ED448:
return cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(data)
return cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
data
)
if len(data) == 32:
if CRYPTOGRAPHY_HAS_X25519 and (self.type == 'X25519' or not CRYPTOGRAPHY_HAS_ED25519):
return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
if CRYPTOGRAPHY_HAS_ED25519 and (self.type == 'Ed25519' or not CRYPTOGRAPHY_HAS_X25519):
return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
if CRYPTOGRAPHY_HAS_X25519 and (
self.type == "X25519" or not CRYPTOGRAPHY_HAS_ED25519
):
return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
)
if CRYPTOGRAPHY_HAS_ED25519 and (
self.type == "Ed25519" or not CRYPTOGRAPHY_HAS_X25519
):
return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
)
if CRYPTOGRAPHY_HAS_X25519 and CRYPTOGRAPHY_HAS_ED25519:
try:
return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
)
except Exception:
return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
raise PrivateKeyError('Cannot load raw key')
return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
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),
backend=self.cryptography_backend
return (
cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if self.passphrase is None else to_bytes(self.passphrase),
backend=self.cryptography_backend,
)
)
except Exception as e:
raise PrivateKeyError(e)
@@ -430,7 +527,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
def _check_passphrase(self):
try:
format = identify_private_key_format(self.existing_private_key_bytes)
if format == 'raw':
if 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()
@@ -438,38 +535,65 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
# provided.
return self.passphrase is None
else:
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
self.existing_private_key_bytes,
None if self.passphrase is None else to_bytes(self.passphrase),
backend=self.cryptography_backend
return (
cryptography.hazmat.primitives.serialization.load_pem_private_key(
self.existing_private_key_bytes,
None if self.passphrase is None else to_bytes(self.passphrase),
backend=self.cryptography_backend,
)
)
except Exception:
return False
def _check_size_and_type(self):
if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
return self.type == 'RSA' and self.size == self.existing_private_key.key_size
if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
return self.type == 'DSA' and self.size == self.existing_private_key.key_size
if CRYPTOGRAPHY_HAS_X25519 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
return self.type == 'X25519'
if CRYPTOGRAPHY_HAS_X448 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
return self.type == 'X448'
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
return self.type == 'Ed25519'
if CRYPTOGRAPHY_HAS_ED448 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
return self.type == 'Ed448'
if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
if self.type != 'ECC':
if isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey,
):
return (
self.type == "RSA" and self.size == self.existing_private_key.key_size
)
if isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey,
):
return (
self.type == "DSA" and self.size == self.existing_private_key.key_size
)
if CRYPTOGRAPHY_HAS_X25519 and isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey,
):
return self.type == "X25519"
if CRYPTOGRAPHY_HAS_X448 and isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey,
):
return self.type == "X448"
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey,
):
return self.type == "Ed25519"
if CRYPTOGRAPHY_HAS_ED448 and isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey,
):
return self.type == "Ed448"
if isinstance(
self.existing_private_key,
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey,
):
if self.type != "ECC":
return False
if self.curve not in self.curves:
return False
return self.curves[self.curve]['verify'](self.existing_private_key)
return self.curves[self.curve]["verify"](self.existing_private_key)
return False
def _check_format(self):
if self.format == 'auto_ignore':
if self.format == "auto_ignore":
return True
try:
format = identify_private_key_format(self.existing_private_key_bytes)
@@ -479,52 +603,96 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
def select_backend(module, backend):
if backend == 'auto':
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Decision
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'cryptography':
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect the required Python library " "cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, PrivateKeyCryptographyBackend(module)
else:
raise Exception('Unsupported value for backend: {0}'.format(backend))
raise Exception("Unsupported value for backend: {0}".format(backend))
def get_privatekey_argument_spec():
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=[
'secp224r1', 'secp256k1', 'secp256r1', 'secp384r1', 'secp521r1',
'secp192r1', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1',
'sect163k1', 'sect163r2', 'sect233k1', 'sect233r1', 'sect283k1',
'sect283r1', 'sect409k1', 'sect409r1', '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'),
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=[
"secp224r1",
"secp256k1",
"secp256r1",
"secp384r1",
"secp521r1",
"secp192r1",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"sect163k1",
"sect163r2",
"sect233k1",
"sect233r1",
"sect283k1",
"sect283r1",
"sect409k1",
"sect409r1",
"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=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
type="str",
default="full_idempotence",
choices=[
"never",
"fail",
"partial_idempotence",
"full_idempotence",
"always",
],
),
),
required_if=[
['type', 'ECC', ['curve']],
["type", "ECC", ["curve"]],
],
)

View File

@@ -38,7 +38,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -50,6 +50,7 @@ try:
import cryptography.hazmat.primitives.asymmetric.rsa
import cryptography.hazmat.primitives.asymmetric.utils
import cryptography.hazmat.primitives.serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -73,18 +74,18 @@ class PrivateKeyError(OpenSSLObjectError):
class PrivateKeyConvertBackend:
def __init__(self, module, backend):
self.module = module
self.src_path = module.params['src_path']
self.src_content = module.params['src_content']
self.src_passphrase = module.params['src_passphrase']
self.format = module.params['format']
self.dest_passphrase = module.params['dest_passphrase']
self.src_path = module.params["src_path"]
self.src_content = module.params["src_content"]
self.src_passphrase = module.params["src_passphrase"]
self.format = module.params["format"]
self.dest_passphrase = module.params["dest_passphrase"]
self.backend = backend
self.src_private_key = None
if self.src_path is not None:
self.src_private_key_bytes = load_file(self.src_path, module)
else:
self.src_private_key_bytes = self.src_content.encode('utf-8')
self.src_private_key_bytes = self.src_content.encode("utf-8")
self.dest_private_key = None
self.dest_private_key_bytes = None
@@ -109,17 +110,25 @@ class PrivateKeyConvertBackend:
def needs_conversion(self):
"""Check whether a conversion is necessary. Must only be called if needs_regeneration() returned False."""
dummy, self.src_private_key = self._load_private_key(self.src_private_key_bytes, self.src_passphrase)
dummy, self.src_private_key = self._load_private_key(
self.src_private_key_bytes, self.src_passphrase
)
if not self.has_existing_destination():
return True
try:
format, self.dest_private_key = self._load_private_key(self.dest_private_key_bytes, self.dest_passphrase, current_hint=self.src_private_key)
format, self.dest_private_key = self._load_private_key(
self.dest_private_key_bytes,
self.dest_passphrase,
current_hint=self.src_private_key,
)
except Exception:
return True
return format != self.format or not cryptography_compare_private_keys(self.dest_private_key, self.src_private_key)
return format != self.format or not cryptography_compare_private_keys(
self.dest_private_key, self.src_private_key
)
def dump(self):
"""Serialize the object into a dictionary."""
@@ -129,7 +138,9 @@ class PrivateKeyConvertBackend:
# Implementation with using cryptography
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
def __init__(self, module):
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module, backend='cryptography')
super(PrivateKeyConvertCryptographyBackend, self).__init__(
module=module, backend="cryptography"
)
self.cryptography_backend = cryptography.hazmat.backends.default_backend()
@@ -138,72 +149,140 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
# Select export format and encoding
try:
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.PEM
if self.format == 'pkcs1':
if self.format == "pkcs1":
# "TraditionalOpenSSL" format is PKCS1
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
elif self.format == 'pkcs8':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
elif self.format == 'raw':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.Raw
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
)
elif self.format == "pkcs8":
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
)
elif self.format == "raw":
export_format = (
cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
)
export_encoding = (
cryptography.hazmat.primitives.serialization.Encoding.Raw
)
except AttributeError:
self.module.fail_json(msg='Cryptography backend does not support the selected output format "{0}"'.format(self.format))
self.module.fail_json(
msg='Cryptography backend does not support the selected output format "{0}"'.format(
self.format
)
)
# Select key encryption
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
encryption_algorithm = (
cryptography.hazmat.primitives.serialization.NoEncryption()
)
if self.dest_passphrase:
encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(to_bytes(self.dest_passphrase))
encryption_algorithm = (
cryptography.hazmat.primitives.serialization.BestAvailableEncryption(
to_bytes(self.dest_passphrase)
)
)
# Serialize key
try:
return self.src_private_key.private_bytes(
encoding=export_encoding,
format=export_format,
encryption_algorithm=encryption_algorithm
encryption_algorithm=encryption_algorithm,
)
except ValueError:
self.module.fail_json(
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(self.format)
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(
self.format
)
)
except Exception:
self.module.fail_json(
msg='Error while serializing the private key in the required format "{0}"'.format(self.format),
exception=traceback.format_exc()
msg='Error while serializing the private key in the required format "{0}"'.format(
self.format
),
exception=traceback.format_exc(),
)
def _load_private_key(self, data, passphrase, current_hint=None):
try:
# Interpret bytes depending on format.
format = identify_private_key_format(data)
if format == 'raw':
if format == "raw":
if passphrase is not None:
raise PrivateKeyError('Cannot load raw key with passphrase')
raise PrivateKeyError("Cannot load raw key with passphrase")
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
return format, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
data
),
)
if len(data) == 57 and CRYPTOGRAPHY_HAS_ED448:
return format, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
data
),
)
if len(data) == 32:
if CRYPTOGRAPHY_HAS_X25519 and not CRYPTOGRAPHY_HAS_ED25519:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
),
)
if CRYPTOGRAPHY_HAS_ED25519 and not CRYPTOGRAPHY_HAS_X25519:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
),
)
if CRYPTOGRAPHY_HAS_X25519 and CRYPTOGRAPHY_HAS_ED25519:
if isinstance(current_hint, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
if isinstance(
current_hint,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey,
):
try:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
),
)
except Exception:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
),
)
else:
try:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
return (
format,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
),
)
except Exception:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
raise PrivateKeyError('Cannot load raw key')
return (
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),
backend=self.cryptography_backend
return (
format,
cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if passphrase is None else to_bytes(passphrase),
backend=self.cryptography_backend,
),
)
except Exception as e:
raise PrivateKeyError(e)
@@ -211,24 +290,28 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
def select_backend(module):
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return PrivateKeyConvertCryptographyBackend(module)
def get_privatekey_argument_spec():
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']),
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"]),
),
mutually_exclusive=[
['src_path', 'src_content'],
["src_path", "src_content"],
],
required_one_of=[
['src_path', 'src_content'],
["src_path", "src_content"],
],
)

View File

@@ -39,12 +39,13 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -52,7 +53,7 @@ except ImportError:
else:
CRYPTOGRAPHY_FOUND = True
SIGNATURE_TEST_DATA = b'1234'
SIGNATURE_TEST_DATA = b"1234"
def _get_cryptography_private_key_info(key, need_private_key_data=False):
@@ -61,25 +62,29 @@ def _get_cryptography_private_key_info(key, need_private_key_data=False):
if need_private_key_data:
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
private_numbers = key.private_numbers()
key_private_data['p'] = private_numbers.p
key_private_data['q'] = private_numbers.q
key_private_data['exponent'] = private_numbers.d
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
key_private_data["p"] = private_numbers.p
key_private_data["q"] = private_numbers.q
key_private_data["exponent"] = private_numbers.d
elif isinstance(
key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey
):
private_numbers = key.private_numbers()
key_private_data['x'] = private_numbers.x
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
key_private_data["x"] = private_numbers.x
elif isinstance(
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
):
private_numbers = key.private_numbers()
key_private_data['multiplier'] = private_numbers.private_value
key_private_data["multiplier"] = private_numbers.private_value
return key_type, key_public_data, key_private_data
def _check_dsa_consistency(key_public_data, key_private_data):
# Get parameters
p = key_public_data.get('p')
q = key_public_data.get('q')
g = key_public_data.get('g')
y = key_public_data.get('y')
x = key_private_data.get('x')
p = key_public_data.get("p")
q = key_public_data.get("q")
g = key_public_data.get("g")
y = key_public_data.get("y")
x = key_private_data.get("x")
for v in (p, q, g, y, x):
if v is None:
return None
@@ -104,10 +109,12 @@ def _check_dsa_consistency(key_public_data, key_private_data):
return True
def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn_func=None):
def _is_cryptography_key_consistent(
key, key_public_data, key_private_data, warn_func=None
):
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
# key._backend was removed in cryptography 42.0.0
backend = getattr(key, '_backend', None)
backend = getattr(key, "_backend", None)
if backend is not None:
return bool(backend._lib.RSA_check_key(key._rsa_cdata))
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
@@ -115,7 +122,9 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn
if result is not None:
return result
try:
signature = key.sign(SIGNATURE_TEST_DATA, cryptography.hazmat.primitives.hashes.SHA256())
signature = key.sign(
SIGNATURE_TEST_DATA, cryptography.hazmat.primitives.hashes.SHA256()
)
except AttributeError:
# sign() was added in cryptography 1.5, but we support older versions
return None
@@ -123,16 +132,20 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn
key.public_key().verify(
signature,
SIGNATURE_TEST_DATA,
cryptography.hazmat.primitives.hashes.SHA256()
cryptography.hazmat.primitives.hashes.SHA256(),
)
return True
except cryptography.exceptions.InvalidSignature:
return False
if isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
if isinstance(
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
):
try:
signature = key.sign(
SIGNATURE_TEST_DATA,
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cryptography.hazmat.primitives.hashes.SHA256())
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(
cryptography.hazmat.primitives.hashes.SHA256()
),
)
except AttributeError:
# sign() was added in cryptography 1.5, but we support older versions
@@ -141,15 +154,21 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn
key.public_key().verify(
signature,
SIGNATURE_TEST_DATA,
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cryptography.hazmat.primitives.hashes.SHA256())
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(
cryptography.hazmat.primitives.hashes.SHA256()
),
)
return True
except cryptography.exceptions.InvalidSignature:
return False
has_simple_sign_function = False
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey
):
has_simple_sign_function = True
if CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
if CRYPTOGRAPHY_HAS_ED448 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey
):
has_simple_sign_function = True
if has_simple_sign_function:
signature = key.sign(SIGNATURE_TEST_DATA)
@@ -160,7 +179,7 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data, warn
return False
# For X25519 and X448, there's no test yet.
if warn_func is not None:
warn_func('Cannot determine consistency for key of type %s' % type(key))
warn_func("Cannot determine consistency for key of type %s" % type(key))
return None
@@ -180,7 +199,15 @@ class PrivateKeyParseError(OpenSSLObjectError):
@six.add_metaclass(abc.ABCMeta)
class PrivateKeyInfoRetrieval(object):
def __init__(self, module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
def __init__(
self,
module,
backend,
content,
passphrase=None,
return_private_key_data=False,
check_consistency=False,
):
# content must be a bytes string
self.module = module
self.backend = backend
@@ -211,28 +238,38 @@ class PrivateKeyInfoRetrieval(object):
self.key = load_privatekey(
path=None,
content=priv_key_detail,
passphrase=to_bytes(self.passphrase) if self.passphrase is not None else self.passphrase,
backend=self.backend
passphrase=(
to_bytes(self.passphrase)
if self.passphrase is not None
else self.passphrase
),
backend=self.backend,
)
result['can_parse_key'] = True
result["can_parse_key"] = True
except OpenSSLObjectError as exc:
raise PrivateKeyParseError(to_native(exc), result)
result['public_key'] = to_native(self._get_public_key(binary=False))
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()
result["public_key_fingerprints"] = (
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
if pk is not None
else dict()
)
key_type, key_public_data, key_private_data = self._get_key_info(
need_private_key_data=self.return_private_key_data or self.check_consistency)
result['type'] = key_type
result['public_data'] = key_public_data
need_private_key_data=self.return_private_key_data or self.check_consistency
)
result["type"] = key_type
result["public_data"] = key_public_data
if self.return_private_key_data:
result['private_data'] = key_private_data
result["private_data"] = key_private_data
if self.check_consistency:
result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data)
if result['key_is_consistent'] is False:
result["key_is_consistent"] = self._is_key_consistent(
key_public_data, key_private_data
)
if result["key_is_consistent"] is False:
# Only fail when it is False, to avoid to fail on None (which means "we do not know")
msg = (
"Private key is not consistent! (See "
@@ -244,48 +281,88 @@ class PrivateKeyInfoRetrieval(object):
class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
"""Validate the supplied private key, using the cryptography backend"""
def __init__(self, module, content, **kwargs):
super(PrivateKeyInfoRetrievalCryptography, self).__init__(module, 'cryptography', content, **kwargs)
super(PrivateKeyInfoRetrievalCryptography, self).__init__(
module, "cryptography", content, **kwargs
)
def _get_public_key(self, binary):
return self.key.public_key().public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
serialization.PublicFormat.SubjectPublicKeyInfo,
)
def _get_key_info(self, need_private_key_data=False):
return _get_cryptography_private_key_info(self.key, need_private_key_data=need_private_key_data)
return _get_cryptography_private_key_info(
self.key, need_private_key_data=need_private_key_data
)
def _is_key_consistent(self, key_public_data, key_private_data):
return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data, warn_func=self.module.warn)
return _is_cryptography_key_consistent(
self.key, key_public_data, key_private_data, warn_func=self.module.warn
)
def get_privatekey_info(module, backend, content, passphrase=None, return_private_key_data=False, prefer_one_fingerprint=False):
if backend == 'cryptography':
def get_privatekey_info(
module,
backend,
content,
passphrase=None,
return_private_key_data=False,
prefer_one_fingerprint=False,
):
if backend == "cryptography":
info = PrivateKeyInfoRetrievalCryptography(
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data)
module,
content,
passphrase=passphrase,
return_private_key_data=return_private_key_data,
)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
def select_backend(module, backend, content, passphrase=None, return_private_key_data=False, check_consistency=False):
if backend == 'auto':
def select_backend(
module,
backend,
content,
passphrase=None,
return_private_key_data=False,
check_consistency=False,
):
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect the required Python library " "cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, PrivateKeyInfoRetrievalCryptography(
module, content, passphrase=passphrase, return_private_key_data=return_private_key_data, check_consistency=check_consistency)
module,
content,
passphrase=passphrase,
return_private_key_data=return_private_key_data,
check_consistency=check_consistency,
)
else:
raise ValueError('Unsupported value for backend: {0}'.format(backend))
raise ValueError("Unsupported value for backend: {0}".format(backend))

View File

@@ -32,12 +32,13 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
from cryptography.hazmat.primitives import serialization
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
@@ -49,37 +50,47 @@ else:
def _get_cryptography_public_key_info(key):
key_public_data = dict()
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
key_type = 'RSA'
key_type = "RSA"
public_numbers = key.public_numbers()
key_public_data['size'] = key.key_size
key_public_data['modulus'] = public_numbers.n
key_public_data['exponent'] = public_numbers.e
key_public_data["size"] = key.key_size
key_public_data["modulus"] = public_numbers.n
key_public_data["exponent"] = public_numbers.e
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
key_type = 'DSA'
key_type = "DSA"
parameter_numbers = key.parameters().parameter_numbers()
public_numbers = key.public_numbers()
key_public_data['size'] = key.key_size
key_public_data['p'] = parameter_numbers.p
key_public_data['q'] = parameter_numbers.q
key_public_data['g'] = parameter_numbers.g
key_public_data['y'] = public_numbers.y
elif CRYPTOGRAPHY_HAS_X25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey):
key_type = 'X25519'
elif CRYPTOGRAPHY_HAS_X448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey):
key_type = 'X448'
elif CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
key_type = 'Ed25519'
elif CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
key_type = 'Ed448'
elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
key_type = 'ECC'
key_public_data["size"] = key.key_size
key_public_data["p"] = parameter_numbers.p
key_public_data["q"] = parameter_numbers.q
key_public_data["g"] = parameter_numbers.g
key_public_data["y"] = public_numbers.y
elif CRYPTOGRAPHY_HAS_X25519 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey
):
key_type = "X25519"
elif CRYPTOGRAPHY_HAS_X448 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey
):
key_type = "X448"
elif CRYPTOGRAPHY_HAS_ED25519 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey
):
key_type = "Ed25519"
elif CRYPTOGRAPHY_HAS_ED448 and isinstance(
key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey
):
key_type = "Ed448"
elif isinstance(
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey
):
key_type = "ECC"
public_numbers = key.public_numbers()
key_public_data['curve'] = key.curve.name
key_public_data['x'] = public_numbers.x
key_public_data['y'] = public_numbers.y
key_public_data['exponent_size'] = key.curve.key_size
key_public_data["curve"] = key.curve.name
key_public_data["x"] = public_numbers.x
key_public_data["y"] = public_numbers.y
key_public_data["exponent_size"] = key.curve.key_size
else:
key_type = 'unknown ({0})'.format(type(key))
key_type = "unknown ({0})".format(type(key))
return key_type, key_public_data
@@ -116,54 +127,75 @@ class PublicKeyInfoRetrieval(object):
raise PublicKeyParseError(to_native(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()
result["fingerprints"] = (
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
if pk is not None
else dict()
)
key_type, key_public_data = self._get_key_info()
result['type'] = key_type
result['public_data'] = key_public_data
result["type"] = key_type
result["public_data"] = key_public_data
return result
class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
"""Validate the supplied public key, using the cryptography backend"""
def __init__(self, module, content=None, key=None):
super(PublicKeyInfoRetrievalCryptography, self).__init__(module, 'cryptography', content=content, key=key)
super(PublicKeyInfoRetrievalCryptography, self).__init__(
module, "cryptography", content=content, key=key
)
def _get_public_key(self, binary):
return self.key.public_bytes(
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
serialization.PublicFormat.SubjectPublicKeyInfo,
)
def _get_key_info(self):
return _get_cryptography_public_key_info(self.key)
def get_publickey_info(module, backend, content=None, key=None, prefer_one_fingerprint=False):
if backend == 'cryptography':
def get_publickey_info(
module, backend, content=None, key=None, prefer_one_fingerprint=False
):
if backend == "cryptography":
info = PublicKeyInfoRetrievalCryptography(module, content=content, key=key)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
def select_backend(module, backend, content=None, key=None):
if backend == 'auto':
if backend == "auto":
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_cryptography = (
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
backend = "cryptography"
# Success?
if backend == 'auto':
module.fail_json(msg=("Cannot detect any of the required Python libraries "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect any of the required Python libraries "
"cryptography (>= {0})"
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
)
if backend == 'cryptography':
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
return backend, PublicKeyInfoRetrievalCryptography(module, content=content, key=key)
module.fail_json(
msg=missing_required_lib(
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
),
exception=CRYPTOGRAPHY_IMP_ERR,
)
return backend, PublicKeyInfoRetrievalCryptography(
module, content=content, key=key
)
else:
raise ValueError('Unsupported value for backend: {0}'.format(backend))
raise ValueError("Unsupported value for backend: {0}".format(backend))