openssl_certificate/csr(_info): add support for SubjectKeyIdentifier and AuthorityKeyIdentifier (#60741)

* Add support for SubjectKeyIdentifier and AuthorityKeyIdentifier to _info modules.

* Adding SubjectKeyIdentifier and AuthorityKeyIdentifier support to openssl_certificate and openssl_csr.

* Fix type of authority_cert_issuer.

* Add basic tests.

* Add changelog.

* Added proper tests for _info modules.

* Fix docs bug.

* Make sure new features are only used when cryptography backend for openssl_csr is available.

* Work around jinja2 being too old on some CI hosts.

* Add tests for openssl_csr.

* Add openssl_certificate tests.

* Fix idempotence test.

* Move one level up.

* Add ownca_create_authority_key_identifier option.

* Add ownca_create_authority_key_identifier option.

* Add idempotency check.

* Apparently the function call expected different args for cryptography < 2.7.

* Fix copy'n'paste errors and typos.

* string -> general name.

* Add disclaimer.

* Implement always_create / create_if_not_provided / never_create for openssl_certificate.

* Update changelog and porting guide.

* Add comments for defaults.
pull/61226/head
Felix Fontein 5 years ago committed by GitHub
parent 77e4371460
commit fa70690e5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,6 @@
minor_changes:
- "openssl_certificate - add support for subject key identifier and authority key identifier extensions. Subject key identifiers are created by default when not explicitly disabled."
- "openssl_certificate - the ``ownca`` provider creates authority key identifiers if not explicitly disabled with ``ownca_create_authority_key_identifier: no``."
- "openssl_certificate_info - add support for subject key identifier and authority key identifier extensions."
- "openssl_csr - add support for subject key identifier and authority key identifier extensions."
- "openssl_csr_info - add support for subject key identifier and authority key identifier extensions."

@ -306,6 +306,8 @@ Noteworthy module changes
* `mysql_db <mysql_db_module>` returns new `db_list` parameter in addition to `db` parameter. This `db_list` parameter refers to list of database names. `db` parameter will be deprecated in version `2.13`.
* `snow_record <snow_record_module>` and `snow_record_find <snow_record_find_module>` now takes environment variables for `instance`, `username` and `password` parameters. This change marks these parameters as optional.
* The deprecated ``force`` option in ``win_firewall_rule`` has been removed.
* :ref:`openssl_certificate <openssl_certificate_module>`'s ``ownca`` provider creates authority key identifiers if not explicitly disabled with ``ownca_create_authority_key_identifier: no``. This is only the case for the ``cryptography`` backend, which is selected by default if the ``cryptography`` library is available.
* :ref:`openssl_certificate <openssl_certificate_module>`'s ``ownca`` and ``selfsigned`` providers create subject key identifiers if not explicitly disabled with ``ownca_create_subject_key_identifier: never_create`` resp. ``selfsigned_create_subject_key_identifier: never_create``. If a subject key identifier is provided by the CSR, it is taken; if not, it is created from the public key. This is only the case for the ``cryptography`` backend, which is selected by default if the ``cryptography`` library is available.
Plugins

@ -138,6 +138,21 @@ options:
default: +3650d
aliases: [ selfsigned_notAfter ]
selfsigned_create_subject_key_identifier:
description:
- Whether to create the Subject Key Identifier (SKI) from the public key.
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
provide one.
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
ignored.
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the C(selfsigned) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: str
choices: [create_if_not_provided, always_create, never_create]
default: create_if_not_provided
version_added: "2.9"
ownca_path:
description:
- Remote absolute path of the CA (Certificate Authority) certificate.
@ -204,6 +219,33 @@ options:
default: +3650d
version_added: "2.7"
ownca_create_subject_key_identifier:
description:
- Whether to create the Subject Key Identifier (SKI) from the public key.
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
provide one.
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
ignored.
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the C(ownca) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: str
choices: [create_if_not_provided, always_create, never_create]
default: create_if_not_provided
version_added: "2.9"
ownca_create_authority_key_identifier:
description:
- Create a Authority Key Identifier from the CA's certificate. If the CSR provided
a authority key identifier, it is ignored.
- The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier,
if available. If it is not available, the CA certificate's public key will be used.
- This is only used by the C(ownca) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: bool
default: yes
version_added: "2.9"
acme_accountkey_path:
description:
- The path to the accountkey for the C(acme) provider.
@ -843,6 +885,11 @@ class Certificate(crypto_utils.OpenSSLObject):
self.backend = backend
self.module = module
# 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_authority_key_identifier = False
self.backup = module.params['backup']
self.backup_file = None
@ -918,13 +965,21 @@ class Certificate(crypto_utils.OpenSSLObject):
if self.csr.subject != self.cert.subject:
return False
# Check extensions
cert_exts = self.cert.extensions
csr_exts = self.csr.extensions
cert_exts = list(self.cert.extensions)
csr_exts = list(self.csr.extensions)
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))
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))
if len(cert_exts) != len(csr_exts):
return False
for cert_ext in cert_exts:
try:
csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid)
csr_ext = self.csr.extensions.get_extension_for_oid(cert_ext.oid)
if cert_ext != csr_ext:
return False
except cryptography.x509.ExtensionNotFound as dummy:
@ -966,6 +1021,29 @@ class Certificate(crypto_utils.OpenSSLObject):
if not self._validate_csr():
return False
# Check SubjectKeyIdentifier
if self.backend == 'cryptography' and self.create_subject_key_identifier != 'never_create':
# Get hold of certificate's SKI
try:
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
except cryptography.x509.ExtensionNotFound as dummy:
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':
try:
csr_ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
except cryptography.x509.ExtensionNotFound as dummy:
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.cert.public_key()).digest:
return False
else:
# If CSR had SKI and we didn't ignore it ('create_if_not_provided'), compare SKIs
if ext.value.digest != csr_ext.value.digest:
return False
return True
@ -995,6 +1073,7 @@ class SelfSignedCertificateCryptography(Certificate):
"""Generate the self-signed certificate, using the cryptography backend"""
def __init__(self, module):
super(SelfSignedCertificateCryptography, self).__init__(module, 'cryptography')
self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier']
self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before')
self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after')
self.digest = crypto_utils.select_message_digest(module.params['selfsigned_digest'])
@ -1043,8 +1122,18 @@ class SelfSignedCertificateCryptography(Certificate):
cert_builder = cert_builder.not_valid_before(self.notBefore)
cert_builder = cert_builder.not_valid_after(self.notAfter)
cert_builder = cert_builder.public_key(self.privatekey.public_key())
has_ski = False
for extension in self.csr.extensions:
if isinstance(extension.value, x509.SubjectKeyIdentifier):
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
)
except ValueError as e:
raise CertificateError(str(e))
@ -1098,6 +1187,8 @@ class SelfSignedCertificate(Certificate):
def __init__(self, module):
super(SelfSignedCertificate, self).__init__(module, 'pyopenssl')
if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided':
module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before')
self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after')
self.digest = module.params['selfsigned_digest']
@ -1187,6 +1278,8 @@ class OwnCACertificateCryptography(Certificate):
"""Generate the own CA certificate. Using the cryptography backend"""
def __init__(self, module):
super(OwnCACertificateCryptography, 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.notBefore = self.get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before')
self.notAfter = self.get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after')
self.digest = crypto_utils.select_message_digest(module.params['ownca_digest'])
@ -1243,8 +1336,34 @@ class OwnCACertificateCryptography(Certificate):
cert_builder = cert_builder.not_valid_before(self.notBefore)
cert_builder = cert_builder.not_valid_after(self.notAfter)
cert_builder = cert_builder.public_key(self.csr.public_key())
has_ski = False
for extension in self.csr.extensions:
if isinstance(extension.value, x509.SubjectKeyIdentifier):
if self.create_subject_key_identifier == 'always_create':
continue
has_ski = True
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(
x509.SubjectKeyIdentifier.from_public_key(self.csr.public_key()),
critical=False
)
if self.create_authority_key_identifier:
try:
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
)
except cryptography.x509.ExtensionNotFound:
cert_builder = cert_builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()),
critical=False
)
certificate = cert_builder.sign(
private_key=self.ca_private_key, algorithm=self.digest,
@ -1264,6 +1383,32 @@ class OwnCACertificateCryptography(Certificate):
if module.set_fs_attributes_if_different(file_args, False):
self.changed = True
def check(self, module, perms_required=True):
"""Ensure the resource is in its desired state."""
if not super(OwnCACertificateCryptography, self).check(module, perms_required):
return False
# Check AuthorityKeyIdentifier
if self.create_authority_key_identifier:
try:
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)
)
except cryptography.x509.ExtensionNotFound:
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
try:
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
if ext.value != expected_ext:
return False
except cryptography.x509.ExtensionNotFound as dummy:
return False
return True
def dump(self, check_mode=False):
result = {
@ -1303,6 +1448,10 @@ class OwnCACertificate(Certificate):
self.digest = module.params['ownca_digest']
self.version = module.params['ownca_version']
self.serial_number = randint(1000, 99999)
if module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided':
module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
if module.params['ownca_create_authority_key_identifier']:
module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!')
self.ca_cert_path = module.params['ownca_path']
self.ca_privatekey_path = module.params['ownca_privatekey_path']
self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase']
@ -2268,6 +2417,11 @@ def main():
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']
),
# provider: ownca
ownca_path=dict(type='path'),
@ -2277,6 +2431,12 @@ def main():
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),
# provider: acme
acme_accountkey_path=dict(type='path'),

@ -238,10 +238,45 @@ valid_at:
or not.
returned: success
type: dict
subject_key_identifier:
description:
- The certificate's subject key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_key_identifier:
description:
- The certificate's authority key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_cert_issuer:
description:
- The certificate's authority cert issuer as a list of general names.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: list
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
version_added: "2.9"
authority_cert_serial_number:
description:
- The certificate's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: int
sample: '12345'
version_added: "2.9"
'''
import abc
import binascii
import datetime
import os
import traceback
@ -392,6 +427,14 @@ class CertificateInfo(crypto_utils.OpenSSLObject):
def _get_public_key(self, binary):
pass
@abc.abstractmethod
def _get_subject_key_identifier(self):
pass
@abc.abstractmethod
def _get_authority_key_identifier(self):
pass
@abc.abstractmethod
def _get_serial_number(self):
pass
@ -437,6 +480,21 @@ class CertificateInfo(crypto_utils.OpenSSLObject):
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
if self.backend != 'pyopenssl':
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
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
result['serial_number'] = self._get_serial_number()
result['extensions_by_oid'] = self._get_all_extensions()
@ -563,6 +621,23 @@ class CertificateInfoCryptography(CertificateInfo):
serialization.PublicFormat.SubjectPublicKeyInfo
)
def _get_subject_key_identifier(self):
try:
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)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [crypto_utils.cryptography_decode_name(san) 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
def _get_serial_number(self):
return self.cert.serial_number
@ -675,6 +750,14 @@ class CertificateInfoPyOpenSSL(CertificateInfo):
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_serial_number(self):
return self.cert.get_serial_number()

@ -200,6 +200,66 @@ options:
type: bool
default: no
version_added: "2.8"
create_subject_key_identifier:
description:
- Create the Subject Key Identifier from the public key.
- "Please note that commercial CAs can ignore the value, respectively use a value of
their own choice instead. Specifying this option is mostly useful for self-signed
certificates or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
type: bool
default: no
version_added: "2.9"
subject_key_identifier:
description:
- The subject key identifier as a hex string, where two bytes are separated by colons.
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this option can only be used if I(create_subject_key_identifier) is C(no).
- Note that this is only supported if the C(cryptography) backend is used!
type: str
version_added: "2.9"
authority_key_identifier:
description:
- The authority key identifier as a hex string, where two bytes are separated by colons.
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- If specified, I(authority_cert_issuer) must also be specified.
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
- The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
type: str
version_added: "2.9"
authority_cert_issuer:
description:
- Names that will be present in the authority cert issuer field of the certificate signing request.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA)
- "Example: C(DNS:ca.example.org)"
- If specified, I(authority_key_identifier) must also be specified.
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
- The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
type: list
version_added: "2.9"
authority_cert_serial_number:
description:
- The authority cert serial number.
- Note that this is only supported if the C(cryptography) backend is used!
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
type: int
version_added: "2.9"
extends_documentation_fragment:
- files
notes:
@ -329,6 +389,7 @@ backup_file:
'''
import abc
import binascii
import os
import traceback
from distutils.version import LooseVersion
@ -406,9 +467,17 @@ class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
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.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.request = 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')
self.backup = module.params['backup']
self.backup_file = None
@ -432,6 +501,18 @@ class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
self.subjectAltName = ['DNS:%s' % sub[1]]
break
if self.subject_key_identifier is not None:
try:
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))
if self.authority_key_identifier is not None:
try:
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))
@abc.abstractmethod
def _generate_csr(self):
pass
@ -496,6 +577,11 @@ class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
def __init__(self, module):
if module.params['create_subject_key_identifier']:
module.fail_json(msg='You cannot use create_subject_key_identifier with the pyOpenSSL backend!')
for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number'):
if module.params[o] is not None:
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
super(CertificateSigningRequestPyOpenSSL, self).__init__(module)
def _generate_csr(self):
@ -691,6 +777,23 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
critical=self.ocspMustStaple_critical
)
if self.create_subject_key_identifier:
csr = csr.add_extension(
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)
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 = [crypto_utils.cryptography_get_name(n) 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
)
digest = None
if self.digest == 'sha256':
digest = cryptography.hazmat.primitives.hashes.SHA256()
@ -806,11 +909,42 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
else:
return tlsfeature_ext is None
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 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
return ext.value.digest == digest
else:
return ext.value.digest == self.subject_key_identifier
else:
return ext is None
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 not ext or ext.critical:
return False
aci = None
csr_aci = None
if self.authority_cert_issuer is not None:
aci = [str(crypto_utils.cryptography_get_name(n)) for n in self.authority_cert_issuer]
if ext.value.authority_cert_issuer is not None:
csr_aci = [str(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)
else:
return ext is None
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))
_check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and
_check_authority_key_identifier(extensions))
def _check_signature(csr):
if not csr.is_signature_valid:
@ -865,8 +999,14 @@ def main():
ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']),
ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']),
backup=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'),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
),
required_together=[('authority_cert_issuer', 'authority_cert_serial_number')],
add_file_common_args=True,
supports_check_mode=True,
)

@ -160,10 +160,45 @@ public_key_fingerprints:
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
subject_key_identifier:
description:
- The CSR's subject key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_key_identifier:
description:
- The CSR's authority key identifier.
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
version_added: "2.9"
authority_cert_issuer:
description:
- The CSR's authority cert issuer as a list of general names.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: list
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
version_added: "2.9"
authority_cert_serial_number:
description:
- The CSR's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used
type: int
sample: '12345'
version_added: "2.9"
'''
import abc
import binascii
import os
import traceback
from distutils.version import LooseVersion
@ -258,6 +293,14 @@ class CertificateSigningRequestInfo(crypto_utils.OpenSSLObject):
def _get_public_key(self, binary):
pass
@abc.abstractmethod
def _get_subject_key_identifier(self):
pass
@abc.abstractmethod
def _get_authority_key_identifier(self):
pass
@abc.abstractmethod
def _get_all_extensions(self):
pass
@ -285,6 +328,21 @@ class CertificateSigningRequestInfo(crypto_utils.OpenSSLObject):
pk = self._get_public_key(binary=True)
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
if self.backend != 'pyopenssl':
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
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
result['extensions_by_oid'] = self._get_all_extensions()
result['signature_valid'] = self._is_signature_valid()
@ -394,6 +452,23 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
serialization.PublicFormat.SubjectPublicKeyInfo
)
def _get_subject_key_identifier(self):
try:
ext = self.csr.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.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [crypto_utils.cryptography_decode_name(san) 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
def _get_all_extensions(self):
return crypto_utils.cryptography_get_extensions_from_csr(self.csr)
@ -486,6 +561,14 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_all_extensions(self):
return crypto_utils.pyopenssl_get_extensions_from_csr(self.csr)

@ -308,4 +308,134 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: ownca_backup_5
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_subject_key_identifier_1
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (idempotency)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_subject_key_identifier_2
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_subject_key_identifier: never_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_subject_key_identifier_3
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove idempotency)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_subject_key_identifier: never_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_subject_key_identifier_4
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (re-enable)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_subject_key_identifier_5
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_authority_key_identifier: yes
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_authority_key_identifier_1
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (idempotency)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_authority_key_identifier: yes
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_authority_key_identifier_2
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_authority_key_identifier: no
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_authority_key_identifier_3
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove idempotency)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_authority_key_identifier: no
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_authority_key_identifier_4
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (re-add)
openssl_certificate:
path: '{{ output_dir }}/ownca_cert_aki.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
ownca_path: '{{ output_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: ownca
ownca_digest: sha256
ownca_create_authority_key_identifier: yes
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: ownca_authority_key_identifier_5
- import_tasks: ../tests/validate_ownca.yml

@ -308,4 +308,64 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: selfsigned_backup_5
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
provider: selfsigned
selfsigned_digest: sha256
selfsigned_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: selfsigned_subject_key_identifier_1
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (idempotency)
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
provider: selfsigned
selfsigned_digest: sha256
selfsigned_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: selfsigned_subject_key_identifier_2
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove)
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
provider: selfsigned
selfsigned_digest: sha256
selfsigned_create_subject_key_identifier: never_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: selfsigned_subject_key_identifier_3
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove idempotency)
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
provider: selfsigned
selfsigned_digest: sha256
selfsigned_create_subject_key_identifier: never_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: selfsigned_subject_key_identifier_4
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (re-enable)
openssl_certificate:
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
provider: selfsigned
selfsigned_digest: sha256
selfsigned_create_subject_key_identifier: always_create
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: selfsigned_subject_key_identifier_5
- import_tasks: ../tests/validate_selfsigned.yml

@ -120,3 +120,23 @@
- ownca_backup_4.backup_file is string
- ownca_backup_5 is not changed
- ownca_backup_5.backup_file is undefined
- name: Check create subject key identifier
assert:
that:
- ownca_subject_key_identifier_1 is changed
- ownca_subject_key_identifier_2 is not changed
- ownca_subject_key_identifier_3 is changed
- ownca_subject_key_identifier_4 is not changed
- ownca_subject_key_identifier_5 is changed
when: select_crypto_backend != 'pyopenssl'
- name: Check create authority key identifier
assert:
that:
- ownca_authority_key_identifier_1 is changed
- ownca_authority_key_identifier_2 is not changed
- ownca_authority_key_identifier_3 is changed
- ownca_authority_key_identifier_4 is not changed
- ownca_authority_key_identifier_5 is changed
when: select_crypto_backend != 'pyopenssl'

@ -126,3 +126,13 @@
- selfsigned_backup_4.backup_file is string
- selfsigned_backup_5 is not changed
- selfsigned_backup_5.backup_file is undefined
- name: Check create subject key identifier
assert:
that:
- selfsigned_subject_key_identifier_1 is changed
- selfsigned_subject_key_identifier_2 is not changed
- selfsigned_subject_key_identifier_3 is changed
- selfsigned_subject_key_identifier_4 is not changed
- selfsigned_subject_key_identifier_5 is changed
when: select_crypto_backend != 'pyopenssl'

@ -18,6 +18,19 @@
- "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered"
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
assert:
that:
- result.subject_key_identifier == "00:11:22:33"
- result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345
vars:
expected_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"
@ -47,6 +60,18 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
- name: Check AuthorityKeyIdentifier
assert:
that:
- result.authority_key_identifier is none
- result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345
vars:
expected_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"
@ -57,6 +82,14 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
- name: Check AuthorityKeyIdentifier
assert:
that:
- result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer is none
- result.authority_cert_serial_number is none
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"

@ -69,6 +69,14 @@
- "pathlen:23"
basic_constraints_critical: yes
ocsp_must_staple: yes
subject_key_identifier: '{{ "00:11:22:33" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
- name: Generate CSR 2
openssl_csr:
@ -90,12 +98,19 @@
- "IP:DEAD:BEEF::1"
basic_constraints:
- "CA:FALSE"
authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
- name: Generate CSR 4
openssl_csr:
path: '{{ output_dir }}/csr_4.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
useCommonNameForSAN: no
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
- name: Generate selfsigned certificates
openssl_certificate:
@ -147,7 +162,14 @@
- name: Compare results
assert:
that:
- item.0 == item.1
- ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)
== (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)'
quiet: yes
loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}"
when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.6', '>=')
vars:
keys_to_ignore:
- subject_key_identifier
- authority_key_identifier
- authority_cert_issuer
- authority_cert_serial_number

@ -0,0 +1,15 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
def compatibility_in_test(a, b):
return a in b
class TestModule:
''' Ansible math jinja2 tests '''
def tests(self):
return {
'in': compatibility_in_test,
}

@ -339,6 +339,179 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: csr_backup_5
- name: Generate CSR with subject key identifier
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
subject_key_identifier: "00:11:22:33"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_1
- name: Generate CSR with subject key identifier (idempotency)
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
subject_key_identifier: "00:11:22:33"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_2
- name: Generate CSR with subject key identifier (change)
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
subject_key_identifier: "44:55:66:77:88"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_3
- name: Generate CSR with subject key identifier (auto-create)
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
create_subject_key_identifier: yes
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_4
- name: Generate CSR with subject key identifier (auto-create idempotency)
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
create_subject_key_identifier: yes
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_5
- name: Generate CSR with subject key identifier (remove)
openssl_csr:
path: '{{ output_dir }}/csr_ski.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: subject_key_identifier_6
- name: Generate CSR with authority key identifier
openssl_csr:
path: '{{ output_dir }}/csr_aki.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_key_identifier: "00:11:22:33"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_key_identifier_1
- name: Generate CSR with authority key identifier (idempotency)
openssl_csr:
path: '{{ output_dir }}/csr_aki.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_key_identifier: "00:11:22:33"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_key_identifier_2
- name: Generate CSR with authority key identifier (change)
openssl_csr:
path: '{{ output_dir }}/csr_aki.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_key_identifier: "44:55:66:77:88"
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_key_identifier_3
- name: Generate CSR with authority key identifier (remove)
openssl_csr:
path: '{{ output_dir }}/csr_aki.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_key_identifier_4
- name: Generate CSR with authority cert issuer / serial number
openssl_csr:
path: '{{ output_dir }}/csr_acisn.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
authority_cert_serial_number: 12345
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_cert_issuer_sn_1
- name: Generate CSR with authority cert issuer / serial number (idempotency)
openssl_csr:
path: '{{ output_dir }}/csr_acisn.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
authority_cert_serial_number: 12345
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_cert_issuer_sn_2
- name: Generate CSR with authority cert issuer / serial number (change issuer)
openssl_csr:
path: '{{ output_dir }}/csr_acisn.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_cert_issuer:
- "IP:1.2.3.4"
- "DNS:ca.example.org"
authority_cert_serial_number: 12345
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_cert_issuer_sn_3
- name: Generate CSR with authority cert issuer / serial number (change serial number)
openssl_csr:
path: '{{ output_dir }}/csr_acisn.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
authority_cert_issuer:
- "IP:1.2.3.4"
- "DNS:ca.example.org"
authority_cert_serial_number: 54321
select_crypto_backend: '{{ select_crypto_backend }}'
when: select_crypto_backend != 'pyopenssl'
register: authority_cert_issuer_sn_4
- name: Generate CSR with authority cert issuer / serial number (remove)
openssl_csr:
path: '{{ output_dir }}/csr_acisn.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
subject:
commonName: www.ansible.com
when: select_crypto_backend != 'pyopenssl'
register: authority_cert_issuer_sn_5
- name: Generate CSR with everything
openssl_csr:
path: '{{ output_dir }}/csr_everything.csr'
@ -396,7 +569,15 @@
- "pathlen:23"
basic_constraints_critical: yes
ocsp_must_staple: yes
select_crypto_backend: '{{ select_crypto_backend }}'
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
register: everything_1
- name: Generate CSR with everything (idempotent, check mode)
@ -456,7 +637,15 @@
- "pathlen:23"
basic_constraints_critical: yes
ocsp_must_staple: yes
select_crypto_backend: '{{ select_crypto_backend }}'
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
check_mode: yes
register: everything_2
@ -517,5 +706,13 @@
- "pathlen:23"
basic_constraints_critical: yes
ocsp_must_staple: yes
select_crypto_backend: '{{ select_crypto_backend }}'
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}'
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
select_crypto_backend: '{{ select_crypto_backend }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
register: everything_3

@ -16,6 +16,8 @@
select_crypto_backend: pyopenssl
- import_tasks: ../tests/validate.yml
vars:
select_crypto_backend: pyopenssl
when: pyopenssl_version.stdout is version('0.15', '>=')
@ -36,5 +38,7 @@
select_crypto_backend: cryptography
- import_tasks: ../tests/validate.yml
vars:
select_crypto_backend: pyopenssl
when: cryptography_version.stdout is version('1.3', '>=')

@ -125,6 +125,36 @@
that:
- output_broken is changed
- name: Verify that subject key identifier handling works
assert:
that:
- subject_key_identifier_1 is changed
- subject_key_identifier_2 is not changed
- subject_key_identifier_3 is changed
- subject_key_identifier_4 is changed
- subject_key_identifier_5 is not changed
- subject_key_identifier_6 is not changed
when: select_crypto_backend != 'pyopenssl'
- name: Verify that authority key identifier handling works
assert:
that:
- authority_key_identifier_1 is changed
- authority_key_identifier_2 is not changed
- authority_key_identifier_3 is changed
- authority_key_identifier_4 is changed
when: select_crypto_backend != 'pyopenssl'
- name: Verify that authority cert issuer / serial number handling works
assert:
that:
- authority_cert_issuer_sn_1 is changed
- authority_cert_issuer_sn_2 is not changed
- authority_cert_issuer_sn_3 is changed
- authority_cert_issuer_sn_4 is changed
- authority_cert_issuer_sn_5 is changed
when: select_crypto_backend != 'pyopenssl'
- name: Check backup
assert:
that:

@ -15,6 +15,19 @@
- "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered"
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
assert:
that:
- result.subject_key_identifier == "00:11:22:33"
- result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345
vars:
expected_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"
@ -35,6 +48,18 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
- name: Check AuthorityKeyIdentifier
assert:
that:
- result.authority_key_identifier is none
- result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345
vars:
expected_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"
@ -45,6 +70,14 @@
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
- name: Check AuthorityKeyIdentifier
assert:
that:
- result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer is none
- result.authority_cert_serial_number is none
when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=')
- name: Update result list
set_fact:
info_results: "{{ info_results + [result] }}"

@ -69,6 +69,14 @@
- "pathlen:23"
basic_constraints_critical: yes
ocsp_must_staple: yes
subject_key_identifier: '{{ "00:11:22:33" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
- name: Generate CSR 2
openssl_csr:
@ -90,12 +98,19 @@
- "IP:DEAD:BEEF::1"
basic_constraints:
- "CA:FALSE"
authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}'
authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}'
vars:
value_for_authority_cert_issuer:
- "DNS:ca.example.org"
- "IP:1.2.3.4"
- name: Generate CSR 4
openssl_csr:
path: '{{ output_dir }}/csr_4.csr'
privatekey_path: '{{ output_dir }}/privatekey.pem'
useCommonNameForSAN: no
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
- name: Prepare result list
set_fact:
@ -132,7 +147,14 @@
- name: Compare results
assert:
that:
- item.0 == item.1
- ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)
== (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)'
quiet: yes
loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}"
when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.3', '>=')
vars:
keys_to_ignore:
- subject_key_identifier
- authority_key_identifier
- authority_cert_issuer
- authority_cert_serial_number

@ -0,0 +1,15 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
def compatibility_in_test(a, b):
return a in b
class TestModule:
''' Ansible math jinja2 tests '''
def tests(self):
return {
'in': compatibility_in_test,
}
Loading…
Cancel
Save