diff --git a/plugins/action/openssl_privatekey_pipe.py b/plugins/action/openssl_privatekey_pipe.py index 8433c4c6..df79dd91 100644 --- a/plugins/action/openssl_privatekey_pipe.py +++ b/plugins/action/openssl_privatekey_pipe.py @@ -31,16 +31,18 @@ class PrivateKeyModule(object): self.module_backend = module_backend self.check_mode = module.check_mode self.changed = False - self.return_current_key = module.params['return_current_key'] + self.return_current_key = module.params["return_current_key"] - if module.params['content'] is not None: - if module.params['content_base64']: + if module.params["content"] is not None: + if module.params["content_base64"]: try: - data = base64.b64decode(module.params['content']) + data = base64.b64decode(module.params["content"]) except Exception as e: - module.fail_json(msg='Cannot decode Base64 encoded data: {0}'.format(e)) + module.fail_json( + msg="Cannot decode Base64 encoded data: {0}".format(e) + ) else: - data = to_bytes(module.params['content']) + data = to_bytes(module.params["content"]) module_backend.set_existing(data) def generate(self, module): @@ -54,13 +56,13 @@ class PrivateKeyModule(object): self.privatekey_bytes = privatekey_data else: self.module.deprecate( - 'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0' - ' to behave the same as without check mode. You can get that behavior right now' - ' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this' - ' breaks your use-case of this module, please create an issue in the' - ' community.crypto repository', - version='3.0.0', - collection_name='community.crypto', + "Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0" + " to behave the same as without check mode. You can get that behavior right now" + " by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this" + " breaks your use-case of this module, please create an issue in the" + " community.crypto repository", + version="3.0.0", + collection_name="community.crypto", ) self.changed = True elif self.module_backend.needs_conversion(): @@ -71,20 +73,22 @@ class PrivateKeyModule(object): self.privatekey_bytes = privatekey_data else: self.module.deprecate( - 'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0' - ' to behave the same as without check mode. You can get that behavior right now' - ' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this' - ' breaks your use-case of this module, please create an issue in the' - ' community.crypto repository', - version='3.0.0', - collection_name='community.crypto', + "Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0" + " to behave the same as without check mode. You can get that behavior right now" + " by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this" + " breaks your use-case of this module, please create an issue in the" + " community.crypto repository", + version="3.0.0", + collection_name="community.crypto", ) self.changed = True def dump(self): """Serialize the object into a dictionary.""" - result = self.module_backend.dump(include_key=self.changed or self.return_current_key) - result['changed'] = self.changed + result = self.module_backend.dump( + include_key=self.changed or self.return_current_key + ) + result["changed"] = self.changed return result @@ -92,11 +96,13 @@ class ActionModule(ActionModuleBase): @staticmethod def setup_module(): argument_spec = get_privatekey_argument_spec() - argument_spec.argument_spec.update(dict( - content=dict(type='str', no_log=True), - content_base64=dict(type='bool', default=False), - return_current_key=dict(type='bool', default=False), - )) + argument_spec.argument_spec.update( + dict( + content=dict(type="str", no_log=True), + content_base64=dict(type="bool", default=False), + return_current_key=dict(type="bool", default=False), + ) + ) return argument_spec, dict( supports_check_mode=True, ) @@ -105,7 +111,7 @@ class ActionModule(ActionModuleBase): def run_module(module): backend, module_backend = select_backend( module=module, - backend=module.params['select_crypto_backend'], + backend=module.params["select_crypto_backend"], ) try: @@ -120,10 +126,10 @@ class ActionModule(ActionModuleBase): # `module.no_log = True`, this should be safe. module.no_log = True try: - module.no_log_values.remove(module.params['content']) + module.no_log_values.remove(module.params["content"]) except KeyError: pass - module.params['content'] = 'ANSIBLE_NO_LOG_VALUE' + module.params["content"] = "ANSIBLE_NO_LOG_VALUE" module.exit_json(**result) except OpenSSLObjectError as exc: module.fail_json(msg=to_native(exc)) diff --git a/plugins/doc_fragments/acme.py b/plugins/doc_fragments/acme.py index e7623721..c6a600f4 100644 --- a/plugins/doc_fragments/acme.py +++ b/plugins/doc_fragments/acme.py @@ -218,7 +218,7 @@ options: """ # No account data documentation fragment - NO_ACCOUNT = r''' + NO_ACCOUNT = r""" notes: - "If a new enough version of the C(cryptography) library is available (see Requirements for details), it will be used @@ -226,7 +226,7 @@ notes: or enabled with the O(select_crypto_backend) option. Note that using the C(openssl) binary will be slower." options: {} -''' +""" CERTIFICATE = r""" options: diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py index 310c787a..94da6a17 100644 --- a/plugins/doc_fragments/attributes.py +++ b/plugins/doc_fragments/attributes.py @@ -37,7 +37,7 @@ attributes: """ # Should be used together with the standard fragment - INFO_MODULE = r''' + INFO_MODULE = r""" options: {} attributes: check_mode: @@ -48,9 +48,9 @@ attributes: support: N/A details: - This action does not modify state. -''' +""" - ACTIONGROUP_ACME = r''' + ACTIONGROUP_ACME = r""" options: {} attributes: action_group: @@ -59,7 +59,7 @@ attributes: membership: - community.crypto.acme - acme -''' +""" FACTS = r""" options: {} @@ -69,7 +69,7 @@ attributes: """ # Should be used together with the standard fragment and the FACTS fragment - FACTS_MODULE = r''' + FACTS_MODULE = r""" options: {} attributes: check_mode: @@ -82,7 +82,7 @@ attributes: - This action does not modify state. facts: support: full -''' +""" FILES = r""" options: {} diff --git a/plugins/doc_fragments/module_certificate.py b/plugins/doc_fragments/module_certificate.py index cae8cc94..8ae16fe6 100644 --- a/plugins/doc_fragments/module_certificate.py +++ b/plugins/doc_fragments/module_certificate.py @@ -96,7 +96,7 @@ seealso: - module: community.crypto.openssl_publickey """ - BACKEND_ACME_DOCUMENTATION = r''' + BACKEND_ACME_DOCUMENTATION = r""" description: - This module allows one to (re)generate OpenSSL certificates. requirements: @@ -129,9 +129,9 @@ options: - "Let's Encrypt recommends using their staging server while developing jobs. U(https://letsencrypt.org/docs/staging-environment/)." type: str default: https://acme-v02.api.letsencrypt.org/directory -''' +""" - BACKEND_ENTRUST_DOCUMENTATION = r''' + BACKEND_ENTRUST_DOCUMENTATION = r""" options: entrust_cert_type: description: @@ -214,9 +214,9 @@ options: - This is only used by the V(entrust) provider. type: path default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml -''' +""" - BACKEND_OWNCA_DOCUMENTATION = r''' + BACKEND_OWNCA_DOCUMENTATION = r""" description: - The V(ownca) provider is intended for generating an OpenSSL certificate signed with your own CA (Certificate Authority) certificate (self-signed certificate). @@ -324,9 +324,9 @@ options: - Note that this is only supported if the C(cryptography) backend is used! type: bool default: true -''' +""" - BACKEND_SELFSIGNED_DOCUMENTATION = r''' + BACKEND_SELFSIGNED_DOCUMENTATION = r""" notes: - For the V(selfsigned) provider, O(csr_path) and O(csr_content) are optional. If not provided, a certificate without any information (Subject, Subject Alternative Names, Key Usage, etc.) is created. @@ -410,4 +410,4 @@ options: type: str choices: [create_if_not_provided, always_create, never_create] default: create_if_not_provided -''' +""" diff --git a/plugins/filter/gpg_fingerprint.py b/plugins/filter/gpg_fingerprint.py index 982fc823..581a2013 100644 --- a/plugins/filter/gpg_fingerprint.py +++ b/plugins/filter/gpg_fingerprint.py @@ -57,7 +57,9 @@ from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import ( def gpg_fingerprint(input): if not isinstance(input, string_types): raise AnsibleFilterError( - 'The input for the community.crypto.gpg_fingerprint filter must be a string; got {type} instead'.format(type=type(input)) + "The input for the community.crypto.gpg_fingerprint filter must be a string; got {type} instead".format( + type=type(input) + ) ) try: gpg = PluginGPGRunner() @@ -67,9 +69,9 @@ def gpg_fingerprint(input): class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'gpg_fingerprint': gpg_fingerprint, + "gpg_fingerprint": gpg_fingerprint, } diff --git a/plugins/filter/openssl_csr_info.py b/plugins/filter/openssl_csr_info.py index 3552cc60..2dd90542 100644 --- a/plugins/filter/openssl_csr_info.py +++ b/plugins/filter/openssl_csr_info.py @@ -292,27 +292,38 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp ) -def openssl_csr_info_filter(data, name_encoding='ignore'): - '''Extract information from X.509 PEM certificate.''' +def openssl_csr_info_filter(data, name_encoding="ignore"): + """Extract information from X.509 PEM certificate.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.openssl_csr_info input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.openssl_csr_info input must be a text type, not %s" + % type(data) + ) if not isinstance(name_encoding, string_types): - raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding)) + raise AnsibleFilterError( + "The name_encoding option must be of a text type, not %s" + % type(name_encoding) + ) name_encoding = to_native(name_encoding) - if name_encoding not in ('ignore', 'idna', 'unicode'): - raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding) + if name_encoding not in ("ignore", "idna", "unicode"): + raise AnsibleFilterError( + 'The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' + % name_encoding + ) - module = FilterModuleMock({'name_encoding': name_encoding}) + module = FilterModuleMock({"name_encoding": name_encoding}) try: - return get_csr_info(module, 'cryptography', content=to_bytes(data), validate_signature=True) + return get_csr_info( + module, "cryptography", content=to_bytes(data), validate_signature=True + ) except OpenSSLObjectError as exc: raise AnsibleFilterError(to_native(exc)) class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'openssl_csr_info': openssl_csr_info_filter, + "openssl_csr_info": openssl_csr_info_filter, } diff --git a/plugins/filter/openssl_privatekey_info.py b/plugins/filter/openssl_privatekey_info.py index 7a50444a..7c019e19 100644 --- a/plugins/filter/openssl_privatekey_info.py +++ b/plugins/filter/openssl_privatekey_info.py @@ -165,20 +165,36 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp ) -def openssl_privatekey_info_filter(data, passphrase=None, return_private_key_data=False): - '''Extract information from X.509 PEM certificate.''' +def openssl_privatekey_info_filter( + data, passphrase=None, return_private_key_data=False +): + """Extract information from X.509 PEM certificate.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.openssl_privatekey_info input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.openssl_privatekey_info input must be a text type, not %s" + % type(data) + ) if passphrase is not None and not isinstance(passphrase, string_types): - raise AnsibleFilterError('The passphrase option must be a text type, not %s' % type(passphrase)) + raise AnsibleFilterError( + "The passphrase option must be a text type, not %s" % type(passphrase) + ) if not isinstance(return_private_key_data, bool): - raise AnsibleFilterError('The return_private_key_data option must be a boolean, not %s' % type(return_private_key_data)) + raise AnsibleFilterError( + "The return_private_key_data option must be a boolean, not %s" + % type(return_private_key_data) + ) module = FilterModuleMock({}) try: - result = get_privatekey_info(module, 'cryptography', content=to_bytes(data), passphrase=passphrase, return_private_key_data=return_private_key_data) - result.pop('can_parse_key', None) - result.pop('key_is_consistent', None) + result = get_privatekey_info( + module, + "cryptography", + content=to_bytes(data), + passphrase=passphrase, + return_private_key_data=return_private_key_data, + ) + result.pop("can_parse_key", None) + result.pop("key_is_consistent", None) return result except PrivateKeyParseError as exc: raise AnsibleFilterError(exc.error_message) @@ -187,9 +203,9 @@ def openssl_privatekey_info_filter(data, passphrase=None, return_private_key_dat class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'openssl_privatekey_info': openssl_privatekey_info_filter, + "openssl_privatekey_info": openssl_privatekey_info_filter, } diff --git a/plugins/filter/openssl_publickey_info.py b/plugins/filter/openssl_publickey_info.py index 04a48c97..0d6956a4 100644 --- a/plugins/filter/openssl_publickey_info.py +++ b/plugins/filter/openssl_publickey_info.py @@ -143,13 +143,16 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp def openssl_publickey_info_filter(data): - '''Extract information from OpenSSL PEM public key.''' + """Extract information from OpenSSL PEM public key.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.openssl_publickey_info input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.openssl_publickey_info input must be a text type, not %s" + % type(data) + ) module = FilterModuleMock({}) try: - return get_publickey_info(module, 'cryptography', content=to_bytes(data)) + return get_publickey_info(module, "cryptography", content=to_bytes(data)) except PublicKeyParseError as exc: raise AnsibleFilterError(exc.error_message) except OpenSSLObjectError as exc: @@ -157,9 +160,9 @@ def openssl_publickey_info_filter(data): class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'openssl_publickey_info': openssl_publickey_info_filter, + "openssl_publickey_info": openssl_publickey_info_filter, } diff --git a/plugins/filter/parse_serial.py b/plugins/filter/parse_serial.py index 8d2d3ba8..1049d5bd 100644 --- a/plugins/filter/parse_serial.py +++ b/plugins/filter/parse_serial.py @@ -53,7 +53,9 @@ from ansible_collections.community.crypto.plugins.module_utils.serial import ( def parse_serial_filter(input): if not isinstance(input, string_types): raise AnsibleFilterError( - 'The input for the community.crypto.parse_serial filter must be a string; got {type} instead'.format(type=type(input)) + "The input for the community.crypto.parse_serial filter must be a string; got {type} instead".format( + type=type(input) + ) ) try: return parse_serial(to_native(input)) @@ -62,9 +64,9 @@ def parse_serial_filter(input): class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'parse_serial': parse_serial_filter, + "parse_serial": parse_serial_filter, } diff --git a/plugins/filter/split_pem.py b/plugins/filter/split_pem.py index 58c73aee..82228152 100644 --- a/plugins/filter/split_pem.py +++ b/plugins/filter/split_pem.py @@ -51,18 +51,21 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import def split_pem_filter(data): - '''Split PEM file.''' + """Split PEM file.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.split_pem input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.split_pem input must be a text type, not %s" + % type(data) + ) data = to_text(data) return split_pem_list(data) class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'split_pem': split_pem_filter, + "split_pem": split_pem_filter, } diff --git a/plugins/filter/to_serial.py b/plugins/filter/to_serial.py index 77f6b608..f02091d1 100644 --- a/plugins/filter/to_serial.py +++ b/plugins/filter/to_serial.py @@ -51,10 +51,14 @@ from ansible_collections.community.crypto.plugins.module_utils.serial import to_ def to_serial_filter(input): if not isinstance(input, integer_types): raise AnsibleFilterError( - 'The input for the community.crypto.to_serial filter must be an integer; got {type} instead'.format(type=type(input)) + "The input for the community.crypto.to_serial filter must be an integer; got {type} instead".format( + type=type(input) + ) ) if input < 0: - raise AnsibleFilterError('The input for the community.crypto.to_serial filter must not be negative') + raise AnsibleFilterError( + "The input for the community.crypto.to_serial filter must not be negative" + ) try: return to_serial(input) except ValueError as exc: @@ -62,9 +66,9 @@ def to_serial_filter(input): class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'to_serial': to_serial_filter, + "to_serial": to_serial_filter, } diff --git a/plugins/filter/x509_certificate_info.py b/plugins/filter/x509_certificate_info.py index 5af978c9..6e1af832 100644 --- a/plugins/filter/x509_certificate_info.py +++ b/plugins/filter/x509_certificate_info.py @@ -326,27 +326,36 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp ) -def x509_certificate_info_filter(data, name_encoding='ignore'): - '''Extract information from X.509 PEM certificate.''' +def x509_certificate_info_filter(data, name_encoding="ignore"): + """Extract information from X.509 PEM certificate.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.x509_certificate_info input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.x509_certificate_info input must be a text type, not %s" + % type(data) + ) if not isinstance(name_encoding, string_types): - raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding)) + raise AnsibleFilterError( + "The name_encoding option must be of a text type, not %s" + % type(name_encoding) + ) name_encoding = to_native(name_encoding) - if name_encoding not in ('ignore', 'idna', 'unicode'): - raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding) + if name_encoding not in ("ignore", "idna", "unicode"): + raise AnsibleFilterError( + 'The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' + % name_encoding + ) - module = FilterModuleMock({'name_encoding': name_encoding}) + module = FilterModuleMock({"name_encoding": name_encoding}) try: - return get_certificate_info(module, 'cryptography', content=to_bytes(data)) + return get_certificate_info(module, "cryptography", content=to_bytes(data)) except OpenSSLObjectError as exc: raise AnsibleFilterError(to_native(exc)) class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'x509_certificate_info': x509_certificate_info_filter, + "x509_certificate_info": x509_certificate_info_filter, } diff --git a/plugins/filter/x509_crl_info.py b/plugins/filter/x509_crl_info.py index 8362023e..7da0cefe 100644 --- a/plugins/filter/x509_crl_info.py +++ b/plugins/filter/x509_crl_info.py @@ -177,17 +177,29 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp ) -def x509_crl_info_filter(data, name_encoding='ignore', list_revoked_certificates=True): - '''Extract information from X.509 PEM certificate.''' +def x509_crl_info_filter(data, name_encoding="ignore", list_revoked_certificates=True): + """Extract information from X.509 PEM certificate.""" if not isinstance(data, string_types): - raise AnsibleFilterError('The community.crypto.x509_crl_info input must be a text type, not %s' % type(data)) + raise AnsibleFilterError( + "The community.crypto.x509_crl_info input must be a text type, not %s" + % type(data) + ) if not isinstance(name_encoding, string_types): - raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding)) + raise AnsibleFilterError( + "The name_encoding option must be of a text type, not %s" + % type(name_encoding) + ) if not isinstance(list_revoked_certificates, bool): - raise AnsibleFilterError('The list_revoked_certificates option must be a boolean, not %s' % type(list_revoked_certificates)) + raise AnsibleFilterError( + "The list_revoked_certificates option must be a boolean, not %s" + % type(list_revoked_certificates) + ) name_encoding = to_native(name_encoding) - if name_encoding not in ('ignore', 'idna', 'unicode'): - raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding) + if name_encoding not in ("ignore", "idna", "unicode"): + raise AnsibleFilterError( + 'The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' + % name_encoding + ) data = to_bytes(data) if not identify_pem_format(data): @@ -196,17 +208,19 @@ def x509_crl_info_filter(data, name_encoding='ignore', list_revoked_certificates except (binascii.Error, TypeError, ValueError, UnicodeEncodeError): pass - module = FilterModuleMock({'name_encoding': name_encoding}) + module = FilterModuleMock({"name_encoding": name_encoding}) try: - return get_crl_info(module, content=data, list_revoked_certificates=list_revoked_certificates) + return get_crl_info( + module, content=data, list_revoked_certificates=list_revoked_certificates + ) except OpenSSLObjectError as exc: raise AnsibleFilterError(to_native(exc)) class FilterModule(object): - '''Ansible jinja2 filters''' + """Ansible jinja2 filters""" def filters(self): return { - 'x509_crl_info': x509_crl_info_filter, + "x509_crl_info": x509_crl_info_filter, } diff --git a/plugins/module_utils/acme/account.py b/plugins/module_utils/acme/account.py index 781b346d..5f7c4623 100644 --- a/plugins/module_utils/acme/account.py +++ b/plugins/module_utils/acme/account.py @@ -19,10 +19,10 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor class ACMEAccount(object): - ''' + """ ACME account object. Allows to create new accounts, check for existence of accounts, retrieve account data. - ''' + """ def __init__(self, client): # Set to true to enable logging of all signed requests @@ -30,9 +30,15 @@ class ACMEAccount(object): self.client = client - def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True, - external_account_binding=None): - ''' + def _new_reg( + self, + contact=None, + agreement=None, + terms_agreed=False, + allow_creation=True, + external_account_binding=None, + ): + """ Registers a new ACME account. Returns a pair ``(created, data)``. Here, ``created`` is ``True`` if the account was created and ``False`` if it already existed (e.g. it was not newly created), @@ -44,23 +50,25 @@ class ACMEAccount(object): (https://tools.ietf.org/html/rfc8555#section-7.3.4). https://tools.ietf.org/html/rfc8555#section-7.3 - ''' + """ contact = contact or [] if self.client.version == 1: - new_reg = { - 'resource': 'new-reg', - 'contact': contact - } + new_reg = {"resource": "new-reg", "contact": contact} if agreement: - new_reg['agreement'] = agreement + new_reg["agreement"] = agreement else: - new_reg['agreement'] = self.client.directory['meta']['terms-of-service'] + new_reg["agreement"] = self.client.directory["meta"]["terms-of-service"] if external_account_binding is not None: - raise ModuleFailException('External account binding is not supported for ACME v1') - url = self.client.directory['new-reg'] + raise ModuleFailException( + "External account binding is not supported for ACME v1" + ) + url = self.client.directory["new-reg"] else: - if (external_account_binding is not None or self.client.directory['meta'].get('externalAccountRequired')) and allow_creation: + if ( + external_account_binding is not None + or self.client.directory["meta"].get("externalAccountRequired") + ) and allow_creation: # Some ACME servers such as ZeroSSL do not like it when you try to register an existing account # and provide external_account_binding credentials. Thus we first send a request with allow_creation=False # to see whether the account already exists. @@ -73,44 +81,53 @@ class ACMEAccount(object): return created, data # An account does not yet exist. Try to create one next. - new_reg = { - 'contact': contact - } + new_reg = {"contact": contact} if not allow_creation: # https://tools.ietf.org/html/rfc8555#section-7.3.1 - new_reg['onlyReturnExisting'] = True + new_reg["onlyReturnExisting"] = True if terms_agreed: - new_reg['termsOfServiceAgreed'] = True - url = self.client.directory['newAccount'] + new_reg["termsOfServiceAgreed"] = True + url = self.client.directory["newAccount"] if external_account_binding is not None: - new_reg['externalAccountBinding'] = self.client.sign_request( + new_reg["externalAccountBinding"] = self.client.sign_request( { - 'alg': external_account_binding['alg'], - 'kid': external_account_binding['kid'], - 'url': url, + "alg": external_account_binding["alg"], + "kid": external_account_binding["kid"], + "url": url, }, self.client.account_jwk, - self.client.backend.create_mac_key(external_account_binding['alg'], external_account_binding['key']) + self.client.backend.create_mac_key( + external_account_binding["alg"], external_account_binding["key"] + ), ) - elif self.client.directory['meta'].get('externalAccountRequired') and allow_creation: + elif ( + self.client.directory["meta"].get("externalAccountRequired") + and allow_creation + ): raise ModuleFailException( - 'To create an account, an external account binding must be specified. ' - 'Use the acme_account module with the external_account_binding option.' + "To create an account, an external account binding must be specified. " + "Use the acme_account module with the external_account_binding option." ) - result, info = self.client.send_signed_request(url, new_reg, fail_on_error=False) + result, info = self.client.send_signed_request( + url, new_reg, fail_on_error=False + ) if not isinstance(result, Mapping): raise ACMEProtocolException( - self.client.module, msg='Invalid account creation reply from ACME server', info=info, content=result) + self.client.module, + msg="Invalid account creation reply from ACME server", + info=info, + content=result, + ) - if info['status'] in ([200, 201] if self.client.version == 1 else [201]): + if info["status"] in ([200, 201] if self.client.version == 1 else [201]): # Account did not exist - if 'location' in info: - self.client.set_account_uri(info['location']) + if "location" in info: + self.client.set_account_uri(info["location"]) return True, result - elif info['status'] == (409 if self.client.version == 1 else 200): + elif info["status"] == (409 if self.client.version == 1 else 200): # Account did exist - if result.get('status') == 'deactivated': + if result.get("status") == "deactivated": # A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and # Boulder (https://github.com/letsencrypt/boulder/issues/3971): this should # not return a valid account object according to @@ -121,15 +138,23 @@ class ACMEAccount(object): return False, None else: raise ModuleFailException("Account is deactivated") - if 'location' in info: - self.client.set_account_uri(info['location']) + if "location" in info: + self.client.set_account_uri(info["location"]) return False, result - elif info['status'] in (400, 404) and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation: + elif ( + info["status"] in (400, 404) + and result["type"] == "urn:ietf:params:acme:error:accountDoesNotExist" + and not allow_creation + ): # Account does not exist (and we did not try to create it) # (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400. # Unfortunately Digicert does not care and sends 404 instead.) return False, None - elif info['status'] == 403 and result['type'] == 'urn:ietf:params:acme:error:unauthorized' and 'deactivated' in (result.get('detail') or ''): + elif ( + info["status"] == 403 + and result["type"] == "urn:ietf:params:acme:error:unauthorized" + and "deactivated" in (result.get("detail") or "") + ): # Account has been deactivated; currently works for Pebble; has not been # implemented for Boulder (https://github.com/letsencrypt/boulder/issues/3971), # might need adjustment in error detection. @@ -139,47 +164,80 @@ class ACMEAccount(object): raise ModuleFailException("Account is deactivated") else: raise ACMEProtocolException( - self.client.module, msg='Registering ACME account failed', info=info, content_json=result) + self.client.module, + msg="Registering ACME account failed", + info=info, + content_json=result, + ) def get_account_data(self): - ''' + """ Retrieve account information. Can only be called when the account URI is already known (such as after calling setup_account). Return None if the account was deactivated, or a dict otherwise. - ''' + """ if self.client.account_uri is None: raise ModuleFailException("Account URI unknown") if self.client.version == 1: data = {} - data['resource'] = 'reg' - result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False) + data["resource"] = "reg" + result, info = self.client.send_signed_request( + self.client.account_uri, data, fail_on_error=False + ) else: # try POST-as-GET first (draft-15 or newer) data = None - result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False) + result, info = self.client.send_signed_request( + self.client.account_uri, data, fail_on_error=False + ) # check whether that failed with a malformed request error - if info['status'] >= 400 and result.get('type') == 'urn:ietf:params:acme:error:malformed': + if ( + info["status"] >= 400 + and result.get("type") == "urn:ietf:params:acme:error:malformed" + ): # retry as a regular POST (with no changed data) for pre-draft-15 ACME servers data = {} - result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False) + result, info = self.client.send_signed_request( + self.client.account_uri, data, fail_on_error=False + ) if not isinstance(result, Mapping): raise ACMEProtocolException( - self.client.module, msg='Invalid account data retrieved from ACME server', info=info, content=result) - if info['status'] in (400, 403) and result.get('type') == 'urn:ietf:params:acme:error:unauthorized': + self.client.module, + msg="Invalid account data retrieved from ACME server", + info=info, + content=result, + ) + if ( + info["status"] in (400, 403) + and result.get("type") == "urn:ietf:params:acme:error:unauthorized" + ): # Returned when account is deactivated return None - if info['status'] in (400, 404) and result.get('type') == 'urn:ietf:params:acme:error:accountDoesNotExist': + if ( + info["status"] in (400, 404) + and result.get("type") == "urn:ietf:params:acme:error:accountDoesNotExist" + ): # Returned when account does not exist return None - if info['status'] < 200 or info['status'] >= 300: + if info["status"] < 200 or info["status"] >= 300: raise ACMEProtocolException( - self.client.module, msg='Error retrieving account data', info=info, content_json=result) + self.client.module, + msg="Error retrieving account data", + info=info, + content_json=result, + ) return result - def setup_account(self, contact=None, agreement=None, terms_agreed=False, - allow_creation=True, remove_account_uri_if_not_exists=False, - external_account_binding=None): - ''' + def setup_account( + self, + contact=None, + agreement=None, + terms_agreed=False, + allow_creation=True, + remove_account_uri_if_not_exists=False, + external_account_binding=None, + ): + """ Detect or create an account on the ACME server. For ACME v1, as the only way (without knowing an account URI) to test if an account exists is to try and create one with the provided account @@ -203,7 +261,7 @@ class ACMEAccount(object): (https://tools.ietf.org/html/rfc8555#section-7.3.4). https://tools.ietf.org/html/rfc8555#section-7.3 - ''' + """ if self.client.account_uri is not None: created = False @@ -214,7 +272,9 @@ class ACMEAccount(object): if remove_account_uri_if_not_exists and not allow_creation: self.client.account_uri = None else: - raise ModuleFailException("Account is deactivated or does not exist!") + raise ModuleFailException( + "Account is deactivated or does not exist!" + ) else: created, account_data = self._new_reg( contact, @@ -223,15 +283,17 @@ class ACMEAccount(object): allow_creation=allow_creation and not self.client.module.check_mode, external_account_binding=external_account_binding, ) - if self.client.module.check_mode and self.client.account_uri is None and allow_creation: + if ( + self.client.module.check_mode + and self.client.account_uri is None + and allow_creation + ): created = True - account_data = { - 'contact': contact or [] - } + account_data = {"contact": contact or []} return created, account_data def update_account(self, account_data, contact=None): - ''' + """ Update an account on the ACME server. Check mode is fully respected. The current account data must be provided as ``account_data``. @@ -242,11 +304,11 @@ class ACMEAccount(object): account data. https://tools.ietf.org/html/rfc8555#section-7.3.2 - ''' + """ # Create request update_request = {} - if contact is not None and account_data.get('contact', []) != contact: - update_request['contact'] = list(contact) + if contact is not None and account_data.get("contact", []) != contact: + update_request["contact"] = list(contact) # No change? if not update_request: @@ -258,10 +320,16 @@ class ACMEAccount(object): account_data.update(update_request) else: if self.client.version == 1: - update_request['resource'] = 'reg' - account_data, info = self.client.send_signed_request(self.client.account_uri, update_request) + update_request["resource"] = "reg" + account_data, info = self.client.send_signed_request( + self.client.account_uri, update_request + ) if not isinstance(account_data, Mapping): raise ACMEProtocolException( - self.client.module, msg='Invalid account updating reply from ACME server', info=info, content=account_data) + self.client.module, + msg="Invalid account updating reply from ACME server", + info=info, + content=account_data, + ) return True, account_data diff --git a/plugins/module_utils/acme/acme.py b/plugins/module_utils/acme/acme.py index 47616a9f..caded1d5 100644 --- a/plugins/module_utils/acme/acme.py +++ b/plugins/module_utils/acme/acme.py @@ -66,72 +66,97 @@ RETRY_COUNT = 10 def _decode_retry(module, response, info, retry_count): - if info['status'] not in RETRY_STATUS_CODES: + if info["status"] not in RETRY_STATUS_CODES: return False if retry_count >= RETRY_COUNT: raise ACMEProtocolException( - module, msg='Giving up after {retry} retries'.format(retry=RETRY_COUNT), info=info, response=response) + module, + msg="Giving up after {retry} retries".format(retry=RETRY_COUNT), + info=info, + response=response, + ) # 429 and 503 should have a Retry-After header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) try: - retry_after = min(max(1, int(info.get('retry-after'))), 60) + retry_after = min(max(1, int(info.get("retry-after"))), 60) except (TypeError, ValueError): retry_after = 10 - module.log('Retrieved a %s HTTP status on %s, retrying in %s seconds' % (format_http_status(info['status']), info['url'], retry_after)) + module.log( + "Retrieved a %s HTTP status on %s, retrying in %s seconds" + % (format_http_status(info["status"]), info["url"], retry_after) + ) time.sleep(retry_after) return True -def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True): - if info['status'] < 0: - raise NetworkException(msg="Failure downloading %s, %s" % (info['url'], info['msg'])) +def _assert_fetch_url_success( + module, + response, + info, + allow_redirect=False, + allow_client_error=True, + allow_server_error=True, +): + if info["status"] < 0: + raise NetworkException( + msg="Failure downloading %s, %s" % (info["url"], info["msg"]) + ) - if (300 <= info['status'] < 400 and not allow_redirect) or \ - (400 <= info['status'] < 500 and not allow_client_error) or \ - (info['status'] >= 500 and not allow_server_error): + if ( + (300 <= info["status"] < 400 and not allow_redirect) + or (400 <= info["status"] < 500 and not allow_client_error) + or (info["status"] >= 500 and not allow_server_error) + ): raise ACMEProtocolException(module, info=info, response=response) def _is_failed(info, expected_status_codes=None): - if info['status'] < 200 or info['status'] >= 400: + if info["status"] < 200 or info["status"] >= 400: return True - if expected_status_codes is not None and info['status'] not in expected_status_codes: + if ( + expected_status_codes is not None + and info["status"] not in expected_status_codes + ): return True return False class ACMEDirectory(object): - ''' + """ The ACME server directory. Gives access to the available resources, and allows to obtain a Replay-Nonce. The acme_directory URL needs to support unauthenticated GET requests; ACME endpoints requiring authentication are not supported. https://tools.ietf.org/html/rfc8555#section-7.1.1 - ''' + """ def __init__(self, module, account): self.module = module - self.directory_root = module.params['acme_directory'] - self.version = module.params['acme_version'] + self.directory_root = module.params["acme_directory"] + self.version = module.params["acme_version"] self.directory, dummy = account.get_request(self.directory_root, get_only=True) - self.request_timeout = module.params['request_timeout'] + self.request_timeout = module.params["request_timeout"] # Check whether self.version matches what we expect if self.version == 1: - for key in ('new-reg', 'new-authz', 'new-cert'): + for key in ("new-reg", "new-authz", "new-cert"): if key not in self.directory: - raise ModuleFailException("ACME directory does not seem to follow protocol ACME v1") + raise ModuleFailException( + "ACME directory does not seem to follow protocol ACME v1" + ) if self.version == 2: - for key in ('newNonce', 'newAccount', 'newOrder'): + for key in ("newNonce", "newAccount", "newOrder"): if key not in self.directory: - raise ModuleFailException("ACME directory does not seem to follow protocol ACME v2") + raise ModuleFailException( + "ACME directory does not seem to follow protocol ACME v2" + ) # Make sure that 'meta' is always available - if 'meta' not in self.directory: - self.directory['meta'] = {} + if "meta" not in self.directory: + self.directory["meta"] = {} def __getitem__(self, key): return self.directory[key] @@ -143,35 +168,48 @@ class ACMEDirectory(object): return self.directory.get(key, default_value) def get_nonce(self, resource=None): - url = self.directory_root if self.version == 1 else self.directory['newNonce'] + url = self.directory_root if self.version == 1 else self.directory["newNonce"] if resource is not None: url = resource retry_count = 0 while True: - response, info = fetch_url(self.module, url, method='HEAD', timeout=self.request_timeout) + response, info = fetch_url( + self.module, url, method="HEAD", timeout=self.request_timeout + ) if _decode_retry(self.module, response, info, retry_count): retry_count += 1 continue - if info['status'] not in (200, 204): - raise NetworkException("Failed to get replay-nonce, got status {0}".format(format_http_status(info['status']))) - if 'replay-nonce' in info: - return info['replay-nonce'] + if info["status"] not in (200, 204): + raise NetworkException( + "Failed to get replay-nonce, got status {0}".format( + format_http_status(info["status"]) + ) + ) + if "replay-nonce" in info: + return info["replay-nonce"] self.module.log( - 'HEAD to {0} did return status {1}, but no replay-nonce header!'.format(url, format_http_status(info['status']))) + "HEAD to {0} did return status {1}, but no replay-nonce header!".format( + url, format_http_status(info["status"]) + ) + ) if retry_count >= 5: raise ACMEProtocolException( - self.module, msg='Was not able to obtain nonce, giving up after 5 retries', info=info, response=response) + self.module, + msg="Was not able to obtain nonce, giving up after 5 retries", + info=info, + response=response, + ) retry_count += 1 def has_renewal_info_endpoint(self): - return 'renewalInfo' in self.directory + return "renewalInfo" in self.directory class ACMEClient(object): - ''' + """ ACME client object. Handles the authorized communication with the ACME server. - ''' + """ def __init__(self, module, backend): # Set to true to enable logging of all signed requests @@ -179,17 +217,17 @@ class ACMEClient(object): self.module = module self.backend = backend - self.version = module.params['acme_version'] + self.version = module.params["acme_version"] # account_key path and content are mutually exclusive - self.account_key_file = module.params.get('account_key_src') - self.account_key_content = module.params.get('account_key_content') - self.account_key_passphrase = module.params.get('account_key_passphrase') + self.account_key_file = module.params.get("account_key_src") + self.account_key_content = module.params.get("account_key_content") + self.account_key_passphrase = module.params.get("account_key_passphrase") # Grab account URI from module parameters. # Make sure empty string is treated as None. - self.account_uri = module.params.get('account_uri') or None + self.account_uri = module.params.get("account_uri") or None - self.request_timeout = module.params['request_timeout'] + self.request_timeout = module.params["request_timeout"] self.account_key_data = None self.account_jwk = None @@ -199,12 +237,15 @@ class ACMEClient(object): self.account_key_data = self.parse_key( key_file=self.account_key_file, key_content=self.account_key_content, - passphrase=self.account_key_passphrase) + passphrase=self.account_key_passphrase, + ) except KeyParsingError as e: - raise ModuleFailException("Error while parsing account key: {msg}".format(msg=e.msg)) - self.account_jwk = self.account_key_data['jwk'] + raise ModuleFailException( + "Error while parsing account key: {msg}".format(msg=e.msg) + ) + self.account_jwk = self.account_key_data["jwk"] self.account_jws_header = { - "alg": self.account_key_data['alg'], + "alg": self.account_key_data["alg"], "jwk": self.account_jwk, } if self.account_uri: @@ -214,56 +255,76 @@ class ACMEClient(object): self.directory = ACMEDirectory(module, self) def set_account_uri(self, uri): - ''' + """ Set account URI. For ACME v2, it needs to be used to sending signed requests. - ''' + """ self.account_uri = uri if self.version != 1: - self.account_jws_header.pop('jwk') - self.account_jws_header['kid'] = self.account_uri + self.account_jws_header.pop("jwk") + self.account_jws_header["kid"] = self.account_uri def parse_key(self, key_file=None, key_content=None, passphrase=None): - ''' + """ Parses an RSA or Elliptic Curve key file in PEM format and returns key_data. In case of an error, raises KeyParsingError. - ''' + """ if key_file is None and key_content is None: - raise AssertionError('One of key_file and key_content must be specified!') + raise AssertionError("One of key_file and key_content must be specified!") return self.backend.parse_key(key_file, key_content, passphrase=passphrase) def sign_request(self, protected, payload, key_data, encode_payload=True): - ''' + """ Signs an ACME request. - ''' + """ try: if payload is None: # POST-as-GET - payload64 = '' + payload64 = "" else: # POST if encode_payload: - payload = self.module.jsonify(payload).encode('utf8') + payload = self.module.jsonify(payload).encode("utf8") payload64 = nopad_b64(to_bytes(payload)) - protected64 = nopad_b64(self.module.jsonify(protected).encode('utf8')) + protected64 = nopad_b64(self.module.jsonify(protected).encode("utf8")) except Exception as e: - raise ModuleFailException("Failed to encode payload / headers as JSON: {0}".format(e)) + raise ModuleFailException( + "Failed to encode payload / headers as JSON: {0}".format(e) + ) return self.backend.sign(payload64, protected64, key_data) def _log(self, msg, data=None): - ''' + """ Write arguments to acme.log when logging is enabled. - ''' + """ if self._debug: - with open('acme.log', 'ab') as f: - f.write('[{0}] {1}\n'.format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%s'), msg).encode('utf-8')) + with open("acme.log", "ab") as f: + f.write( + "[{0}] {1}\n".format( + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%s"), msg + ).encode("utf-8") + ) if data is not None: - f.write('{0}\n\n'.format(json.dumps(data, indent=2, sort_keys=True)).encode('utf-8')) + f.write( + "{0}\n\n".format( + json.dumps(data, indent=2, sort_keys=True) + ).encode("utf-8") + ) - def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, - encode_payload=True, fail_on_error=True, error_msg=None, expected_status_codes=None): - ''' + def send_signed_request( + self, + url, + payload, + key_data=None, + jws_header=None, + parse_json_result=True, + encode_payload=True, + fail_on_error=True, + error_msg=None, + expected_status_codes=None, + ): + """ Sends a JWS signed HTTP POST request to the ACME server and returns the response as dictionary (if parse_json_result is True) or in raw form (if parse_json_result is False). @@ -271,7 +332,7 @@ class ACMEClient(object): If payload is None, a POST-as-GET is performed. (https://tools.ietf.org/html/rfc8555#section-6.3) - ''' + """ key_data = key_data or self.account_key_data jws_header = jws_header or self.account_jws_header failed_tries = 0 @@ -281,21 +342,30 @@ class ACMEClient(object): if self.version != 1: protected["url"] = url - self._log('URL', url) - self._log('protected', protected) - self._log('payload', payload) - data = self.sign_request(protected, payload, key_data, encode_payload=encode_payload) + self._log("URL", url) + self._log("protected", protected) + self._log("payload", payload) + data = self.sign_request( + protected, payload, key_data, encode_payload=encode_payload + ) if self.version == 1: data["header"] = jws_header.copy() for k, v in protected.items(): data["header"].pop(k, None) - self._log('signed request', data) + self._log("signed request", data) data = self.module.jsonify(data) headers = { - 'Content-Type': 'application/jose+json', + "Content-Type": "application/jose+json", } - resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST', timeout=self.request_timeout) + resp, info = fetch_url( + self.module, + url, + data=data, + headers=headers, + method="POST", + timeout=self.request_timeout, + ) if _decode_retry(self.module, resp, info, failed_tries): failed_tries += 1 continue @@ -309,20 +379,26 @@ class ACMEClient(object): raise TypeError content = resp.read() except (AttributeError, TypeError): - content = info.pop('body', None) + content = info.pop("body", None) if content or not parse_json_result: - if (parse_json_result and info['content-type'].startswith('application/json')) or 400 <= info['status'] < 600: + if ( + parse_json_result + and info["content-type"].startswith("application/json") + ) or 400 <= info["status"] < 600: try: - decoded_result = self.module.from_json(content.decode('utf8')) - self._log('parsed result', decoded_result) + decoded_result = self.module.from_json(content.decode("utf8")) + self._log("parsed result", decoded_result) # In case of badNonce error, try again (up to 5 times) # (https://tools.ietf.org/html/rfc8555#section-6.7) - if all(( - 400 <= info['status'] < 600, - decoded_result.get('type') == 'urn:ietf:params:acme:error:badNonce', - failed_tries <= 5, - )): + if all( + ( + 400 <= info["status"] < 600, + decoded_result.get("type") + == "urn:ietf:params:acme:error:badNonce", + failed_tries <= 5, + ) + ): failed_tries += 1 continue if parse_json_result: @@ -330,25 +406,46 @@ class ACMEClient(object): else: result = content except ValueError: - raise NetworkException("Failed to parse the ACME response: {0} {1}".format(url, content)) + raise NetworkException( + "Failed to parse the ACME response: {0} {1}".format( + url, content + ) + ) else: result = content - if fail_on_error and _is_failed(info, expected_status_codes=expected_status_codes): + if fail_on_error and _is_failed( + info, expected_status_codes=expected_status_codes + ): raise ACMEProtocolException( - self.module, msg=error_msg, info=info, content=content, content_json=result if parse_json_result else None) + self.module, + msg=error_msg, + info=info, + content=content, + content_json=result if parse_json_result else None, + ) return result, info - def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, - fail_on_error=True, error_msg=None, expected_status_codes=None): - ''' + def get_request( + self, + uri, + parse_json_result=True, + headers=None, + get_only=False, + fail_on_error=True, + error_msg=None, + expected_status_codes=None, + ): + """ Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback to GET if server replies with a status code of 405. - ''' + """ if not get_only and self.version != 1: # Try POST-as-GET - content, info = self.send_signed_request(uri, None, parse_json_result=False, fail_on_error=False) - if info['status'] == 405: + content, info = self.send_signed_request( + uri, None, parse_json_result=False, fail_on_error=False + ) + if info["status"] == 405: # Instead, do unauthenticated GET get_only = True else: @@ -359,7 +456,13 @@ class ACMEClient(object): # Perform unauthenticated GET retry_count = 0 while True: - resp, info = fetch_url(self.module, uri, method='GET', headers=headers, timeout=self.request_timeout) + resp, info = fetch_url( + self.module, + uri, + method="GET", + headers=headers, + timeout=self.request_timeout, + ) if not _decode_retry(self.module, resp, info, retry_count): break retry_count += 1 @@ -373,27 +476,38 @@ class ACMEClient(object): raise TypeError content = resp.read() except (AttributeError, TypeError): - content = info.pop('body', None) + content = info.pop("body", None) # Process result parsed_json_result = False if parse_json_result: result = {} if content: - if info['content-type'].startswith('application/json'): + if info["content-type"].startswith("application/json"): try: - result = self.module.from_json(content.decode('utf8')) + result = self.module.from_json(content.decode("utf8")) parsed_json_result = True except ValueError: - raise NetworkException("Failed to parse the ACME response: {0} {1}".format(uri, content)) + raise NetworkException( + "Failed to parse the ACME response: {0} {1}".format( + uri, content + ) + ) else: result = content else: result = content - if fail_on_error and _is_failed(info, expected_status_codes=expected_status_codes): + if fail_on_error and _is_failed( + info, expected_status_codes=expected_status_codes + ): raise ACMEProtocolException( - self.module, msg=error_msg, info=info, content=content, content_json=result if parsed_json_result else None) + self.module, + msg=error_msg, + info=info, + content=content, + content_json=result if parsed_json_result else None, + ) return result, info def get_renewal_info( @@ -406,19 +520,30 @@ class ACMEClient(object): retry_after_relative_with_timezone=True, ): if not self.directory.has_renewal_info_endpoint(): - raise ModuleFailException('The ACME endpoint does not support ACME Renewal Information retrieval') + raise ModuleFailException( + "The ACME endpoint does not support ACME Renewal Information retrieval" + ) if cert_id is None: - cert_id = compute_cert_id(self.backend, cert_info=cert_info, cert_filename=cert_filename, cert_content=cert_content) - url = '{base}/{cert_id}'.format(base=self.directory.directory['renewalInfo'].rstrip('/'), cert_id=cert_id) + cert_id = compute_cert_id( + self.backend, + cert_info=cert_info, + cert_filename=cert_filename, + cert_content=cert_content, + ) + url = "{base}/{cert_id}".format( + base=self.directory.directory["renewalInfo"].rstrip("/"), cert_id=cert_id + ) - data, info = self.get_request(url, parse_json_result=True, fail_on_error=True, get_only=True) + data, info = self.get_request( + url, parse_json_result=True, fail_on_error=True, get_only=True + ) # Include Retry-After header if asked for - if include_retry_after and 'retry-after' in info: + if include_retry_after and "retry-after" in info: try: - data['retryAfter'] = parse_retry_after( - info['retry-after'], + data["retryAfter"] = parse_retry_after( + info["retry-after"], relative_with_timezone=retry_after_relative_with_timezone, ) except ValueError: @@ -427,21 +552,23 @@ class ACMEClient(object): def get_default_argspec(): - ''' + """ Provides default argument spec for the options documented in the acme doc fragment. DEPRECATED: will be removed in community.crypto 3.0.0 - ''' + """ return dict( - acme_directory=dict(type='str', required=True), - acme_version=dict(type='int', required=True, choices=[1, 2]), - validate_certs=dict(type='bool', default=True), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']), - request_timeout=dict(type='int', default=10), - account_key_src=dict(type='path', aliases=['account_key']), - account_key_content=dict(type='str', no_log=True), - account_key_passphrase=dict(type='str', no_log=True), - account_uri=dict(type='str'), + acme_directory=dict(type="str", required=True), + acme_version=dict(type="int", required=True, choices=[1, 2]), + validate_certs=dict(type="bool", default=True), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "openssl", "cryptography"] + ), + request_timeout=dict(type="int", default=10), + account_key_src=dict(type="path", aliases=["account_key"]), + account_key_content=dict(type="str", no_log=True), + account_key_passphrase=dict(type="str", no_log=True), + account_uri=dict(type="str"), ) @@ -450,90 +577,109 @@ def create_default_argspec( require_account_key=True, with_certificate=False, ): - ''' + """ Provides default argument spec for the options documented in the acme doc fragment. - ''' + """ result = ArgumentSpec( argument_spec=dict( - acme_directory=dict(type='str', required=True), - acme_version=dict(type='int', required=True, choices=[1, 2]), - validate_certs=dict(type='bool', default=True), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']), - request_timeout=dict(type='int', default=10), + acme_directory=dict(type="str", required=True), + acme_version=dict(type="int", required=True, choices=[1, 2]), + validate_certs=dict(type="bool", default=True), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "openssl", "cryptography"] + ), + request_timeout=dict(type="int", default=10), ), ) if with_account: result.update_argspec( - account_key_src=dict(type='path', aliases=['account_key']), - account_key_content=dict(type='str', no_log=True), - account_key_passphrase=dict(type='str', no_log=True), - account_uri=dict(type='str'), + account_key_src=dict(type="path", aliases=["account_key"]), + account_key_content=dict(type="str", no_log=True), + account_key_passphrase=dict(type="str", no_log=True), + account_uri=dict(type="str"), ) if require_account_key: - result.update(required_one_of=[['account_key_src', 'account_key_content']]) - result.update(mutually_exclusive=[['account_key_src', 'account_key_content']]) + result.update(required_one_of=[["account_key_src", "account_key_content"]]) + result.update(mutually_exclusive=[["account_key_src", "account_key_content"]]) if with_certificate: result.update_argspec( - csr=dict(type='path'), - csr_content=dict(type='str'), + csr=dict(type="path"), + csr_content=dict(type="str"), ) result.update( - required_one_of=[['csr', 'csr_content']], - mutually_exclusive=[['csr', 'csr_content']], + required_one_of=[["csr", "csr_content"]], + mutually_exclusive=[["csr", "csr_content"]], ) return result def create_backend(module, needs_acme_v2): if not HAS_IPADDRESS: - module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR) + module.fail_json( + msg=missing_required_lib("ipaddress"), exception=IPADDRESS_IMPORT_ERROR + ) - backend = module.params['select_crypto_backend'] + backend = module.params["select_crypto_backend"] # Backend autodetect - if backend == 'auto': - backend = 'cryptography' if HAS_CURRENT_CRYPTOGRAPHY else 'openssl' + if backend == "auto": + backend = "cryptography" if HAS_CURRENT_CRYPTOGRAPHY else "openssl" # Create backend object - if backend == 'cryptography': + if backend == "cryptography": if CRYPTOGRAPHY_ERROR is not None: # Either we could not import cryptography at all, or there was an unexpected error if CRYPTOGRAPHY_VERSION is None: - msg = missing_required_lib('cryptography') + msg = missing_required_lib("cryptography") else: - msg = 'Unexpected error while preparing cryptography: {0}'.format(CRYPTOGRAPHY_ERROR.splitlines()[-1]) + msg = "Unexpected error while preparing cryptography: {0}".format( + CRYPTOGRAPHY_ERROR.splitlines()[-1] + ) module.fail_json(msg=msg, exception=CRYPTOGRAPHY_ERROR) if not HAS_CURRENT_CRYPTOGRAPHY: # We succeeded importing cryptography, but its version is too old. module.fail_json( - msg='Found cryptography, but only version {0}. {1}'.format( + msg="Found cryptography, but only version {0}. {1}".format( CRYPTOGRAPHY_VERSION, - missing_required_lib('cryptography >= {0}'.format(CRYPTOGRAPHY_MINIMAL_VERSION)))) - module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION)) + missing_required_lib( + "cryptography >= {0}".format(CRYPTOGRAPHY_MINIMAL_VERSION) + ), + ) + ) + module.debug( + "Using cryptography backend (library version {0})".format( + CRYPTOGRAPHY_VERSION + ) + ) module_backend = CryptographyBackend(module) - elif backend == 'openssl': - module.debug('Using OpenSSL binary backend') + elif backend == "openssl": + module.debug("Using OpenSSL binary backend") module_backend = OpenSSLCLIBackend(module) else: module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend)) # Check common module parameters - if not module.params['validate_certs']: + if not module.params["validate_certs"]: module.warn( - 'Disabling certificate validation for communications with ACME endpoint. ' - 'This should only be done for testing against a local ACME server for ' - 'development purposes, but *never* for production purposes.' + "Disabling certificate validation for communications with ACME endpoint. " + "This should only be done for testing against a local ACME server for " + "development purposes, but *never* for production purposes." ) - if needs_acme_v2 and module.params['acme_version'] < 2: - module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name)) + if needs_acme_v2 and module.params["acme_version"] < 2: + module.fail_json( + msg="The {0} module requires the ACME v2 protocol!".format(module._name) + ) - if module.params['acme_version'] == 1: - module.deprecate("The value 1 for 'acme_version' is deprecated. Please switch to ACME v2", - version='3.0.0', collection_name='community.crypto') + if module.params["acme_version"] == 1: + module.deprecate( + "The value 1 for 'acme_version' is deprecated. Please switch to ACME v2", + version="3.0.0", + collection_name="community.crypto", + ) # AnsibleModule() changes the locale, so change it back to C because we rely # on datetime.datetime.strptime() when parsing certificate dates. - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") return module_backend diff --git a/plugins/module_utils/acme/backend_cryptography.py b/plugins/module_utils/acme/backend_cryptography.py index 28e5c6c2..e4bfe6d2 100644 --- a/plugins/module_utils/acme/backend_cryptography.py +++ b/plugins/module_utils/acme/backend_cryptography.py @@ -57,7 +57,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -CRYPTOGRAPHY_MINIMAL_VERSION = '1.5' +CRYPTOGRAPHY_MINIMAL_VERSION = "1.5" CRYPTOGRAPHY_ERROR = None try: @@ -78,7 +78,9 @@ except ImportError: CRYPTOGRAPHY_ERROR = traceback.format_exc() else: CRYPTOGRAPHY_VERSION = cryptography.__version__ - HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion(CRYPTOGRAPHY_MINIMAL_VERSION)) + HAS_CURRENT_CRYPTOGRAPHY = LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion( + CRYPTOGRAPHY_MINIMAL_VERSION + ) try: if HAS_CURRENT_CRYPTOGRAPHY: _cryptography_backend = cryptography.hazmat.backends.default_backend() @@ -91,13 +93,19 @@ class CryptographyChainMatcher(ChainMatcher): def _parse_key_identifier(key_identifier, name, criterium_idx, module): if key_identifier: try: - return binascii.unhexlify(key_identifier.replace(':', '')) + return binascii.unhexlify(key_identifier.replace(":", "")) except Exception: if criterium_idx is None: - module.warn('Criterium has invalid {0} value. Ignoring criterium.'.format(name)) + module.warn( + "Criterium has invalid {0} value. Ignoring criterium.".format( + name + ) + ) else: - module.warn('Criterium {0} in select_chain has invalid {1} value. ' - 'Ignoring criterium.'.format(criterium_idx, name)) + module.warn( + "Criterium {0} in select_chain has invalid {1} value. " + "Ignoring criterium.".format(criterium_idx, name) + ) return None def __init__(self, criterium, module): @@ -107,16 +115,26 @@ class CryptographyChainMatcher(ChainMatcher): self.issuer = [] if criterium.subject: self.subject = [ - (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject, 'subject') + (cryptography_name_to_oid(k), to_native(v)) + for k, v in parse_name_field(criterium.subject, "subject") ] if criterium.issuer: self.issuer = [ - (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer, 'issuer') + (cryptography_name_to_oid(k), to_native(v)) + for k, v in parse_name_field(criterium.issuer, "issuer") ] self.subject_key_identifier = CryptographyChainMatcher._parse_key_identifier( - criterium.subject_key_identifier, 'subject_key_identifier', criterium.index, module) + criterium.subject_key_identifier, + "subject_key_identifier", + criterium.index, + module, + ) self.authority_key_identifier = CryptographyChainMatcher._parse_key_identifier( - criterium.authority_key_identifier, 'authority_key_identifier', criterium.index, module) + criterium.authority_key_identifier, + "authority_key_identifier", + criterium.index, + module, + ) def _match_subject(self, x509_subject, match_subject): for oid, value in match_subject: @@ -130,17 +148,19 @@ class CryptographyChainMatcher(ChainMatcher): return True def match(self, certificate): - ''' + """ Check whether an alternate chain matches the specified criterium. - ''' + """ chain = certificate.chain - if self.test_certificates == 'last': + if self.test_certificates == "last": chain = chain[-1:] - elif self.test_certificates == 'first': + elif self.test_certificates == "first": chain = chain[:1] for cert in chain: try: - x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography.hazmat.backends.default_backend()) + x509 = cryptography.x509.load_pem_x509_certificate( + to_bytes(cert), cryptography.hazmat.backends.default_backend() + ) matches = True if not self._match_subject(x509.subject, self.subject): matches = False @@ -148,14 +168,18 @@ class CryptographyChainMatcher(ChainMatcher): matches = False if self.subject_key_identifier: try: - ext = x509.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier) + ext = x509.extensions.get_extension_for_class( + cryptography.x509.SubjectKeyIdentifier + ) if self.subject_key_identifier != ext.value.digest: matches = False except cryptography.x509.ExtensionNotFound: matches = False if self.authority_key_identifier: try: - ext = x509.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier) + ext = x509.extensions.get_extension_for_class( + cryptography.x509.AuthorityKeyIdentifier + ) if self.authority_key_identifier != ext.value.key_identifier: matches = False except cryptography.x509.ExtensionNotFound: @@ -163,19 +187,23 @@ class CryptographyChainMatcher(ChainMatcher): if matches: return True except Exception as e: - self.module.warn('Error while loading certificate {0}: {1}'.format(cert, e)) + self.module.warn( + "Error while loading certificate {0}: {1}".format(cert, e) + ) return False class CryptographyBackend(CryptoBackend): def __init__(self, module): - super(CryptographyBackend, self).__init__(module, with_timezone=CRYPTOGRAPHY_TIMEZONE) + super(CryptographyBackend, self).__init__( + module, with_timezone=CRYPTOGRAPHY_TIMEZONE + ) def parse_key(self, key_file=None, key_content=None, passphrase=None): - ''' + """ Parses an RSA or Elliptic Curve key file in PEM format and returns key_data. Raises KeyParsingError in case of errors. - ''' + """ # If key_content is not given, read key_file if key_content is None: key_content = read_file(key_file) @@ -186,84 +214,97 @@ class CryptographyBackend(CryptoBackend): key = cryptography.hazmat.primitives.serialization.load_pem_private_key( key_content, password=to_bytes(passphrase) if passphrase is not None else None, - backend=_cryptography_backend) + backend=_cryptography_backend, + ) except Exception as e: - raise KeyParsingError('error while loading key: {0}'.format(e)) + raise KeyParsingError("error while loading key: {0}".format(e)) if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): pk = key.public_key().public_numbers() return { - 'key_obj': key, - 'type': 'rsa', - 'alg': 'RS256', - 'jwk': { + "key_obj": key, + "type": "rsa", + "alg": "RS256", + "jwk": { "kty": "RSA", "e": nopad_b64(convert_int_to_bytes(pk.e)), "n": nopad_b64(convert_int_to_bytes(pk.n)), }, - 'hash': 'sha256', + "hash": "sha256", } - elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + elif isinstance( + key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey + ): pk = key.public_key().public_numbers() - if pk.curve.name == 'secp256r1': + if pk.curve.name == "secp256r1": bits = 256 - alg = 'ES256' - hashalg = 'sha256' + alg = "ES256" + hashalg = "sha256" point_size = 32 - curve = 'P-256' - elif pk.curve.name == 'secp384r1': + curve = "P-256" + elif pk.curve.name == "secp384r1": bits = 384 - alg = 'ES384' - hashalg = 'sha384' + alg = "ES384" + hashalg = "sha384" point_size = 48 - curve = 'P-384' - elif pk.curve.name == 'secp521r1': + curve = "P-384" + elif pk.curve.name == "secp521r1": # Not yet supported on Let's Encrypt side, see # https://github.com/letsencrypt/boulder/issues/2217 bits = 521 - alg = 'ES512' - hashalg = 'sha512' + alg = "ES512" + hashalg = "sha512" point_size = 66 - curve = 'P-521' + curve = "P-521" else: - raise KeyParsingError('unknown elliptic curve: {0}'.format(pk.curve.name)) + raise KeyParsingError( + "unknown elliptic curve: {0}".format(pk.curve.name) + ) num_bytes = (bits + 7) // 8 return { - 'key_obj': key, - 'type': 'ec', - 'alg': alg, - 'jwk': { + "key_obj": key, + "type": "ec", + "alg": alg, + "jwk": { "kty": "EC", "crv": curve, "x": nopad_b64(convert_int_to_bytes(pk.x, count=num_bytes)), "y": nopad_b64(convert_int_to_bytes(pk.y, count=num_bytes)), }, - 'hash': hashalg, - 'point_size': point_size, + "hash": hashalg, + "point_size": point_size, } else: raise KeyParsingError('unknown key type "{0}"'.format(type(key))) def sign(self, payload64, protected64, key_data): - sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8') - if 'mac_obj' in key_data: - mac = key_data['mac_obj']() + sign_payload = "{0}.{1}".format(protected64, payload64).encode("utf8") + if "mac_obj" in key_data: + mac = key_data["mac_obj"]() mac.update(sign_payload) signature = mac.finalize() - elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + elif isinstance( + key_data["key_obj"], + cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey, + ): padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15() hashalg = cryptography.hazmat.primitives.hashes.SHA256 - signature = key_data['key_obj'].sign(sign_payload, padding, hashalg()) - elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): - if key_data['hash'] == 'sha256': + signature = key_data["key_obj"].sign(sign_payload, padding, hashalg()) + elif isinstance( + key_data["key_obj"], + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey, + ): + if key_data["hash"] == "sha256": hashalg = cryptography.hazmat.primitives.hashes.SHA256 - elif key_data['hash'] == 'sha384': + elif key_data["hash"] == "sha384": hashalg = cryptography.hazmat.primitives.hashes.SHA384 - elif key_data['hash'] == 'sha512': + elif key_data["hash"] == "sha512": hashalg = cryptography.hazmat.primitives.hashes.SHA512 ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg()) - r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa)) - rr = convert_int_to_hex(r, 2 * key_data['point_size']) - ss = convert_int_to_hex(s, 2 * key_data['point_size']) + r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature( + key_data["key_obj"].sign(sign_payload, ecdsa) + ) + rr = convert_int_to_hex(r, 2 * key_data["point_size"]) + ss = convert_int_to_hex(s, 2 * key_data["point_size"]) signature = binascii.unhexlify(rr) + binascii.unhexlify(ss) return { @@ -273,44 +314,50 @@ class CryptographyBackend(CryptoBackend): } def create_mac_key(self, alg, key): - '''Create a MAC key.''' - if alg == 'HS256': + """Create a MAC key.""" + if alg == "HS256": hashalg = cryptography.hazmat.primitives.hashes.SHA256 hashbytes = 32 - elif alg == 'HS384': + elif alg == "HS384": hashalg = cryptography.hazmat.primitives.hashes.SHA384 hashbytes = 48 - elif alg == 'HS512': + elif alg == "HS512": hashalg = cryptography.hazmat.primitives.hashes.SHA512 hashbytes = 64 else: - raise BackendException('Unsupported MAC key algorithm for cryptography backend: {0}'.format(alg)) + raise BackendException( + "Unsupported MAC key algorithm for cryptography backend: {0}".format( + alg + ) + ) key_bytes = base64.urlsafe_b64decode(key) if len(key_bytes) < hashbytes: raise BackendException( - '{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes)) + "{0} key must be at least {1} bytes long (after Base64 decoding)".format( + alg, hashbytes + ) + ) return { - 'mac_obj': lambda: cryptography.hazmat.primitives.hmac.HMAC( - key_bytes, - hashalg(), - _cryptography_backend), - 'type': 'hmac', - 'alg': alg, - 'jwk': { - 'kty': 'oct', - 'k': key, + "mac_obj": lambda: cryptography.hazmat.primitives.hmac.HMAC( + key_bytes, hashalg(), _cryptography_backend + ), + "type": "hmac", + "alg": alg, + "jwk": { + "kty": "oct", + "k": key, }, } def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a list of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. The list is deduplicated, and if a CNAME is present, it will be returned as the first element in the result. - ''' + """ if csr_content is None: csr_content = read_file(csr_filename) else: @@ -328,34 +375,43 @@ class CryptographyBackend(CryptoBackend): for sub in csr.subject: if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME: - add_identifier(('dns', sub.value)) + add_identifier(("dns", sub.value)) for extension in csr.extensions: - if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME: + if ( + extension.oid + == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ): for name in extension.value: if isinstance(name, cryptography.x509.DNSName): - add_identifier(('dns', name.value)) + add_identifier(("dns", name.value)) elif isinstance(name, cryptography.x509.IPAddress): - add_identifier(('ip', name.value.compressed)) + add_identifier(("ip", name.value.compressed)) else: - raise BackendException('Found unsupported SAN identifier {0}'.format(name)) + raise BackendException( + "Found unsupported SAN identifier {0}".format(name) + ) return result def get_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a set of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. - ''' - return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)) + """ + return set( + self.get_ordered_csr_identifiers( + csr_filename=csr_filename, csr_content=csr_content + ) + ) def get_cert_days(self, cert_filename=None, cert_content=None, now=None): - ''' + """ Return the days the certificate in cert_filename remains valid and -1 if the file was not found. If cert_filename contains more than one certificate, only the first one will be considered. If now is not specified, datetime.datetime.now() is used. - ''' + """ if cert_filename is not None: cert_content = None if os.path.exists(cert_filename): @@ -367,14 +423,18 @@ class CryptographyBackend(CryptoBackend): return -1 # Make sure we have at most one PEM. Otherwise cryptography 36.0.0 will barf. - cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or '') + cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or "") try: - cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend) + cert = cryptography.x509.load_pem_x509_certificate( + cert_content, _cryptography_backend + ) except Exception as e: if cert_filename is None: - raise BackendException('Cannot parse certificate: {0}'.format(e)) - raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e)) + raise BackendException("Cannot parse certificate: {0}".format(e)) + raise BackendException( + "Cannot parse certificate {0}: {1}".format(cert_filename, e) + ) if now is None: now = self.get_now() @@ -383,40 +443,48 @@ class CryptographyBackend(CryptoBackend): return (get_not_valid_after(cert) - now).days def create_chain_matcher(self, criterium): - ''' + """ Given a Criterium object, creates a ChainMatcher object. - ''' + """ return CryptographyChainMatcher(criterium, self.module) def get_cert_information(self, cert_filename=None, cert_content=None): - ''' + """ Return some information on a X.509 certificate as a CertificateInformation object. - ''' + """ if cert_filename is not None: cert_content = read_file(cert_filename) else: cert_content = to_bytes(cert_content) # Make sure we have at most one PEM. Otherwise cryptography 36.0.0 will barf. - cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or '') + cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or "") try: - cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend) + cert = cryptography.x509.load_pem_x509_certificate( + cert_content, _cryptography_backend + ) except Exception as e: if cert_filename is None: - raise BackendException('Cannot parse certificate: {0}'.format(e)) - raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e)) + raise BackendException("Cannot parse certificate: {0}".format(e)) + raise BackendException( + "Cannot parse certificate {0}: {1}".format(cert_filename, e) + ) ski = None try: - ext = cert.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier) + ext = cert.extensions.get_extension_for_class( + cryptography.x509.SubjectKeyIdentifier + ) ski = ext.value.digest except cryptography.x509.ExtensionNotFound: pass aki = None try: - ext = cert.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier) + ext = cert.extensions.get_extension_for_class( + cryptography.x509.AuthorityKeyIdentifier + ) aki = ext.value.key_identifier except cryptography.x509.ExtensionNotFound: pass diff --git a/plugins/module_utils/acme/backend_openssl_cli.py b/plugins/module_utils/acme/backend_openssl_cli.py index 1fd2f6b8..ae9238e5 100644 --- a/plugins/module_utils/acme/backend_openssl_cli.py +++ b/plugins/module_utils/acme/backend_openssl_cli.py @@ -45,7 +45,7 @@ except ImportError: pass -_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') +_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C") def _extract_date(out_text, name, cert_filename_suffix=""): @@ -55,11 +55,17 @@ def _extract_date(out_text, name, cert_filename_suffix=""): # even though the information is there and a supported timezone for all supported # Python implementations (GMT). So we have to modify the datetime object by # replacing it by UTC. - return ensure_utc_timezone(datetime.datetime.strptime(date_str, '%b %d %H:%M:%S %Y %Z')) + return ensure_utc_timezone( + datetime.datetime.strptime(date_str, "%b %d %H:%M:%S %Y %Z") + ) except AttributeError: - raise BackendException("No '{0}' date found{1}".format(name, cert_filename_suffix)) + raise BackendException( + "No '{0}' date found{1}".format(name, cert_filename_suffix) + ) except ValueError as exc: - raise BackendException("Failed to parse '{0}' date{1}: {2}".format(name, cert_filename_suffix, exc)) + raise BackendException( + "Failed to parse '{0}' date{1}: {2}".format(name, cert_filename_suffix, exc) + ) def _decode_octets(octets_text): @@ -69,7 +75,11 @@ def _decode_octets(octets_text): def _extract_octets(out_text, name, required=True, potential_prefixes=None): regexp = r"\s+%s:\s*\n\s+%s([A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2})*)\s*\n" % ( name, - ('(?:%s)' % '|'.join(re.escape(pp) for pp in potential_prefixes)) if potential_prefixes else '', + ( + ("(?:%s)" % "|".join(re.escape(pp) for pp in potential_prefixes)) + if potential_prefixes + else "" + ), ) match = re.search(regexp, out_text, re.MULTILINE | re.DOTALL) if match is not None: @@ -83,36 +93,41 @@ class OpenSSLCLIBackend(CryptoBackend): def __init__(self, module, openssl_binary=None): super(OpenSSLCLIBackend, self).__init__(module, with_timezone=True) if openssl_binary is None: - openssl_binary = module.get_bin_path('openssl', True) + openssl_binary = module.get_bin_path("openssl", True) self.openssl_binary = openssl_binary def parse_key(self, key_file=None, key_content=None, passphrase=None): - ''' + """ Parses an RSA or Elliptic Curve key file in PEM format and returns key_data. Raises KeyParsingError in case of errors. - ''' + """ if passphrase is not None: - raise KeyParsingError('openssl backend does not support key passphrases') + raise KeyParsingError("openssl backend does not support key passphrases") # If key_file is not given, but key_content, write that to a temporary file if key_file is None: 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(key_content.encode('utf-8')) + f.write(key_content.encode("utf-8")) key_file = tmpsrc except Exception as err: try: f.close() except Exception: pass - raise KeyParsingError("failed to create temporary content file: %s" % to_native(err), exception=traceback.format_exc()) + raise KeyParsingError( + "failed to create temporary content file: %s" % to_native(err), + exception=traceback.format_exc(), + ) f.close() # Parse key account_key_type = None with open(key_file, "rt") as f: for line in f: - m = re.match(r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line) + m = re.match( + r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line + ) if m is not None: account_key_type = m.group(1).lower() break @@ -125,111 +140,162 @@ class OpenSSLCLIBackend(CryptoBackend): if account_key_type not in ("rsa", "ec"): raise KeyParsingError('unknown key type "%s"' % account_key_type) - openssl_keydump_cmd = [self.openssl_binary, account_key_type, "-in", key_file, "-noout", "-text"] + openssl_keydump_cmd = [ + self.openssl_binary, + account_key_type, + "-in", + key_file, + "-noout", + "-text", + ] rc, out, err = self.module.run_command( - openssl_keydump_cmd, check_rc=False, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) + openssl_keydump_cmd, + check_rc=False, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) if rc != 0: - raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_keydump_cmd), stderr=to_text(err))) + raise BackendException( + "Error while running {cmd}: {stderr}".format( + cmd=" ".join(openssl_keydump_cmd), stderr=to_text(err) + ) + ) - out_text = to_text(out, errors='surrogate_or_strict') + out_text = to_text(out, errors="surrogate_or_strict") - if account_key_type == 'rsa': - pub_hex = re.search(r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent", out_text, re.MULTILINE | re.DOTALL).group(1) + if account_key_type == "rsa": + pub_hex = re.search( + r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent", + out_text, + re.MULTILINE | re.DOTALL, + ).group(1) - pub_exp = re.search(r"\npublicExponent: ([0-9]+)", out_text, re.MULTILINE | re.DOTALL).group(1) + pub_exp = re.search( + r"\npublicExponent: ([0-9]+)", out_text, re.MULTILINE | re.DOTALL + ).group(1) pub_exp = "{0:x}".format(int(pub_exp)) if len(pub_exp) % 2: pub_exp = "0{0}".format(pub_exp) return { - 'key_file': key_file, - 'type': 'rsa', - 'alg': 'RS256', - 'jwk': { + "key_file": key_file, + "type": "rsa", + "alg": "RS256", + "jwk": { "kty": "RSA", "e": nopad_b64(binascii.unhexlify(pub_exp.encode("utf-8"))), "n": nopad_b64(_decode_octets(pub_hex)), }, - 'hash': 'sha256', + "hash": "sha256", } - elif account_key_type == 'ec': + elif account_key_type == "ec": pub_data = re.search( r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?", out_text, re.MULTILINE | re.DOTALL, ) if pub_data is None: - raise KeyParsingError('cannot parse elliptic curve key') + raise KeyParsingError("cannot parse elliptic curve key") pub_hex = _decode_octets(pub_data.group(1)) asn1_oid_curve = pub_data.group(2).lower() nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None - if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256': + if asn1_oid_curve == "prime256v1" or nist_curve == "p-256": bits = 256 - alg = 'ES256' - hashalg = 'sha256' + alg = "ES256" + hashalg = "sha256" point_size = 32 - curve = 'P-256' - elif asn1_oid_curve == 'secp384r1' or nist_curve == 'p-384': + curve = "P-256" + elif asn1_oid_curve == "secp384r1" or nist_curve == "p-384": bits = 384 - alg = 'ES384' - hashalg = 'sha384' + alg = "ES384" + hashalg = "sha384" point_size = 48 - curve = 'P-384' - elif asn1_oid_curve == 'secp521r1' or nist_curve == 'p-521': + curve = "P-384" + elif asn1_oid_curve == "secp521r1" or nist_curve == "p-521": # Not yet supported on Let's Encrypt side, see # https://github.com/letsencrypt/boulder/issues/2217 bits = 521 - alg = 'ES512' - hashalg = 'sha512' + alg = "ES512" + hashalg = "sha512" point_size = 66 - curve = 'P-521' + curve = "P-521" else: - raise KeyParsingError('unknown elliptic curve: %s / %s' % (asn1_oid_curve, nist_curve)) + raise KeyParsingError( + "unknown elliptic curve: %s / %s" % (asn1_oid_curve, nist_curve) + ) num_bytes = (bits + 7) // 8 if len(pub_hex) != 2 * num_bytes: - raise KeyParsingError('bad elliptic curve point (%s / %s)' % (asn1_oid_curve, nist_curve)) + raise KeyParsingError( + "bad elliptic curve point (%s / %s)" % (asn1_oid_curve, nist_curve) + ) return { - 'key_file': key_file, - 'type': 'ec', - 'alg': alg, - 'jwk': { + "key_file": key_file, + "type": "ec", + "alg": alg, + "jwk": { "kty": "EC", "crv": curve, "x": nopad_b64(pub_hex[:num_bytes]), "y": nopad_b64(pub_hex[num_bytes:]), }, - 'hash': hashalg, - 'point_size': point_size, + "hash": hashalg, + "point_size": point_size, } def sign(self, payload64, protected64, key_data): - sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8') - if key_data['type'] == 'hmac': - hex_key = to_native(binascii.hexlify(base64.urlsafe_b64decode(key_data['jwk']['k']))) - cmd_postfix = ["-mac", "hmac", "-macopt", "hexkey:{0}".format(hex_key), "-binary"] + sign_payload = "{0}.{1}".format(protected64, payload64).encode("utf8") + if key_data["type"] == "hmac": + hex_key = to_native( + binascii.hexlify(base64.urlsafe_b64decode(key_data["jwk"]["k"])) + ) + cmd_postfix = [ + "-mac", + "hmac", + "-macopt", + "hexkey:{0}".format(hex_key), + "-binary", + ] else: - cmd_postfix = ["-sign", key_data['key_file']] - openssl_sign_cmd = [self.openssl_binary, "dgst", "-{0}".format(key_data['hash'])] + cmd_postfix + cmd_postfix = ["-sign", key_data["key_file"]] + openssl_sign_cmd = [ + self.openssl_binary, + "dgst", + "-{0}".format(key_data["hash"]), + ] + cmd_postfix rc, out, err = self.module.run_command( - openssl_sign_cmd, data=sign_payload, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) + openssl_sign_cmd, + data=sign_payload, + check_rc=False, + binary_data=True, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) if rc != 0: - raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_sign_cmd), stderr=to_text(err))) + raise BackendException( + "Error while running {cmd}: {stderr}".format( + cmd=" ".join(openssl_sign_cmd), stderr=to_text(err) + ) + ) - if key_data['type'] == 'ec': + if key_data["type"] == "ec": dummy, der_out, dummy = self.module.run_command( [self.openssl_binary, "asn1parse", "-inform", "DER"], - data=out, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) - expected_len = 2 * key_data['point_size'] + data=out, + binary_data=True, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) + expected_len = 2 * key_data["point_size"] sig = re.findall( r"prim:\s+INTEGER\s+:([0-9A-F]{1,%s})\n" % expected_len, - to_text(der_out, errors='surrogate_or_strict')) + to_text(der_out, errors="surrogate_or_strict"), + ) if len(sig) != 2: raise BackendException( "failed to generate Elliptic Curve signature; cannot parse DER output: {0}".format( - to_text(der_out, errors='surrogate_or_strict'))) - sig[0] = (expected_len - len(sig[0])) * '0' + sig[0] - sig[1] = (expected_len - len(sig[1])) * '0' + sig[1] + to_text(der_out, errors="surrogate_or_strict") + ) + ) + sig[0] = (expected_len - len(sig[0])) * "0" + sig[0] + sig[1] = (expected_len - len(sig[1])) * "0" + sig[1] out = binascii.unhexlify(sig[0]) + binascii.unhexlify(sig[1]) return { @@ -239,30 +305,35 @@ class OpenSSLCLIBackend(CryptoBackend): } def create_mac_key(self, alg, key): - '''Create a MAC key.''' - if alg == 'HS256': - hashalg = 'sha256' + """Create a MAC key.""" + if alg == "HS256": + hashalg = "sha256" hashbytes = 32 - elif alg == 'HS384': - hashalg = 'sha384' + elif alg == "HS384": + hashalg = "sha384" hashbytes = 48 - elif alg == 'HS512': - hashalg = 'sha512' + elif alg == "HS512": + hashalg = "sha512" hashbytes = 64 else: - raise BackendException('Unsupported MAC key algorithm for OpenSSL backend: {0}'.format(alg)) + raise BackendException( + "Unsupported MAC key algorithm for OpenSSL backend: {0}".format(alg) + ) key_bytes = base64.urlsafe_b64decode(key) if len(key_bytes) < hashbytes: raise BackendException( - '{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes)) + "{0} key must be at least {1} bytes long (after Base64 decoding)".format( + alg, hashbytes + ) + ) return { - 'type': 'hmac', - 'alg': alg, - 'jwk': { - 'kty': 'oct', - 'k': key, + "type": "hmac", + "alg": alg, + "jwk": { + "kty": "oct", + "k": key, }, - 'hash': hashalg, + "hash": hashalg, } @staticmethod @@ -274,25 +345,41 @@ class OpenSSLCLIBackend(CryptoBackend): return ip def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a list of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. The list is deduplicated, and if a CNAME is present, it will be returned as the first element in the result. - ''' + """ filename = csr_filename data = None if csr_content is not None: - filename = '/dev/stdin' - data = csr_content.encode('utf-8') + filename = "/dev/stdin" + data = csr_content.encode("utf-8") - openssl_csr_cmd = [self.openssl_binary, "req", "-in", filename, "-noout", "-text"] + openssl_csr_cmd = [ + self.openssl_binary, + "req", + "-in", + filename, + "-noout", + "-text", + ] rc, out, err = self.module.run_command( - openssl_csr_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) + openssl_csr_cmd, + data=data, + check_rc=False, + binary_data=True, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) if rc != 0: - raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_csr_cmd), stderr=to_text(err))) + raise BackendException( + "Error while running {cmd}: {stderr}".format( + cmd=" ".join(openssl_csr_cmd), stderr=to_text(err) + ) + ) identifiers = set() result = [] @@ -303,61 +390,90 @@ class OpenSSLCLIBackend(CryptoBackend): identifiers.add(identifier) result.append(identifier) - common_name = re.search(r"Subject:.* CN\s?=\s?([^\s,;/]+)", to_text(out, errors='surrogate_or_strict')) + common_name = re.search( + r"Subject:.* CN\s?=\s?([^\s,;/]+)", + to_text(out, errors="surrogate_or_strict"), + ) if common_name is not None: - add_identifier(('dns', common_name.group(1))) + add_identifier(("dns", common_name.group(1))) subject_alt_names = re.search( r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n", - to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL) + to_text(out, errors="surrogate_or_strict"), + re.MULTILINE | re.DOTALL, + ) if subject_alt_names is not None: for san in subject_alt_names.group(1).split(", "): if san.lower().startswith("dns:"): - add_identifier(('dns', san[4:])) + add_identifier(("dns", san[4:])) elif san.lower().startswith("ip:"): - add_identifier(('ip', self._normalize_ip(san[3:]))) + add_identifier(("ip", self._normalize_ip(san[3:]))) elif san.lower().startswith("ip address:"): - add_identifier(('ip', self._normalize_ip(san[11:]))) + add_identifier(("ip", self._normalize_ip(san[11:]))) else: - raise BackendException('Found unsupported SAN identifier "{0}"'.format(san)) + raise BackendException( + 'Found unsupported SAN identifier "{0}"'.format(san) + ) return result def get_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a set of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. - ''' - return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)) + """ + return set( + self.get_ordered_csr_identifiers( + csr_filename=csr_filename, csr_content=csr_content + ) + ) def get_cert_days(self, cert_filename=None, cert_content=None, now=None): - ''' + """ Return the days the certificate in cert_filename remains valid and -1 if the file was not found. If cert_filename contains more than one certificate, only the first one will be considered. If now is not specified, datetime.datetime.now() is used. - ''' + """ filename = cert_filename data = None if cert_content is not None: - filename = '/dev/stdin' - data = cert_content.encode('utf-8') - cert_filename_suffix = '' + filename = "/dev/stdin" + data = cert_content.encode("utf-8") + cert_filename_suffix = "" elif cert_filename is not None: if not os.path.exists(cert_filename): return -1 - cert_filename_suffix = ' in {0}'.format(cert_filename) + cert_filename_suffix = " in {0}".format(cert_filename) else: return -1 - openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"] + openssl_cert_cmd = [ + self.openssl_binary, + "x509", + "-in", + filename, + "-noout", + "-text", + ] rc, out, err = self.module.run_command( - openssl_cert_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) + openssl_cert_cmd, + data=data, + check_rc=False, + binary_data=True, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) if rc != 0: - raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_cert_cmd), stderr=to_text(err))) + raise BackendException( + "Error while running {cmd}: {stderr}".format( + cmd=" ".join(openssl_cert_cmd), stderr=to_text(err) + ) + ) - out_text = to_text(out, errors='surrogate_or_strict') - not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix) + out_text = to_text(out, errors="surrogate_or_strict") + not_after = _extract_date( + out_text, "Not After", cert_filename_suffix=cert_filename_suffix + ) if now is None: now = self.get_now() else: @@ -365,45 +481,76 @@ class OpenSSLCLIBackend(CryptoBackend): return (not_after - now).days def create_chain_matcher(self, criterium): - ''' + """ Given a Criterium object, creates a ChainMatcher object. - ''' - raise BackendException('Alternate chain matching can only be used with the "cryptography" backend.') + """ + raise BackendException( + 'Alternate chain matching can only be used with the "cryptography" backend.' + ) def get_cert_information(self, cert_filename=None, cert_content=None): - ''' + """ Return some information on a X.509 certificate as a CertificateInformation object. - ''' + """ filename = cert_filename data = None if cert_filename is not None: - cert_filename_suffix = ' in {0}'.format(cert_filename) + cert_filename_suffix = " in {0}".format(cert_filename) else: - filename = '/dev/stdin' + filename = "/dev/stdin" data = to_bytes(cert_content) - cert_filename_suffix = '' + cert_filename_suffix = "" - openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"] + openssl_cert_cmd = [ + self.openssl_binary, + "x509", + "-in", + filename, + "-noout", + "-text", + ] rc, out, err = self.module.run_command( - openssl_cert_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE) + openssl_cert_cmd, + data=data, + check_rc=False, + binary_data=True, + environ_update=_OPENSSL_ENVIRONMENT_UPDATE, + ) if rc != 0: - raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_cert_cmd), stderr=to_text(err))) + raise BackendException( + "Error while running {cmd}: {stderr}".format( + cmd=" ".join(openssl_cert_cmd), stderr=to_text(err) + ) + ) - out_text = to_text(out, errors='surrogate_or_strict') + out_text = to_text(out, errors="surrogate_or_strict") - not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix) - not_before = _extract_date(out_text, 'Not Before', cert_filename_suffix=cert_filename_suffix) + not_after = _extract_date( + out_text, "Not After", cert_filename_suffix=cert_filename_suffix + ) + not_before = _extract_date( + out_text, "Not Before", cert_filename_suffix=cert_filename_suffix + ) sn = re.search( r" Serial Number: ([0-9]+)", - to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL) + to_text(out, errors="surrogate_or_strict"), + re.MULTILINE | re.DOTALL, + ) if sn: serial = int(sn.group(1)) else: - serial = convert_bytes_to_int(_extract_octets(out_text, 'Serial Number', required=True)) + serial = convert_bytes_to_int( + _extract_octets(out_text, "Serial Number", required=True) + ) - ski = _extract_octets(out_text, 'X509v3 Subject Key Identifier', required=False) - aki = _extract_octets(out_text, 'X509v3 Authority Key Identifier', required=False, potential_prefixes=['keyid:', '']) + ski = _extract_octets(out_text, "X509v3 Subject Key Identifier", required=False) + aki = _extract_octets( + out_text, + "X509v3 Authority Key Identifier", + required=False, + potential_prefixes=["keyid:", ""], + ) return CertificateInformation( not_valid_after=not_after, diff --git a/plugins/module_utils/acme/backends.py b/plugins/module_utils/acme/backends.py index 4e87c428..8e19fa65 100644 --- a/plugins/module_utils/acme/backends.py +++ b/plugins/module_utils/acme/backends.py @@ -36,18 +36,20 @@ from ansible_collections.community.crypto.plugins.module_utils.time import ( CertificateInformation = namedtuple( - 'CertificateInformation', + "CertificateInformation", ( - 'not_valid_after', - 'not_valid_before', - 'serial_number', - 'subject_key_identifier', - 'authority_key_identifier', + "not_valid_after", + "not_valid_before", + "serial_number", + "subject_key_identifier", + "authority_key_identifier", ), ) -_FRACTIONAL_MATCHER = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(|\.\d+)(Z|[+-]\d{2}:?\d{2}.*)$') +_FRACTIONAL_MATCHER = re.compile( + r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(|\.\d+)(Z|[+-]\d{2}:?\d{2}.*)$" +) def _reduce_fractional_digits(timestamp_str): @@ -57,13 +59,15 @@ def _reduce_fractional_digits(timestamp_str): # RFC 3339 (https://www.rfc-editor.org/info/rfc3339) m = _FRACTIONAL_MATCHER.match(timestamp_str) if not m: - raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str)) + raise BackendException( + "Cannot parse ISO 8601 timestamp {0!r}".format(timestamp_str) + ) timestamp, fractional, timezone = m.groups() if len(fractional) > 7: # Python does not support anything smaller than microseconds # (Golang supports nanoseconds, Boulder often emits more fractional digits, which Python chokes on) fractional = fractional[:7] - return '%s%s%s' % (timestamp, fractional, timezone) + return "%s%s%s" % (timestamp, fractional, timezone) def _parse_acme_timestamp(timestamp_str, with_timezone): @@ -72,15 +76,26 @@ def _parse_acme_timestamp(timestamp_str, with_timezone): """ # RFC 3339 (https://www.rfc-editor.org/info/rfc3339) timestamp_str = _reduce_fractional_digits(timestamp_str) - for format in ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z'): + for format in ( + "%Y-%m-%dT%H:%M:%SZ", + "%Y-%m-%dT%H:%M:%S.%fZ", + "%Y-%m-%dT%H:%M:%S%z", + "%Y-%m-%dT%H:%M:%S.%f%z", + ): # Note that %z will not work with Python 2... https://stackoverflow.com/a/27829491 try: result = datetime.datetime.strptime(timestamp_str, format) except ValueError: pass else: - return ensure_utc_timezone(result) if with_timezone else remove_timezone(result) - raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str)) + return ( + ensure_utc_timezone(result) + if with_timezone + else remove_timezone(result) + ) + raise BackendException( + "Cannot parse ISO 8601 timestamp {0!r}".format(timestamp_str) + ) @six.add_metaclass(abc.ABCMeta) @@ -98,30 +113,34 @@ class CryptoBackend(object): def parse_module_parameter(self, value, name): try: - return get_relative_time_option(value, name, backend='cryptography', with_timezone=self._with_timezone) + return get_relative_time_option( + value, name, backend="cryptography", with_timezone=self._with_timezone + ) except OpenSSLObjectError as exc: raise BackendException(to_native(exc)) def interpolate_timestamp(self, timestamp_start, timestamp_end, percentage): start = get_epoch_seconds(timestamp_start) end = get_epoch_seconds(timestamp_end) - return from_epoch_seconds(start + percentage * (end - start), with_timezone=self._with_timezone) + return from_epoch_seconds( + start + percentage * (end - start), with_timezone=self._with_timezone + ) def get_utc_datetime(self, *args, **kwargs): kwargs_ext = dict(kwargs) - if self._with_timezone and ('tzinfo' not in kwargs_ext and len(args) < 8): - kwargs_ext['tzinfo'] = UTC + if self._with_timezone and ("tzinfo" not in kwargs_ext and len(args) < 8): + kwargs_ext["tzinfo"] = UTC result = datetime.datetime(*args, **kwargs_ext) - if self._with_timezone and ('tzinfo' in kwargs or len(args) >= 8): + if self._with_timezone and ("tzinfo" in kwargs or len(args) >= 8): result = ensure_utc_timezone(result) return result @abc.abstractmethod def parse_key(self, key_file=None, key_content=None, passphrase=None): - ''' + """ Parses an RSA or Elliptic Curve key file in PEM format and returns key_data. Raises KeyParsingError in case of errors. - ''' + """ @abc.abstractmethod def sign(self, payload64, protected64, key_data): @@ -129,54 +148,56 @@ class CryptoBackend(object): @abc.abstractmethod def create_mac_key(self, alg, key): - '''Create a MAC key.''' + """Create a MAC key.""" def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a list of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. The list is deduplicated, and if a CNAME is present, it will be returned as the first element in the result. - ''' + """ self.module.deprecate( "Every backend must override the get_ordered_csr_identifiers() method." " The default implementation will be removed in 3.0.0 and this method will be marked as `abstractmethod` by then.", - version='3.0.0', - collection_name='community.crypto', + version="3.0.0", + collection_name="community.crypto", + ) + return sorted( + self.get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content) ) - return sorted(self.get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)) @abc.abstractmethod def get_csr_identifiers(self, csr_filename=None, csr_content=None): - ''' + """ Return a set of requested identifiers (CN and SANs) for the CSR. Each identifier is a pair (type, identifier), where type is either 'dns' or 'ip'. - ''' + """ @abc.abstractmethod def get_cert_days(self, cert_filename=None, cert_content=None, now=None): - ''' + """ Return the days the certificate in cert_filename remains valid and -1 if the file was not found. If cert_filename contains more than one certificate, only the first one will be considered. If now is not specified, datetime.datetime.now() is used. - ''' + """ @abc.abstractmethod def create_chain_matcher(self, criterium): - ''' + """ Given a Criterium object, creates a ChainMatcher object. - ''' + """ def get_cert_information(self, cert_filename=None, cert_content=None): - ''' + """ Return some information on a X.509 certificate as a CertificateInformation object. - ''' + """ # Not implementing this method in a backend is DEPRECATED and will be # disallowed in community.crypto 3.0.0. This method will be marked as # @abstractmethod by then. - raise BackendException('This backend does not support get_cert_information()') + raise BackendException("This backend does not support get_cert_information()") diff --git a/plugins/module_utils/acme/certificate.py b/plugins/module_utils/acme/certificate.py index a7f35541..a8509381 100644 --- a/plugins/module_utils/acme/certificate.py +++ b/plugins/module_utils/acme/certificate.py @@ -37,38 +37,44 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import class ACMECertificateClient(object): - ''' + """ ACME v2 client class. Uses an ACME account object and a CSR to start and validate ACME challenges and download the respective certificates. - ''' + """ def __init__(self, module, backend, client=None, account=None): self.module = module - self.version = module.params['acme_version'] - self.csr = module.params.get('csr') - self.csr_content = module.params.get('csr_content') + self.version = module.params["acme_version"] + self.csr = module.params.get("csr") + self.csr_content = module.params.get("csr_content") if client is None: client = ACMEClient(module, backend) self.client = client if account is None: account = ACMEAccount(self.client) self.account = account - self.order_uri = module.params.get('order_uri') - self.order_creation_error_strategy = module.params.get('order_creation_error_strategy', 'auto') - self.order_creation_max_retries = module.params.get('order_creation_max_retries', 3) + self.order_uri = module.params.get("order_uri") + self.order_creation_error_strategy = module.params.get( + "order_creation_error_strategy", "auto" + ) + self.order_creation_max_retries = module.params.get( + "order_creation_max_retries", 3 + ) # Make sure account exists dummy, account_data = self.account.setup_account(allow_creation=False) if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException(msg="Account does not exist or is deactivated.") if self.csr is not None and not os.path.exists(self.csr): raise ModuleFailException("CSR %s not found" % (self.csr)) # Extract list of identifiers from CSR if self.csr is not None or self.csr_content is not None: - self.identifiers = self.client.backend.get_ordered_csr_identifiers(csr_filename=self.csr, csr_content=self.csr_content) + self.identifiers = self.client.backend.get_ordered_csr_identifiers( + csr_filename=self.csr, csr_content=self.csr_content + ) else: self.identifiers = None @@ -78,24 +84,31 @@ class ACMECertificateClient(object): for criterium_idx, criterium in enumerate(select_chain): try: select_chain_matcher.append( - self.client.backend.create_chain_matcher(Criterium(criterium, index=criterium_idx))) + self.client.backend.create_chain_matcher( + Criterium(criterium, index=criterium_idx) + ) + ) except ValueError as exc: - self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc)) + self.module.warn( + "Error while parsing criterium: {error}. Ignoring criterium.".format( + error=exc + ) + ) return select_chain_matcher def load_order(self): if not self.order_uri: - raise ModuleFailException('The order URI has not been provided') + raise ModuleFailException("The order URI has not been provided") order = Order.from_url(self.client, self.order_uri) order.load_authorizations(self.client) return order def create_order(self, replaces_cert_id=None, profile=None): - ''' + """ Create a new order. - ''' + """ if self.identifiers is None: - raise ModuleFailException('No identifiers have been provided') + raise ModuleFailException("No identifiers have been provided") order = Order.create_with_error_handling( self.client, self.identifiers, @@ -110,64 +123,78 @@ class ACMECertificateClient(object): return order def get_challenges_data(self, order): - ''' + """ Get challenge details. Return a tuple of generic challenge details, and specialized DNS challenge details. - ''' + """ # Get general challenge data data = [] for authz in order.authorizations.values(): # Skip valid authentications: their challenges are already valid # and do not need to be returned - if authz.status == 'valid': + if authz.status == "valid": continue - data.append(dict( - identifier=authz.identifier, - identifier_type=authz.identifier_type, - challenges=authz.get_challenge_data(self.client), - )) + data.append( + dict( + identifier=authz.identifier, + identifier_type=authz.identifier_type, + challenges=authz.get_challenge_data(self.client), + ) + ) # Get DNS challenge data data_dns = {} - dns_challenge_type = 'dns-01' + dns_challenge_type = "dns-01" for entry in data: - dns_challenge = entry['challenges'].get(dns_challenge_type) + dns_challenge = entry["challenges"].get(dns_challenge_type) if dns_challenge: - values = data_dns.get(dns_challenge['record']) + values = data_dns.get(dns_challenge["record"]) if values is None: values = [] - data_dns[dns_challenge['record']] = values - values.append(dns_challenge['resource_value']) + data_dns[dns_challenge["record"]] = values + values.append(dns_challenge["resource_value"]) return data, data_dns def check_that_authorizations_can_be_used(self, order): bad_authzs = [] for authz in order.authorizations.values(): - if authz.status not in ('valid', 'pending'): - bad_authzs.append('{authz} (status={status!r})'.format( - authz=authz.combined_identifier, - status=authz.status, - )) + if authz.status not in ("valid", "pending"): + bad_authzs.append( + "{authz} (status={status!r})".format( + authz=authz.combined_identifier, + status=authz.status, + ) + ) if bad_authzs: raise ModuleFailException( - 'Some of the authorizations for the order are in a bad state, so the order' - ' can no longer be satisfied: {bad_authzs}'.format( - bad_authzs=', '.join(sorted(bad_authzs)), + "Some of the authorizations for the order are in a bad state, so the order" + " can no longer be satisfied: {bad_authzs}".format( + bad_authzs=", ".join(sorted(bad_authzs)), ), ) def collect_invalid_authzs(self, order): - return [authz for authz in order.authorizations.values() if authz.status == 'invalid'] + return [ + authz + for authz in order.authorizations.values() + if authz.status == "invalid" + ] def collect_pending_authzs(self, order): - return [authz for authz in order.authorizations.values() if authz.status == 'pending'] + return [ + authz + for authz in order.authorizations.values() + if authz.status == "pending" + ] def call_validate(self, pending_authzs, get_challenge, wait=True): authzs_with_challenges_to_wait_for = [] for authz in pending_authzs: challenge_type = get_challenge(authz) authz.call_validate(self.client, challenge_type, wait=wait) - authzs_with_challenges_to_wait_for.append((authz, challenge_type, authz.find_challenge(challenge_type))) + authzs_with_challenges_to_wait_for.append( + (authz, challenge_type, authz.find_challenge(challenge_type)) + ) return authzs_with_challenges_to_wait_for def wait_for_validation(self, authzs_to_wait_for): @@ -179,27 +206,45 @@ class ACMECertificateClient(object): try: alt_cert = CertificateChain.download(self.client, alternate) except ModuleFailException as e: - self.module.warn('Error while downloading alternative certificate {0}: {1}'.format(alternate, e)) + self.module.warn( + "Error while downloading alternative certificate {0}: {1}".format( + alternate, e + ) + ) continue if alt_cert.cert is not None: alternate_chains.append(alt_cert) else: - self.module.warn('Error while downloading alternative certificate {0}: no certificate found'.format(alternate)) + self.module.warn( + "Error while downloading alternative certificate {0}: no certificate found".format( + alternate + ) + ) return alternate_chains def download_certificate(self, order, download_all_chains=True): - ''' + """ Download certificate from a valid oder. - ''' - if order.status != 'valid': - raise ModuleFailException('The order must be valid, but has state {state!r}!'.format(state=order.state)) + """ + if order.status != "valid": + raise ModuleFailException( + "The order must be valid, but has state {state!r}!".format( + state=order.state + ) + ) if not order.certificate_uri: - raise ModuleFailException("Order's crtificate URL {url!r} is empty!".format(url=order.certificate_uri)) + raise ModuleFailException( + "Order's crtificate URL {url!r} is empty!".format( + url=order.certificate_uri + ) + ) cert = CertificateChain.download(self.client, order.certificate_uri) if cert.cert is None: - raise ModuleFailException('Certificate at {url} is empty!'.format(url=order.certificate_uri)) + raise ModuleFailException( + "Certificate at {url} is empty!".format(url=order.certificate_uri) + ) alternate_chains = None if download_all_chains: @@ -208,15 +253,18 @@ class ACMECertificateClient(object): return cert, alternate_chains def get_certificate(self, order, download_all_chains=True): - ''' + """ Request a new certificate and downloads it, and optionally all certificate chains. First verifies whether all authorizations are valid; if not, aborts with an error. - ''' + """ if self.csr is None and self.csr_content is None: - raise ModuleFailException('No CSR has been provided') + raise ModuleFailException("No CSR has been provided") for identifier, authz in order.authorizations.items(): - if authz.status != 'valid': - authz.raise_error('Status is {status!r} and not "valid"'.format(status=authz.status), module=self.module) + if authz.status != "valid": + authz.raise_error( + 'Status is {status!r} and not "valid"'.format(status=authz.status), + module=self.module, + ) order.finalize(self.client, pem_to_der(self.csr, self.csr_content)) @@ -226,30 +274,40 @@ class ACMECertificateClient(object): for criterium_idx, matcher in enumerate(select_chain_matcher): for chain in chains: if matcher.match(chain): - self.module.debug('Found matching chain for criterium {0}'.format(criterium_idx)) + self.module.debug( + "Found matching chain for criterium {0}".format(criterium_idx) + ) return chain return None - def write_cert_chain(self, cert, cert_dest=None, fullchain_dest=None, chain_dest=None): + def write_cert_chain( + self, cert, cert_dest=None, fullchain_dest=None, chain_dest=None + ): changed = False - if cert_dest and write_file(self.module, cert_dest, cert.cert.encode('utf8')): + if cert_dest and write_file(self.module, cert_dest, cert.cert.encode("utf8")): changed = True - if fullchain_dest and write_file(self.module, fullchain_dest, (cert.cert + "\n".join(cert.chain)).encode('utf8')): + if fullchain_dest and write_file( + self.module, + fullchain_dest, + (cert.cert + "\n".join(cert.chain)).encode("utf8"), + ): changed = True - if chain_dest and write_file(self.module, chain_dest, ("\n".join(cert.chain)).encode('utf8')): + if chain_dest and write_file( + self.module, chain_dest, ("\n".join(cert.chain)).encode("utf8") + ): changed = True return changed def deactivate_authzs(self, order): - ''' + """ Deactivates all valid authz's. Does not raise exceptions. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://tools.ietf.org/html/rfc8555#section-7.5.2 - ''' + """ if len(order.authorization_uris) > len(order.authorizations): for authz_uri in order.authorization_uris: authz = None @@ -258,8 +316,12 @@ class ACMECertificateClient(object): except Exception: # ignore errors pass - if authz is None or authz.status != 'deactivated': - self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz_uri)) + if authz is None or authz.status != "deactivated": + self.module.warn( + warning="Could not deactivate authz object {0}.".format( + authz_uri + ) + ) else: for authz in order.authorizations.values(): try: @@ -267,5 +329,9 @@ class ACMECertificateClient(object): except Exception: # ignore errors pass - if authz.status != 'deactivated': - self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url)) + if authz.status != "deactivated": + self.module.warn( + warning="Could not deactivate authz object {0}.".format( + authz.url + ) + ) diff --git a/plugins/module_utils/acme/certificates.py b/plugins/module_utils/acme/certificates.py index ac204168..f8d8867d 100644 --- a/plugins/module_utils/acme/certificates.py +++ b/plugins/module_utils/acme/certificates.py @@ -28,10 +28,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import class CertificateChain(object): - ''' + """ Download and parse the certificate chain. https://tools.ietf.org/html/rfc8555#section-7.4.2 - ''' + """ def __init__(self, url): self.url = url @@ -41,86 +41,106 @@ class CertificateChain(object): @classmethod def download(cls, client, url): - content, info = client.get_request(url, parse_json_result=False, headers={'Accept': 'application/pem-certificate-chain'}) + content, info = client.get_request( + url, + parse_json_result=False, + headers={"Accept": "application/pem-certificate-chain"}, + ) - if not content or not info['content-type'].startswith('application/pem-certificate-chain'): + if not content or not info["content-type"].startswith( + "application/pem-certificate-chain" + ): raise ModuleFailException( "Cannot download certificate chain from {0}, as content type is not application/pem-certificate-chain: {1} (headers: {2})".format( - url, content, info)) + url, content, info + ) + ) result = cls(url) # Parse data - certs = split_pem_list(content.decode('utf-8'), keep_inbetween=True) + certs = split_pem_list(content.decode("utf-8"), keep_inbetween=True) if certs: result.cert = certs[0] result.chain = certs[1:] - process_links(info, lambda link, relation: result._process_links(client, link, relation)) + process_links( + info, lambda link, relation: result._process_links(client, link, relation) + ) if result.cert is None: - raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info)) + raise ModuleFailException( + "Failed to parse certificate chain download from {0}: {1} (headers: {2})".format( + url, content, info + ) + ) return result def _process_links(self, client, link, relation): - if relation == 'up': + if relation == "up": # Process link-up headers if there was no chain in reply if not self.chain: - chain_result, chain_info = client.get_request(link, parse_json_result=False) - if chain_info['status'] in [200, 201]: + chain_result, chain_info = client.get_request( + link, parse_json_result=False + ) + if chain_info["status"] in [200, 201]: self.chain.append(der_to_pem(chain_result)) - elif relation == 'alternate': + elif relation == "alternate": self.alternates.append(link) def to_json(self): - cert = self.cert.encode('utf8') - chain = ('\n'.join(self.chain)).encode('utf8') + cert = self.cert.encode("utf8") + chain = ("\n".join(self.chain)).encode("utf8") return { - 'cert': cert, - 'chain': chain, - 'full_chain': cert + chain, + "cert": cert, + "chain": chain, + "full_chain": cert + chain, } class Criterium(object): def __init__(self, criterium, index=None): self.index = index - self.test_certificates = criterium['test_certificates'] - self.subject = criterium['subject'] - self.issuer = criterium['issuer'] - self.subject_key_identifier = criterium['subject_key_identifier'] - self.authority_key_identifier = criterium['authority_key_identifier'] + self.test_certificates = criterium["test_certificates"] + self.subject = criterium["subject"] + self.issuer = criterium["issuer"] + self.subject_key_identifier = criterium["subject_key_identifier"] + self.authority_key_identifier = criterium["authority_key_identifier"] @six.add_metaclass(abc.ABCMeta) class ChainMatcher(object): @abc.abstractmethod def match(self, certificate): - ''' + """ Check whether a certificate chain (CertificateChain instance) matches. - ''' + """ def retrieve_acme_v1_certificate(client, csr_der): - ''' + """ Create a new certificate based on the CSR (ACME v1 protocol). Return the certificate object as dict https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5 - ''' + """ new_cert = { "resource": "new-cert", "csr": nopad_b64(csr_der), } result, info = client.send_signed_request( - client.directory['new-cert'], new_cert, error_msg='Failed to receive certificate', expected_status_codes=[200, 201]) - cert = CertificateChain(info['location']) + client.directory["new-cert"], + new_cert, + error_msg="Failed to receive certificate", + expected_status_codes=[200, 201], + ) + cert = CertificateChain(info["location"]) cert.cert = der_to_pem(result) def f(link, relation): - if relation == 'up': + if relation == "up": chain_result, chain_info = client.get_request(link, parse_json_result=False) - if chain_info['status'] in [200, 201]: + if chain_info["status"] in [200, 201]: del cert.chain[:] cert.chain.append(der_to_pem(chain_result)) diff --git a/plugins/module_utils/acme/challenges.py b/plugins/module_utils/acme/challenges.py index 0a1b3861..701ed1a2 100644 --- a/plugins/module_utils/acme/challenges.py +++ b/plugins/module_utils/acme/challenges.py @@ -35,17 +35,19 @@ except ImportError: def create_key_authorization(client, token): - ''' + """ Returns the key authorization for the given token https://tools.ietf.org/html/rfc8555#section-8.1 - ''' - accountkey_json = json.dumps(client.account_jwk, sort_keys=True, separators=(',', ':')) - thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) + """ + accountkey_json = json.dumps( + client.account_jwk, sort_keys=True, separators=(",", ":") + ) + thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode("utf8")).digest()) return "{0}.{1}".format(token, thumbprint) def combine_identifier(identifier_type, identifier): - return '{type}:{identifier}'.format(type=identifier_type, identifier=identifier) + return "{type}:{identifier}".format(type=identifier_type, identifier=identifier) def normalize_combined_identifier(identifier): @@ -56,10 +58,13 @@ def normalize_combined_identifier(identifier): def split_identifier(identifier): - parts = identifier.split(':', 1) + parts = identifier.split(":", 1) if len(parts) != 2: raise ModuleFailException( - 'Identifier "{identifier}" is not of the form :'.format(identifier=identifier)) + 'Identifier "{identifier}" is not of the form :'.format( + identifier=identifier + ) + ) return parts @@ -67,27 +72,27 @@ class Challenge(object): def __init__(self, data, url): self.data = data - self.type = data['type'] + self.type = data["type"] self.url = url - self.status = data['status'] - self.token = data.get('token') + self.status = data["status"] + self.token = data.get("token") @classmethod def from_json(cls, client, data, url=None): - return cls(data, url or (data['uri'] if client.version == 1 else data['url'])) + return cls(data, url or (data["uri"] if client.version == 1 else data["url"])) def call_validate(self, client): challenge_response = {} if client.version == 1: token = re.sub(r"[^A-Za-z0-9_\-]", "_", self.token) key_authorization = create_key_authorization(client, token) - challenge_response['resource'] = 'challenge' - challenge_response['keyAuthorization'] = key_authorization - challenge_response['type'] = self.type + challenge_response["resource"] = "challenge" + challenge_response["keyAuthorization"] = key_authorization + challenge_response["type"] = self.type client.send_signed_request( self.url, challenge_response, - error_msg='Failed to validate challenge', + error_msg="Failed to validate challenge", expected_status_codes=[200, 202], ) @@ -98,40 +103,44 @@ class Challenge(object): token = re.sub(r"[^A-Za-z0-9_\-]", "_", self.token) key_authorization = create_key_authorization(client, token) - if self.type == 'http-01': + if self.type == "http-01": # https://tools.ietf.org/html/rfc8555#section-8.3 return { - 'resource': '.well-known/acme-challenge/{token}'.format(token=token), - 'resource_value': key_authorization, + "resource": ".well-known/acme-challenge/{token}".format(token=token), + "resource_value": key_authorization, } - if self.type == 'dns-01': - if identifier_type != 'dns': + if self.type == "dns-01": + if identifier_type != "dns": return None # https://tools.ietf.org/html/rfc8555#section-8.4 - resource = '_acme-challenge' + resource = "_acme-challenge" value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest()) - record = '{0}.{1}'.format(resource, identifier[2:] if identifier.startswith('*.') else identifier) + record = "{0}.{1}".format( + resource, identifier[2:] if identifier.startswith("*.") else identifier + ) return { - 'resource': resource, - 'resource_value': value, - 'record': record, + "resource": resource, + "resource_value": value, + "record": record, } - if self.type == 'tls-alpn-01': + if self.type == "tls-alpn-01": # https://www.rfc-editor.org/rfc/rfc8737.html#section-3 - if identifier_type == 'ip': + if identifier_type == "ip": # IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596) resource = ipaddress.ip_address(identifier).reverse_pointer - if not resource.endswith('.'): - resource += '.' + if not resource.endswith("."): + resource += "." else: resource = identifier - value = base64.b64encode(hashlib.sha256(to_bytes(key_authorization)).digest()) + value = base64.b64encode( + hashlib.sha256(to_bytes(key_authorization)).digest() + ) return { - 'resource': resource, - 'resource_original': combine_identifier(identifier_type, identifier), - 'resource_value': value, + "resource": resource, + "resource_original": combine_identifier(identifier_type, identifier), + "resource_value": value, } # Unknown challenge type: ignore @@ -140,25 +149,28 @@ class Challenge(object): class Authorization(object): def _setup(self, client, data): - data['uri'] = self.url + data["uri"] = self.url self.data = data # While 'challenges' is a required field, apparently not every CA cares # (https://github.com/ansible-collections/community.crypto/issues/824) - if data.get('challenges'): - self.challenges = [Challenge.from_json(client, challenge) for challenge in data['challenges']] + if data.get("challenges"): + self.challenges = [ + Challenge.from_json(client, challenge) + for challenge in data["challenges"] + ] else: self.challenges = [] - if client.version == 1 and 'status' not in data: + if client.version == 1 and "status" not in data: # https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1.2 # "status (required, string): ... # If this field is missing, then the default value is "pending"." - self.status = 'pending' + self.status = "pending" else: - self.status = data['status'] - self.identifier = data['identifier']['value'] - self.identifier_type = data['identifier']['type'] - if data.get('wildcard', False): - self.identifier = '*.{0}'.format(self.identifier) + self.status = data["status"] + self.identifier = data["identifier"]["value"] + self.identifier_type = data["identifier"]["type"] + if data.get("wildcard", False): + self.identifier = "*.{0}".format(self.identifier) def __init__(self, url): self.url = url @@ -183,11 +195,11 @@ class Authorization(object): @classmethod def create(cls, client, identifier_type, identifier): - ''' + """ Create a new authorization for the given identifier. Return the authorization object of the new authorization https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4 - ''' + """ new_authz = { "identifier": { "type": identifier_type, @@ -195,16 +207,22 @@ class Authorization(object): }, } if client.version == 1: - url = client.directory['new-authz'] + url = client.directory["new-authz"] new_authz["resource"] = "new-authz" else: - if 'newAuthz' not in client.directory.directory: - raise ACMEProtocolException(client.module, 'ACME endpoint does not support pre-authorization') - url = client.directory['newAuthz'] + if "newAuthz" not in client.directory.directory: + raise ACMEProtocolException( + client.module, "ACME endpoint does not support pre-authorization" + ) + url = client.directory["newAuthz"] result, info = client.send_signed_request( - url, new_authz, error_msg='Failed to request challenges', expected_status_codes=[200, 201]) - return cls.from_json(client, result, info['location']) + url, + new_authz, + error_msg="Failed to request challenges", + expected_status_codes=[200, 201], + ) + return cls.from_json(client, result, info["location"]) @property def combined_identifier(self): @@ -220,39 +238,44 @@ class Authorization(object): return changed def get_challenge_data(self, client): - ''' + """ Returns a dict with the data for all proposed (and supported) challenges of the given authorization. - ''' + """ data = {} for challenge in self.challenges: - validation_data = challenge.get_validation_data(client, self.identifier_type, self.identifier) + validation_data = challenge.get_validation_data( + client, self.identifier_type, self.identifier + ) if validation_data is not None: data[challenge.type] = validation_data return data def raise_error(self, error_msg, module=None): - ''' + """ Aborts with a specific error for a challenge. - ''' + """ error_details = [] # multiple challenges could have failed at this point, gather error # details for all of them before failing for challenge in self.challenges: - if challenge.status == 'invalid': - msg = 'Challenge {type}'.format(type=challenge.type) - if 'error' in challenge.data: - msg = '{msg}: {problem}'.format( + if challenge.status == "invalid": + msg = "Challenge {type}".format(type=challenge.type) + if "error" in challenge.data: + msg = "{msg}: {problem}".format( msg=msg, - problem=format_error_problem(challenge.data['error'], subproblem_prefix='{0}.'.format(challenge.type)), + problem=format_error_problem( + challenge.data["error"], + subproblem_prefix="{0}.".format(challenge.type), + ), ) error_details.append(msg) raise ACMEProtocolException( module, - 'Failed to validate challenge for {identifier}: {error}. {details}'.format( + "Failed to validate challenge for {identifier}: {error}. {details}".format( identifier=self.combined_identifier, error=error_msg, - details='; '.join(error_details), + details="; ".join(error_details), ), extras=dict( identifier=self.combined_identifier, @@ -269,88 +292,90 @@ class Authorization(object): def wait_for_validation(self, client, callenge_type): while True: self.refresh(client) - if self.status in ['valid', 'invalid', 'revoked']: + if self.status in ["valid", "invalid", "revoked"]: break time.sleep(2) - if self.status == 'invalid': + if self.status == "invalid": self.raise_error('Status is "invalid"', module=client.module) - return self.status == 'valid' + return self.status == "valid" def call_validate(self, client, challenge_type, wait=True): - ''' + """ Validate the authorization provided in the auth dict. Returns True when the validation was successful and False when it was not. - ''' + """ challenge = self.find_challenge(challenge_type) if challenge is None: - raise ModuleFailException('Found no challenge of type "{challenge}" for identifier {identifier}!'.format( - challenge=challenge_type, - identifier=self.combined_identifier, - )) + raise ModuleFailException( + 'Found no challenge of type "{challenge}" for identifier {identifier}!'.format( + challenge=challenge_type, + identifier=self.combined_identifier, + ) + ) challenge.call_validate(client) if not wait: - return self.status == 'valid' + return self.status == "valid" return self.wait_for_validation(client, challenge_type) def can_deactivate(self): - ''' + """ Deactivates this authorization. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://tools.ietf.org/html/rfc8555#section-7.5.2 - ''' - return self.status in ('valid', 'pending') + """ + return self.status in ("valid", "pending") def deactivate(self, client): - ''' + """ Deactivates this authorization. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://tools.ietf.org/html/rfc8555#section-7.5.2 - ''' + """ if not self.can_deactivate(): return - authz_deactivate = { - 'status': 'deactivated' - } + authz_deactivate = {"status": "deactivated"} if client.version == 1: - authz_deactivate['resource'] = 'authz' - result, info = client.send_signed_request(self.url, authz_deactivate, fail_on_error=False) - if 200 <= info['status'] < 300 and result.get('status') == 'deactivated': - self.status = 'deactivated' + authz_deactivate["resource"] = "authz" + result, info = client.send_signed_request( + self.url, authz_deactivate, fail_on_error=False + ) + if 200 <= info["status"] < 300 and result.get("status") == "deactivated": + self.status = "deactivated" return True return False @classmethod def deactivate_url(cls, client, url): - ''' + """ Deactivates this authorization. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://tools.ietf.org/html/rfc8555#section-7.5.2 - ''' + """ authz = cls(url) - authz_deactivate = { - 'status': 'deactivated' - } + authz_deactivate = {"status": "deactivated"} if client.version == 1: - authz_deactivate['resource'] = 'authz' - result, info = client.send_signed_request(url, authz_deactivate, fail_on_error=True) + authz_deactivate["resource"] = "authz" + result, info = client.send_signed_request( + url, authz_deactivate, fail_on_error=True + ) authz._setup(client, result) return authz def wait_for_validation(authzs, client): - ''' + """ Wait until a list of authz is valid. Fail if at least one of them is invalid or revoked. - ''' + """ while authzs: authzs_next = [] for authz in authzs: authz.refresh(client) - if authz.status in ['valid', 'invalid', 'revoked']: - if authz.status != 'valid': + if authz.status in ["valid", "invalid", "revoked"]: + if authz.status != "valid": authz.raise_error('Status is not "valid"', module=client.module) else: authzs_next.append(authz) diff --git a/plugins/module_utils/acme/errors.py b/plugins/module_utils/acme/errors.py index d8e000da..12fe5c4e 100644 --- a/plugins/module_utils/acme/errors.py +++ b/plugins/module_utils/acme/errors.py @@ -19,37 +19,42 @@ def format_http_status(status_code): expl = http_responses.get(status_code) if not expl: return str(status_code) - return '%d %s' % (status_code, expl) + return "%d %s" % (status_code, expl) -def format_error_problem(problem, subproblem_prefix=''): - error_type = problem.get('type', 'about:blank') # https://www.rfc-editor.org/rfc/rfc7807#section-3.1 - if 'title' in problem: +def format_error_problem(problem, subproblem_prefix=""): + error_type = problem.get( + "type", "about:blank" + ) # https://www.rfc-editor.org/rfc/rfc7807#section-3.1 + if "title" in problem: msg = 'Error "{title}" ({type})'.format( type=error_type, - title=problem['title'], + title=problem["title"], ) else: - msg = 'Error {type}'.format(type=error_type) - if 'detail' in problem: - msg += ': "{detail}"'.format(detail=problem['detail']) - subproblems = problem.get('subproblems') + msg = "Error {type}".format(type=error_type) + if "detail" in problem: + msg += ': "{detail}"'.format(detail=problem["detail"]) + subproblems = problem.get("subproblems") if subproblems is not None: - msg = '{msg} Subproblems:'.format(msg=msg) + msg = "{msg} Subproblems:".format(msg=msg) for index, problem in enumerate(subproblems): - index_str = '{prefix}{index}'.format(prefix=subproblem_prefix, index=index) - msg = '{msg}\n({index}) {problem}'.format( + index_str = "{prefix}{index}".format(prefix=subproblem_prefix, index=index) + msg = "{msg}\n({index}) {problem}".format( msg=msg, index=index_str, - problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index_str)), + problem=format_error_problem( + problem, subproblem_prefix="{0}.".format(index_str) + ), ) return msg class ModuleFailException(Exception): - ''' + """ If raised, module.fail_json() will be called with the given parameters after cleanup. - ''' + """ + def __init__(self, msg, **args): super(ModuleFailException, self).__init__(self, msg) self.msg = msg @@ -60,7 +65,16 @@ class ModuleFailException(Exception): class ACMEProtocolException(ModuleFailException): - def __init__(self, module, msg=None, info=None, response=None, content=None, content_json=None, extras=None): + def __init__( + self, + module, + msg=None, + info=None, + response=None, + content=None, + content_json=None, + extras=None, + ): # Try to get hold of content, if response is given and content is not provided if content is None and content_json is None and response is not None: try: @@ -70,7 +84,7 @@ class ACMEProtocolException(ModuleFailException): raise TypeError content = response.read() except (AttributeError, TypeError): - content = info.pop('body', None) + content = info.pop("body", None) # Make sure that content_json is None or a dictionary if content_json is not None and not isinstance(content_json, dict): @@ -90,53 +104,71 @@ class ACMEProtocolException(ModuleFailException): error_type = None if msg is None: - msg = 'ACME request failed' - add_msg = '' + msg = "ACME request failed" + add_msg = "" if info is not None: - url = info['url'] - code = info['status'] - extras['http_url'] = url - extras['http_status'] = code + url = info["url"] + code = info["status"] + extras["http_url"] = url + extras["http_status"] = code error_code = code - if code is not None and code >= 400 and content_json is not None and 'type' in content_json: - error_type = content_json['type'] - if 'status' in content_json and content_json['status'] != code: - code_msg = 'status {problem_code} (HTTP status: {http_code})'.format( - http_code=format_http_status(code), problem_code=content_json['status']) + if ( + code is not None + and code >= 400 + and content_json is not None + and "type" in content_json + ): + error_type = content_json["type"] + if "status" in content_json and content_json["status"] != code: + code_msg = ( + "status {problem_code} (HTTP status: {http_code})".format( + http_code=format_http_status(code), + problem_code=content_json["status"], + ) + ) else: - code_msg = 'status {problem_code}'.format(problem_code=format_http_status(code)) - if code == -1 and info.get('msg'): - code_msg = 'error: {msg}'.format(msg=info['msg']) - subproblems = content_json.pop('subproblems', None) - add_msg = ' {problem}.'.format(problem=format_error_problem(content_json)) - extras['problem'] = content_json - extras['subproblems'] = subproblems or [] + code_msg = "status {problem_code}".format( + problem_code=format_http_status(code) + ) + if code == -1 and info.get("msg"): + code_msg = "error: {msg}".format(msg=info["msg"]) + subproblems = content_json.pop("subproblems", None) + add_msg = " {problem}.".format( + problem=format_error_problem(content_json) + ) + extras["problem"] = content_json + extras["subproblems"] = subproblems or [] if subproblems is not None: - add_msg = '{add_msg} Subproblems:'.format(add_msg=add_msg) + add_msg = "{add_msg} Subproblems:".format(add_msg=add_msg) for index, problem in enumerate(subproblems): - add_msg = '{add_msg}\n({index}) {problem}.'.format( + add_msg = "{add_msg}\n({index}) {problem}.".format( add_msg=add_msg, index=index, - problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)), + problem=format_error_problem( + problem, subproblem_prefix="{0}.".format(index) + ), ) else: - code_msg = 'HTTP status {code}'.format(code=format_http_status(code)) - if code == -1 and info.get('msg'): - code_msg = 'error: {msg}'.format(msg=info['msg']) + code_msg = "HTTP status {code}".format(code=format_http_status(code)) + if code == -1 and info.get("msg"): + code_msg = "error: {msg}".format(msg=info["msg"]) if content_json is not None: - add_msg = ' The JSON error result: {content}'.format(content=content_json) + add_msg = " The JSON error result: {content}".format( + content=content_json + ) elif content is not None: - add_msg = ' The raw error result: {content}'.format(content=to_text(content)) - msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code_msg) + add_msg = " The raw error result: {content}".format( + content=to_text(content) + ) + msg = "{msg} for {url} with {code}".format(msg=msg, url=url, code=code_msg) elif content_json is not None: - add_msg = ' The JSON result: {content}'.format(content=content_json) + add_msg = " The JSON result: {content}".format(content=content_json) elif content is not None: - add_msg = ' The raw result: {content}'.format(content=to_text(content)) + add_msg = " The raw result: {content}".format(content=to_text(content)) super(ACMEProtocolException, self).__init__( - '{msg}.{add_msg}'.format(msg=msg, add_msg=add_msg), - **extras + "{msg}.{add_msg}".format(msg=msg, add_msg=add_msg), **extras ) self.problem = {} self.subproblems = [] diff --git a/plugins/module_utils/acme/io.py b/plugins/module_utils/acme/io.py index b0f8b96f..4acd440a 100644 --- a/plugins/module_utils/acme/io.py +++ b/plugins/module_utils/acme/io.py @@ -23,9 +23,9 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor ) -def read_file(fn, mode='b'): +def read_file(fn, mode="b"): try: - with open(fn, 'r' + mode) as f: + with open(fn, "r" + mode) as f: return f.read() except Exception as e: raise ModuleFailException('Error while reading file "{0}": {1}'.format(fn, e)) @@ -33,14 +33,14 @@ def read_file(fn, mode='b'): # This function was adapted from an earlier version of https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/uri.py def write_file(module, dest, content): - ''' + """ Write content to destination file dest, only if the content has changed. - ''' + """ changed = False # create a tempfile fd, tmpsrc = tempfile.mkstemp(text=False) - f = os.fdopen(fd, 'wb') + f = os.fdopen(fd, "wb") try: f.write(content) except Exception as err: @@ -49,7 +49,10 @@ def write_file(module, dest, content): except Exception: pass os.remove(tmpsrc) - raise ModuleFailException("failed to create temporary content file: %s" % to_native(err), exception=traceback.format_exc()) + raise ModuleFailException( + "failed to create temporary content file: %s" % to_native(err), + exception=traceback.format_exc(), + ) f.close() checksum_src = None checksum_dest = None @@ -75,7 +78,7 @@ def write_file(module, dest, content): raise ModuleFailException("Destination %s not readable" % (dest)) checksum_dest = module.sha1(dest) else: - dirname = os.path.dirname(dest) or '.' + dirname = os.path.dirname(dest) or "." if not os.access(dirname, os.W_OK): os.remove(tmpsrc) raise ModuleFailException("Destination dir %s not writable" % (dirname)) @@ -85,6 +88,9 @@ def write_file(module, dest, content): changed = True except Exception as err: os.remove(tmpsrc) - raise ModuleFailException("failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(err)), exception=traceback.format_exc()) + raise ModuleFailException( + "failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(err)), + exception=traceback.format_exc(), + ) os.remove(tmpsrc) return changed diff --git a/plugins/module_utils/acme/orders.py b/plugins/module_utils/acme/orders.py index a378e696..ddcc7d11 100644 --- a/plugins/module_utils/acme/orders.py +++ b/plugins/module_utils/acme/orders.py @@ -29,14 +29,14 @@ class Order(object): def _setup(self, client, data): self.data = data - self.status = data['status'] + self.status = data["status"] self.identifiers = [] - for identifier in data['identifiers']: - self.identifiers.append((identifier['type'], identifier['value'])) - self.replaces_cert_id = data.get('replaces') - self.finalize_uri = data.get('finalize') - self.certificate_uri = data.get('certificate') - self.authorization_uris = data['authorizations'] + for identifier in data["identifiers"]: + self.identifiers.append((identifier["type"], identifier["value"])) + self.replaces_cert_id = data.get("replaces") + self.finalize_uri = data.get("finalize") + self.certificate_uri = data.get("certificate") + self.authorization_uris = data["authorizations"] self.authorizations = {} def __init__(self, url): @@ -66,33 +66,37 @@ class Order(object): @classmethod def create(cls, client, identifiers, replaces_cert_id=None, profile=None): - ''' + """ Start a new certificate order (ACME v2 protocol). https://tools.ietf.org/html/rfc8555#section-7.4 - ''' + """ acme_identifiers = [] for identifier_type, identifier in identifiers: - acme_identifiers.append({ - 'type': identifier_type, - 'value': identifier, - }) - new_order = { - "identifiers": acme_identifiers - } + acme_identifiers.append( + { + "type": identifier_type, + "value": identifier, + } + ) + new_order = {"identifiers": acme_identifiers} if replaces_cert_id is not None: new_order["replaces"] = replaces_cert_id if profile is not None: new_order["profile"] = profile result, info = client.send_signed_request( - client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201]) - return cls.from_json(client, result, info['location']) + client.directory["newOrder"], + new_order, + error_msg="Failed to start new order", + expected_status_codes=[201], + ) + return cls.from_json(client, result, info["location"]) @classmethod def create_with_error_handling( cls, client, identifiers, - error_strategy='auto', + error_strategy="auto", error_max_retries=3, replaces_cert_id=None, profile=None, @@ -113,20 +117,29 @@ class Order(object): while True: tries += 1 try: - return cls.create(client, identifiers, replaces_cert_id=replaces_cert_id, profile=profile) + return cls.create( + client, + identifiers, + replaces_cert_id=replaces_cert_id, + profile=profile, + ) except ACMEProtocolException as exc: - if tries <= error_max_retries + 1 and error_strategy != 'fail': - if error_strategy == 'always': + if tries <= error_max_retries + 1 and error_strategy != "fail": + if error_strategy == "always": continue if ( - error_strategy in ('auto', 'retry_without_replaces_cert_id') and - replaces_cert_id is not None and - not (exc.error_code == 409 and exc.error_type == 'urn:ietf:params:acme:error:alreadyReplaced') + error_strategy in ("auto", "retry_without_replaces_cert_id") + and replaces_cert_id is not None + and not ( + exc.error_code == 409 + and exc.error_type + == "urn:ietf:params:acme:error:alreadyReplaced" + ) ): if message_callback: message_callback( - 'Stop passing `replaces={replaces}` due to error {code} {type} when creating ACME order'.format( + "Stop passing `replaces={replaces}` due to error {code} {type} when creating ACME order".format( code=exc.error_code, type=exc.error_type, replaces=replaces_cert_id, @@ -146,32 +159,41 @@ class Order(object): def load_authorizations(self, client): for auth_uri in self.authorization_uris: authz = Authorization.from_url(client, auth_uri) - self.authorizations[normalize_combined_identifier(authz.combined_identifier)] = authz + self.authorizations[ + normalize_combined_identifier(authz.combined_identifier) + ] = authz def wait_for_finalization(self, client): while True: self.refresh(client) - if self.status in ['valid', 'invalid', 'pending', 'ready']: + if self.status in ["valid", "invalid", "pending", "ready"]: break time.sleep(2) - if self.status != 'valid': + if self.status != "valid": raise ACMEProtocolException( client.module, - 'Failed to wait for order to complete; got status "{status}"'.format(status=self.status), - content_json=self.data) + 'Failed to wait for order to complete; got status "{status}"'.format( + status=self.status + ), + content_json=self.data, + ) def finalize(self, client, csr_der, wait=True): - ''' + """ Create a new certificate based on the csr. Return the certificate object as dict https://tools.ietf.org/html/rfc8555#section-7.4 - ''' + """ new_cert = { "csr": nopad_b64(csr_der), } result, info = client.send_signed_request( - self.finalize_uri, new_cert, error_msg='Failed to finalizing order', expected_status_codes=[200]) + self.finalize_uri, + new_cert, + error_msg="Failed to finalizing order", + expected_status_codes=[200], + ) # It is not clear from the RFC whether the finalize call returns the order object or not. # Instead of using the result, we call self.refresh(client) below. @@ -179,9 +201,12 @@ class Order(object): self.wait_for_finalization(client) else: self.refresh(client) - if self.status not in ['procesing', 'valid', 'invalid']: + if self.status not in ["procesing", "valid", "invalid"]: raise ACMEProtocolException( client.module, - 'Failed to finalize order; got status "{status}"'.format(status=self.status), + 'Failed to finalize order; got status "{status}"'.format( + status=self.status + ), info=info, - content_json=result) + content_json=result, + ) diff --git a/plugins/module_utils/acme/utils.py b/plugins/module_utils/acme/utils.py index 5639f716..2aed4f0c 100644 --- a/plugins/module_utils/acme/utils.py +++ b/plugins/module_utils/acme/utils.py @@ -31,23 +31,24 @@ from ansible_collections.community.crypto.plugins.module_utils.time import ( def nopad_b64(data): - return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "") + return base64.urlsafe_b64encode(data).decode("utf8").replace("=", "") def der_to_pem(der_cert): - ''' + """ Convert the DER format certificate in der_cert to a PEM format certificate and return it. - ''' + """ return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( - "\n".join(textwrap.wrap(base64.b64encode(der_cert).decode('utf8'), 64))) + "\n".join(textwrap.wrap(base64.b64encode(der_cert).decode("utf8"), 64)) + ) def pem_to_der(pem_filename=None, pem_content=None): - ''' + """ Load PEM file, or use PEM file's content, and convert to DER. If PEM contains multiple entities, the first entity will be used. - ''' + """ certificate_lines = [] if pem_content is not None: lines = pem_content.splitlines() @@ -56,12 +57,17 @@ def pem_to_der(pem_filename=None, pem_content=None): with open(pem_filename, "rt") as f: lines = list(f) except Exception as err: - raise ModuleFailException("cannot load PEM file {0}: {1}".format(pem_filename, to_native(err)), exception=traceback.format_exc()) + raise ModuleFailException( + "cannot load PEM file {0}: {1}".format(pem_filename, to_native(err)), + exception=traceback.format_exc(), + ) else: - raise ModuleFailException('One of pem_filename and pem_content must be provided') + raise ModuleFailException( + "One of pem_filename and pem_content must be provided" + ) header_line_count = 0 for line in lines: - if line.startswith('-----'): + if line.startswith("-----"): header_line_count += 1 if header_line_count == 2: # If certificate file contains other certs appended @@ -69,27 +75,27 @@ def pem_to_der(pem_filename=None, pem_content=None): break continue certificate_lines.append(line.strip()) - return base64.b64decode(''.join(certificate_lines)) + return base64.b64decode("".join(certificate_lines)) def process_links(info, callback): - ''' + """ Process link header, calls callback for every link header with the URL and relation as options. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link - ''' - if 'link' in info: - link = info['link'] + """ + if "link" in info: + link = info["link"] for url, relation in re.findall(r'<([^>]+)>;\s*rel="(\w+)"', link): callback(unquote(url), relation) def parse_retry_after(value, relative_with_timezone=True, now=None): - ''' + """ Parse the value of a Retry-After header and return a timestamp. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - ''' + """ # First try a number of seconds try: delta = datetime.timedelta(seconds=int(value)) @@ -100,11 +106,11 @@ def parse_retry_after(value, relative_with_timezone=True, now=None): pass try: - return datetime.datetime.strptime(value, '%a, %d %b %Y %H:%M:%S GMT') + return datetime.datetime.strptime(value, "%a, %d %b %Y %H:%M:%S GMT") except ValueError: pass - raise ValueError('Cannot parse Retry-After header value %s' % repr(value)) + raise ValueError("Cannot parse Retry-After header value %s" % repr(value)) def compute_cert_id( @@ -116,20 +122,26 @@ def compute_cert_id( ): # Obtain certificate info if not provided if cert_info is None: - cert_info = backend.get_cert_information(cert_filename=cert_filename, cert_content=cert_content) + cert_info = backend.get_cert_information( + cert_filename=cert_filename, cert_content=cert_content + ) # Convert Authority Key Identifier to string if cert_info.authority_key_identifier is None: if none_if_required_information_is_missing: return None - raise ModuleFailException('Certificate has no Authority Key Identifier extension') - aki = to_native(base64.urlsafe_b64encode(cert_info.authority_key_identifier)).replace('=', '') + raise ModuleFailException( + "Certificate has no Authority Key Identifier extension" + ) + aki = to_native( + base64.urlsafe_b64encode(cert_info.authority_key_identifier) + ).replace("=", "") # Convert serial number to string serial_bytes = convert_int_to_bytes(cert_info.serial_number) if ord(serial_bytes[:1]) >= 128: - serial_bytes = b'\x00' + serial_bytes - serial = to_native(base64.urlsafe_b64encode(serial_bytes)).replace('=', '') + serial_bytes = b"\x00" + serial_bytes + serial = to_native(base64.urlsafe_b64encode(serial_bytes)).replace("=", "") # Compose cert ID - return '{aki}.{serial}'.format(aki=aki, serial=serial) + return "{aki}.{serial}".format(aki=aki, serial=serial) diff --git a/plugins/module_utils/argspec.py b/plugins/module_utils/argspec.py index 077065a2..59450b42 100644 --- a/plugins/module_utils/argspec.py +++ b/plugins/module_utils/argspec.py @@ -20,7 +20,15 @@ def _ensure_list(value): class ArgumentSpec: - def __init__(self, argument_spec=None, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): + def __init__( + self, + argument_spec=None, + mutually_exclusive=None, + required_together=None, + required_one_of=None, + required_if=None, + required_by=None, + ): self.argument_spec = argument_spec or {} self.mutually_exclusive = _ensure_list(mutually_exclusive) self.required_together = _ensure_list(required_together) @@ -32,7 +40,14 @@ class ArgumentSpec: self.argument_spec.update(kwargs) return self - def update(self, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): + def update( + self, + mutually_exclusive=None, + required_together=None, + required_one_of=None, + required_if=None, + required_by=None, + ): if mutually_exclusive: self.mutually_exclusive.extend(mutually_exclusive) if required_together: @@ -68,10 +83,11 @@ class ArgumentSpec: required_one_of=self.required_one_of, required_if=self.required_if, required_by=self.required_by, - **kwargs) + **kwargs + ) def create_ansible_module(self, **kwargs): return self.create_ansible_module_helper(AnsibleModule, (), **kwargs) -__all__ = ('ArgumentSpec', ) +__all__ = ("ArgumentSpec",) diff --git a/plugins/module_utils/crypto/_asn1.py b/plugins/module_utils/crypto/_asn1.py index 58cd915b..8cd5e542 100644 --- a/plugins/module_utils/crypto/_asn1.py +++ b/plugins/module_utils/crypto/_asn1.py @@ -31,8 +31,10 @@ type: value: The value to encode, the format of this value depends on the specified. """ -ASN1_STRING_REGEX = re.compile(r'^((?PIMPLICIT|EXPLICIT):(?P\d+)(?PU|A|P|C)?,)?' - r'(?P[\w\d]+):(?P.*)') +ASN1_STRING_REGEX = re.compile( + r"^((?PIMPLICIT|EXPLICIT):(?P\d+)(?PU|A|P|C)?,)?" + r"(?P[\w\d]+):(?P.*)" +) class TagClass: @@ -48,7 +50,7 @@ class TagNumber: def _pack_octet_integer(value): - """ Packs an integer value into 1 or multiple octets. """ + """Packs an integer value into 1 or multiple octets.""" # NOTE: This is *NOT* the same as packing an ASN.1 INTEGER like value. octets = bytearray() @@ -70,37 +72,41 @@ def _pack_octet_integer(value): def serialize_asn1_string_as_der(value): - """ Deserializes an ASN.1 string to a DER encoded byte string. """ + """Deserializes an ASN.1 string to a DER encoded byte string.""" asn1_match = ASN1_STRING_REGEX.match(value) if not asn1_match: - raise ValueError("The ASN.1 serialized string must be in the format [modifier,]type[:value]") + raise ValueError( + "The ASN.1 serialized string must be in the format [modifier,]type[:value]" + ) - tag_type = asn1_match.group('tag_type') - tag_number = asn1_match.group('tag_number') - tag_class = asn1_match.group('tag_class') or 'C' - value_type = asn1_match.group('value_type') - asn1_value = asn1_match.group('value') + tag_type = asn1_match.group("tag_type") + tag_number = asn1_match.group("tag_number") + tag_class = asn1_match.group("tag_class") or "C" + value_type = asn1_match.group("value_type") + asn1_value = asn1_match.group("value") - if value_type != 'UTF8': - raise ValueError('The ASN.1 serialized string is not a known type "{0}", only UTF8 types are ' - 'supported'.format(value_type)) + if value_type != "UTF8": + raise ValueError( + 'The ASN.1 serialized string is not a known type "{0}", only UTF8 types are ' + "supported".format(value_type) + ) - b_value = to_bytes(asn1_value, encoding='utf-8', errors='surrogate_or_strict') + b_value = to_bytes(asn1_value, encoding="utf-8", errors="surrogate_or_strict") # We should only do a universal type tag if not IMPLICITLY tagged or the tag class is not universal. - if not tag_type or (tag_type == 'EXPLICIT' and tag_class != 'U'): + if not tag_type or (tag_type == "EXPLICIT" and tag_class != "U"): b_value = pack_asn1(TagClass.universal, False, TagNumber.utf8_string, b_value) if tag_type: tag_class = { - 'U': TagClass.universal, - 'A': TagClass.application, - 'P': TagClass.private, - 'C': TagClass.context_specific, + "U": TagClass.universal, + "A": TagClass.application, + "P": TagClass.private, + "C": TagClass.context_specific, }[tag_class] # When adding support for more types this should be looked into further. For now it works with UTF8Strings. - constructed = tag_type == 'EXPLICIT' and tag_class != TagClass.universal + constructed = tag_type == "EXPLICIT" and tag_class != TagClass.universal b_value = pack_asn1(tag_class, constructed, int(tag_number), b_value) return b_value @@ -121,7 +127,7 @@ def pack_asn1(tag_class, constructed, tag_number, b_data): # Bit 8 and 7 denotes the class. identifier_octets = tag_class << 6 # Bit 6 denotes whether the value is primitive or constructed. - identifier_octets |= ((1 if constructed else 0) << 5) + identifier_octets |= (1 if constructed else 0) << 5 # Bits 5-1 contain the tag number, if it cannot be encoded in these 5 bits # then they are set and another octet(s) is used to denote the tag number. diff --git a/plugins/module_utils/crypto/_obj2txt.py b/plugins/module_utils/crypto/_obj2txt.py index 8fc2c805..30c9348c 100644 --- a/plugins/module_utils/crypto/_obj2txt.py +++ b/plugins/module_utils/crypto/_obj2txt.py @@ -36,6 +36,7 @@ __metaclass__ = type # It must **ONLY** be used in compatibility code for older # cryptography versions! + def obj2txt(openssl_lib, openssl_ffi, obj): # Set to 80 on the recommendation of # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values diff --git a/plugins/module_utils/crypto/_objects.py b/plugins/module_utils/crypto/_objects.py index 510ce569..32cf23a4 100644 --- a/plugins/module_utils/crypto/_objects.py +++ b/plugins/module_utils/crypto/_objects.py @@ -21,17 +21,19 @@ for dotted, names in OID_MAP.items(): for name in names: if name in NORMALIZE_NAMES and OID_LOOKUP[name] != dotted: raise AssertionError( - 'Name collision during setup: "{0}" for OIDs {1} and {2}' - .format(name, dotted, OID_LOOKUP[name]) + 'Name collision during setup: "{0}" for OIDs {1} and {2}'.format( + name, dotted, OID_LOOKUP[name] + ) ) NORMALIZE_NAMES[name] = names[0] NORMALIZE_NAMES_SHORT[name] = names[-1] OID_LOOKUP[name] = dotted -for alias, original in [('userID', 'userId')]: +for alias, original in [("userID", "userId")]: if alias in NORMALIZE_NAMES: raise AssertionError( - 'Name collision during adding aliases: "{0}" (alias for "{1}") is already mapped to OID {2}' - .format(alias, original, OID_LOOKUP[alias]) + 'Name collision during adding aliases: "{0}" (alias for "{1}") is already mapped to OID {2}'.format( + alias, original, OID_LOOKUP[alias] + ) ) NORMALIZE_NAMES[alias] = original NORMALIZE_NAMES_SHORT[alias] = NORMALIZE_NAMES_SHORT[original] diff --git a/plugins/module_utils/crypto/_objects_data.py b/plugins/module_utils/crypto/_objects_data.py index 21c08472..4c199715 100644 --- a/plugins/module_utils/crypto/_objects_data.py +++ b/plugins/module_utils/crypto/_objects_data.py @@ -21,1097 +21,1157 @@ __metaclass__ = type OID_MAP = { - '0': ('itu-t', 'ITU-T', 'ccitt'), - '0.3.4401.5': ('ntt-ds', ), - '0.3.4401.5.3.1.9': ('camellia', ), - '0.3.4401.5.3.1.9.1': ('camellia-128-ecb', 'CAMELLIA-128-ECB'), - '0.3.4401.5.3.1.9.3': ('camellia-128-ofb', 'CAMELLIA-128-OFB'), - '0.3.4401.5.3.1.9.4': ('camellia-128-cfb', 'CAMELLIA-128-CFB'), - '0.3.4401.5.3.1.9.6': ('camellia-128-gcm', 'CAMELLIA-128-GCM'), - '0.3.4401.5.3.1.9.7': ('camellia-128-ccm', 'CAMELLIA-128-CCM'), - '0.3.4401.5.3.1.9.9': ('camellia-128-ctr', 'CAMELLIA-128-CTR'), - '0.3.4401.5.3.1.9.10': ('camellia-128-cmac', 'CAMELLIA-128-CMAC'), - '0.3.4401.5.3.1.9.21': ('camellia-192-ecb', 'CAMELLIA-192-ECB'), - '0.3.4401.5.3.1.9.23': ('camellia-192-ofb', 'CAMELLIA-192-OFB'), - '0.3.4401.5.3.1.9.24': ('camellia-192-cfb', 'CAMELLIA-192-CFB'), - '0.3.4401.5.3.1.9.26': ('camellia-192-gcm', 'CAMELLIA-192-GCM'), - '0.3.4401.5.3.1.9.27': ('camellia-192-ccm', 'CAMELLIA-192-CCM'), - '0.3.4401.5.3.1.9.29': ('camellia-192-ctr', 'CAMELLIA-192-CTR'), - '0.3.4401.5.3.1.9.30': ('camellia-192-cmac', 'CAMELLIA-192-CMAC'), - '0.3.4401.5.3.1.9.41': ('camellia-256-ecb', 'CAMELLIA-256-ECB'), - '0.3.4401.5.3.1.9.43': ('camellia-256-ofb', 'CAMELLIA-256-OFB'), - '0.3.4401.5.3.1.9.44': ('camellia-256-cfb', 'CAMELLIA-256-CFB'), - '0.3.4401.5.3.1.9.46': ('camellia-256-gcm', 'CAMELLIA-256-GCM'), - '0.3.4401.5.3.1.9.47': ('camellia-256-ccm', 'CAMELLIA-256-CCM'), - '0.3.4401.5.3.1.9.49': ('camellia-256-ctr', 'CAMELLIA-256-CTR'), - '0.3.4401.5.3.1.9.50': ('camellia-256-cmac', 'CAMELLIA-256-CMAC'), - '0.9': ('data', ), - '0.9.2342': ('pss', ), - '0.9.2342.19200300': ('ucl', ), - '0.9.2342.19200300.100': ('pilot', ), - '0.9.2342.19200300.100.1': ('pilotAttributeType', ), - '0.9.2342.19200300.100.1.1': ('userId', 'UID'), - '0.9.2342.19200300.100.1.2': ('textEncodedORAddress', ), - '0.9.2342.19200300.100.1.3': ('rfc822Mailbox', 'mail'), - '0.9.2342.19200300.100.1.4': ('info', ), - '0.9.2342.19200300.100.1.5': ('favouriteDrink', ), - '0.9.2342.19200300.100.1.6': ('roomNumber', ), - '0.9.2342.19200300.100.1.7': ('photo', ), - '0.9.2342.19200300.100.1.8': ('userClass', ), - '0.9.2342.19200300.100.1.9': ('host', ), - '0.9.2342.19200300.100.1.10': ('manager', ), - '0.9.2342.19200300.100.1.11': ('documentIdentifier', ), - '0.9.2342.19200300.100.1.12': ('documentTitle', ), - '0.9.2342.19200300.100.1.13': ('documentVersion', ), - '0.9.2342.19200300.100.1.14': ('documentAuthor', ), - '0.9.2342.19200300.100.1.15': ('documentLocation', ), - '0.9.2342.19200300.100.1.20': ('homeTelephoneNumber', ), - '0.9.2342.19200300.100.1.21': ('secretary', ), - '0.9.2342.19200300.100.1.22': ('otherMailbox', ), - '0.9.2342.19200300.100.1.23': ('lastModifiedTime', ), - '0.9.2342.19200300.100.1.24': ('lastModifiedBy', ), - '0.9.2342.19200300.100.1.25': ('domainComponent', 'DC'), - '0.9.2342.19200300.100.1.26': ('aRecord', ), - '0.9.2342.19200300.100.1.27': ('pilotAttributeType27', ), - '0.9.2342.19200300.100.1.28': ('mXRecord', ), - '0.9.2342.19200300.100.1.29': ('nSRecord', ), - '0.9.2342.19200300.100.1.30': ('sOARecord', ), - '0.9.2342.19200300.100.1.31': ('cNAMERecord', ), - '0.9.2342.19200300.100.1.37': ('associatedDomain', ), - '0.9.2342.19200300.100.1.38': ('associatedName', ), - '0.9.2342.19200300.100.1.39': ('homePostalAddress', ), - '0.9.2342.19200300.100.1.40': ('personalTitle', ), - '0.9.2342.19200300.100.1.41': ('mobileTelephoneNumber', ), - '0.9.2342.19200300.100.1.42': ('pagerTelephoneNumber', ), - '0.9.2342.19200300.100.1.43': ('friendlyCountryName', ), - '0.9.2342.19200300.100.1.44': ('uniqueIdentifier', 'uid'), - '0.9.2342.19200300.100.1.45': ('organizationalStatus', ), - '0.9.2342.19200300.100.1.46': ('janetMailbox', ), - '0.9.2342.19200300.100.1.47': ('mailPreferenceOption', ), - '0.9.2342.19200300.100.1.48': ('buildingName', ), - '0.9.2342.19200300.100.1.49': ('dSAQuality', ), - '0.9.2342.19200300.100.1.50': ('singleLevelQuality', ), - '0.9.2342.19200300.100.1.51': ('subtreeMinimumQuality', ), - '0.9.2342.19200300.100.1.52': ('subtreeMaximumQuality', ), - '0.9.2342.19200300.100.1.53': ('personalSignature', ), - '0.9.2342.19200300.100.1.54': ('dITRedirect', ), - '0.9.2342.19200300.100.1.55': ('audio', ), - '0.9.2342.19200300.100.1.56': ('documentPublisher', ), - '0.9.2342.19200300.100.3': ('pilotAttributeSyntax', ), - '0.9.2342.19200300.100.3.4': ('iA5StringSyntax', ), - '0.9.2342.19200300.100.3.5': ('caseIgnoreIA5StringSyntax', ), - '0.9.2342.19200300.100.4': ('pilotObjectClass', ), - '0.9.2342.19200300.100.4.3': ('pilotObject', ), - '0.9.2342.19200300.100.4.4': ('pilotPerson', ), - '0.9.2342.19200300.100.4.5': ('account', ), - '0.9.2342.19200300.100.4.6': ('document', ), - '0.9.2342.19200300.100.4.7': ('room', ), - '0.9.2342.19200300.100.4.9': ('documentSeries', ), - '0.9.2342.19200300.100.4.13': ('Domain', 'domain'), - '0.9.2342.19200300.100.4.14': ('rFC822localPart', ), - '0.9.2342.19200300.100.4.15': ('dNSDomain', ), - '0.9.2342.19200300.100.4.17': ('domainRelatedObject', ), - '0.9.2342.19200300.100.4.18': ('friendlyCountry', ), - '0.9.2342.19200300.100.4.19': ('simpleSecurityObject', ), - '0.9.2342.19200300.100.4.20': ('pilotOrganization', ), - '0.9.2342.19200300.100.4.21': ('pilotDSA', ), - '0.9.2342.19200300.100.4.22': ('qualityLabelledData', ), - '0.9.2342.19200300.100.10': ('pilotGroups', ), - '1': ('iso', 'ISO'), - '1.0.9797.3.4': ('gmac', 'GMAC'), - '1.0.10118.3.0.55': ('whirlpool', ), - '1.2': ('ISO Member Body', 'member-body'), - '1.2.156': ('ISO CN Member Body', 'ISO-CN'), - '1.2.156.10197': ('oscca', ), - '1.2.156.10197.1': ('sm-scheme', ), - '1.2.156.10197.1.104.1': ('sm4-ecb', 'SM4-ECB'), - '1.2.156.10197.1.104.2': ('sm4-cbc', 'SM4-CBC'), - '1.2.156.10197.1.104.3': ('sm4-ofb', 'SM4-OFB'), - '1.2.156.10197.1.104.4': ('sm4-cfb', 'SM4-CFB'), - '1.2.156.10197.1.104.5': ('sm4-cfb1', 'SM4-CFB1'), - '1.2.156.10197.1.104.6': ('sm4-cfb8', 'SM4-CFB8'), - '1.2.156.10197.1.104.7': ('sm4-ctr', 'SM4-CTR'), - '1.2.156.10197.1.301': ('sm2', 'SM2'), - '1.2.156.10197.1.401': ('sm3', 'SM3'), - '1.2.156.10197.1.501': ('SM2-with-SM3', 'SM2-SM3'), - '1.2.156.10197.1.504': ('sm3WithRSAEncryption', 'RSA-SM3'), - '1.2.392.200011.61.1.1.1.2': ('camellia-128-cbc', 'CAMELLIA-128-CBC'), - '1.2.392.200011.61.1.1.1.3': ('camellia-192-cbc', 'CAMELLIA-192-CBC'), - '1.2.392.200011.61.1.1.1.4': ('camellia-256-cbc', 'CAMELLIA-256-CBC'), - '1.2.392.200011.61.1.1.3.2': ('id-camellia128-wrap', ), - '1.2.392.200011.61.1.1.3.3': ('id-camellia192-wrap', ), - '1.2.392.200011.61.1.1.3.4': ('id-camellia256-wrap', ), - '1.2.410.200004': ('kisa', 'KISA'), - '1.2.410.200004.1.3': ('seed-ecb', 'SEED-ECB'), - '1.2.410.200004.1.4': ('seed-cbc', 'SEED-CBC'), - '1.2.410.200004.1.5': ('seed-cfb', 'SEED-CFB'), - '1.2.410.200004.1.6': ('seed-ofb', 'SEED-OFB'), - '1.2.410.200046.1.1': ('aria', ), - '1.2.410.200046.1.1.1': ('aria-128-ecb', 'ARIA-128-ECB'), - '1.2.410.200046.1.1.2': ('aria-128-cbc', 'ARIA-128-CBC'), - '1.2.410.200046.1.1.3': ('aria-128-cfb', 'ARIA-128-CFB'), - '1.2.410.200046.1.1.4': ('aria-128-ofb', 'ARIA-128-OFB'), - '1.2.410.200046.1.1.5': ('aria-128-ctr', 'ARIA-128-CTR'), - '1.2.410.200046.1.1.6': ('aria-192-ecb', 'ARIA-192-ECB'), - '1.2.410.200046.1.1.7': ('aria-192-cbc', 'ARIA-192-CBC'), - '1.2.410.200046.1.1.8': ('aria-192-cfb', 'ARIA-192-CFB'), - '1.2.410.200046.1.1.9': ('aria-192-ofb', 'ARIA-192-OFB'), - '1.2.410.200046.1.1.10': ('aria-192-ctr', 'ARIA-192-CTR'), - '1.2.410.200046.1.1.11': ('aria-256-ecb', 'ARIA-256-ECB'), - '1.2.410.200046.1.1.12': ('aria-256-cbc', 'ARIA-256-CBC'), - '1.2.410.200046.1.1.13': ('aria-256-cfb', 'ARIA-256-CFB'), - '1.2.410.200046.1.1.14': ('aria-256-ofb', 'ARIA-256-OFB'), - '1.2.410.200046.1.1.15': ('aria-256-ctr', 'ARIA-256-CTR'), - '1.2.410.200046.1.1.34': ('aria-128-gcm', 'ARIA-128-GCM'), - '1.2.410.200046.1.1.35': ('aria-192-gcm', 'ARIA-192-GCM'), - '1.2.410.200046.1.1.36': ('aria-256-gcm', 'ARIA-256-GCM'), - '1.2.410.200046.1.1.37': ('aria-128-ccm', 'ARIA-128-CCM'), - '1.2.410.200046.1.1.38': ('aria-192-ccm', 'ARIA-192-CCM'), - '1.2.410.200046.1.1.39': ('aria-256-ccm', 'ARIA-256-CCM'), - '1.2.643.2.2': ('cryptopro', ), - '1.2.643.2.2.3': ('GOST R 34.11-94 with GOST R 34.10-2001', 'id-GostR3411-94-with-GostR3410-2001'), - '1.2.643.2.2.4': ('GOST R 34.11-94 with GOST R 34.10-94', 'id-GostR3411-94-with-GostR3410-94'), - '1.2.643.2.2.9': ('GOST R 34.11-94', 'md_gost94'), - '1.2.643.2.2.10': ('HMAC GOST 34.11-94', 'id-HMACGostR3411-94'), - '1.2.643.2.2.14.0': ('id-Gost28147-89-None-KeyMeshing', ), - '1.2.643.2.2.14.1': ('id-Gost28147-89-CryptoPro-KeyMeshing', ), - '1.2.643.2.2.19': ('GOST R 34.10-2001', 'gost2001'), - '1.2.643.2.2.20': ('GOST R 34.10-94', 'gost94'), - '1.2.643.2.2.20.1': ('id-GostR3410-94-a', ), - '1.2.643.2.2.20.2': ('id-GostR3410-94-aBis', ), - '1.2.643.2.2.20.3': ('id-GostR3410-94-b', ), - '1.2.643.2.2.20.4': ('id-GostR3410-94-bBis', ), - '1.2.643.2.2.21': ('GOST 28147-89', 'gost89'), - '1.2.643.2.2.22': ('GOST 28147-89 MAC', 'gost-mac'), - '1.2.643.2.2.23': ('GOST R 34.11-94 PRF', 'prf-gostr3411-94'), - '1.2.643.2.2.30.0': ('id-GostR3411-94-TestParamSet', ), - '1.2.643.2.2.30.1': ('id-GostR3411-94-CryptoProParamSet', ), - '1.2.643.2.2.31.0': ('id-Gost28147-89-TestParamSet', ), - '1.2.643.2.2.31.1': ('id-Gost28147-89-CryptoPro-A-ParamSet', ), - '1.2.643.2.2.31.2': ('id-Gost28147-89-CryptoPro-B-ParamSet', ), - '1.2.643.2.2.31.3': ('id-Gost28147-89-CryptoPro-C-ParamSet', ), - '1.2.643.2.2.31.4': ('id-Gost28147-89-CryptoPro-D-ParamSet', ), - '1.2.643.2.2.31.5': ('id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet', ), - '1.2.643.2.2.31.6': ('id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet', ), - '1.2.643.2.2.31.7': ('id-Gost28147-89-CryptoPro-RIC-1-ParamSet', ), - '1.2.643.2.2.32.0': ('id-GostR3410-94-TestParamSet', ), - '1.2.643.2.2.32.2': ('id-GostR3410-94-CryptoPro-A-ParamSet', ), - '1.2.643.2.2.32.3': ('id-GostR3410-94-CryptoPro-B-ParamSet', ), - '1.2.643.2.2.32.4': ('id-GostR3410-94-CryptoPro-C-ParamSet', ), - '1.2.643.2.2.32.5': ('id-GostR3410-94-CryptoPro-D-ParamSet', ), - '1.2.643.2.2.33.1': ('id-GostR3410-94-CryptoPro-XchA-ParamSet', ), - '1.2.643.2.2.33.2': ('id-GostR3410-94-CryptoPro-XchB-ParamSet', ), - '1.2.643.2.2.33.3': ('id-GostR3410-94-CryptoPro-XchC-ParamSet', ), - '1.2.643.2.2.35.0': ('id-GostR3410-2001-TestParamSet', ), - '1.2.643.2.2.35.1': ('id-GostR3410-2001-CryptoPro-A-ParamSet', ), - '1.2.643.2.2.35.2': ('id-GostR3410-2001-CryptoPro-B-ParamSet', ), - '1.2.643.2.2.35.3': ('id-GostR3410-2001-CryptoPro-C-ParamSet', ), - '1.2.643.2.2.36.0': ('id-GostR3410-2001-CryptoPro-XchA-ParamSet', ), - '1.2.643.2.2.36.1': ('id-GostR3410-2001-CryptoPro-XchB-ParamSet', ), - '1.2.643.2.2.98': ('GOST R 34.10-2001 DH', 'id-GostR3410-2001DH'), - '1.2.643.2.2.99': ('GOST R 34.10-94 DH', 'id-GostR3410-94DH'), - '1.2.643.2.9': ('cryptocom', ), - '1.2.643.2.9.1.3.3': ('GOST R 34.11-94 with GOST R 34.10-94 Cryptocom', 'id-GostR3411-94-with-GostR3410-94-cc'), - '1.2.643.2.9.1.3.4': ('GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom', 'id-GostR3411-94-with-GostR3410-2001-cc'), - '1.2.643.2.9.1.5.3': ('GOST 34.10-94 Cryptocom', 'gost94cc'), - '1.2.643.2.9.1.5.4': ('GOST 34.10-2001 Cryptocom', 'gost2001cc'), - '1.2.643.2.9.1.6.1': ('GOST 28147-89 Cryptocom ParamSet', 'id-Gost28147-89-cc'), - '1.2.643.2.9.1.8.1': ('GOST R 3410-2001 Parameter Set Cryptocom', 'id-GostR3410-2001-ParamSet-cc'), - '1.2.643.3.131.1.1': ('INN', 'INN'), - '1.2.643.7.1': ('id-tc26', ), - '1.2.643.7.1.1': ('id-tc26-algorithms', ), - '1.2.643.7.1.1.1': ('id-tc26-sign', ), - '1.2.643.7.1.1.1.1': ('GOST R 34.10-2012 with 256 bit modulus', 'gost2012_256'), - '1.2.643.7.1.1.1.2': ('GOST R 34.10-2012 with 512 bit modulus', 'gost2012_512'), - '1.2.643.7.1.1.2': ('id-tc26-digest', ), - '1.2.643.7.1.1.2.2': ('GOST R 34.11-2012 with 256 bit hash', 'md_gost12_256'), - '1.2.643.7.1.1.2.3': ('GOST R 34.11-2012 with 512 bit hash', 'md_gost12_512'), - '1.2.643.7.1.1.3': ('id-tc26-signwithdigest', ), - '1.2.643.7.1.1.3.2': ('GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)', 'id-tc26-signwithdigest-gost3410-2012-256'), - '1.2.643.7.1.1.3.3': ('GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)', 'id-tc26-signwithdigest-gost3410-2012-512'), - '1.2.643.7.1.1.4': ('id-tc26-mac', ), - '1.2.643.7.1.1.4.1': ('HMAC GOST 34.11-2012 256 bit', 'id-tc26-hmac-gost-3411-2012-256'), - '1.2.643.7.1.1.4.2': ('HMAC GOST 34.11-2012 512 bit', 'id-tc26-hmac-gost-3411-2012-512'), - '1.2.643.7.1.1.5': ('id-tc26-cipher', ), - '1.2.643.7.1.1.5.1': ('id-tc26-cipher-gostr3412-2015-magma', ), - '1.2.643.7.1.1.5.1.1': ('id-tc26-cipher-gostr3412-2015-magma-ctracpkm', ), - '1.2.643.7.1.1.5.1.2': ('id-tc26-cipher-gostr3412-2015-magma-ctracpkm-omac', ), - '1.2.643.7.1.1.5.2': ('id-tc26-cipher-gostr3412-2015-kuznyechik', ), - '1.2.643.7.1.1.5.2.1': ('id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm', ), - '1.2.643.7.1.1.5.2.2': ('id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm-omac', ), - '1.2.643.7.1.1.6': ('id-tc26-agreement', ), - '1.2.643.7.1.1.6.1': ('id-tc26-agreement-gost-3410-2012-256', ), - '1.2.643.7.1.1.6.2': ('id-tc26-agreement-gost-3410-2012-512', ), - '1.2.643.7.1.1.7': ('id-tc26-wrap', ), - '1.2.643.7.1.1.7.1': ('id-tc26-wrap-gostr3412-2015-magma', ), - '1.2.643.7.1.1.7.1.1': ('id-tc26-wrap-gostr3412-2015-magma-kexp15', 'id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15'), - '1.2.643.7.1.1.7.2': ('id-tc26-wrap-gostr3412-2015-kuznyechik', ), - '1.2.643.7.1.2': ('id-tc26-constants', ), - '1.2.643.7.1.2.1': ('id-tc26-sign-constants', ), - '1.2.643.7.1.2.1.1': ('id-tc26-gost-3410-2012-256-constants', ), - '1.2.643.7.1.2.1.1.1': ('GOST R 34.10-2012 (256 bit) ParamSet A', 'id-tc26-gost-3410-2012-256-paramSetA'), - '1.2.643.7.1.2.1.1.2': ('GOST R 34.10-2012 (256 bit) ParamSet B', 'id-tc26-gost-3410-2012-256-paramSetB'), - '1.2.643.7.1.2.1.1.3': ('GOST R 34.10-2012 (256 bit) ParamSet C', 'id-tc26-gost-3410-2012-256-paramSetC'), - '1.2.643.7.1.2.1.1.4': ('GOST R 34.10-2012 (256 bit) ParamSet D', 'id-tc26-gost-3410-2012-256-paramSetD'), - '1.2.643.7.1.2.1.2': ('id-tc26-gost-3410-2012-512-constants', ), - '1.2.643.7.1.2.1.2.0': ('GOST R 34.10-2012 (512 bit) testing parameter set', 'id-tc26-gost-3410-2012-512-paramSetTest'), - '1.2.643.7.1.2.1.2.1': ('GOST R 34.10-2012 (512 bit) ParamSet A', 'id-tc26-gost-3410-2012-512-paramSetA'), - '1.2.643.7.1.2.1.2.2': ('GOST R 34.10-2012 (512 bit) ParamSet B', 'id-tc26-gost-3410-2012-512-paramSetB'), - '1.2.643.7.1.2.1.2.3': ('GOST R 34.10-2012 (512 bit) ParamSet C', 'id-tc26-gost-3410-2012-512-paramSetC'), - '1.2.643.7.1.2.2': ('id-tc26-digest-constants', ), - '1.2.643.7.1.2.5': ('id-tc26-cipher-constants', ), - '1.2.643.7.1.2.5.1': ('id-tc26-gost-28147-constants', ), - '1.2.643.7.1.2.5.1.1': ('GOST 28147-89 TC26 parameter set', 'id-tc26-gost-28147-param-Z'), - '1.2.643.100.1': ('OGRN', 'OGRN'), - '1.2.643.100.3': ('SNILS', 'SNILS'), - '1.2.643.100.111': ('Signing Tool of Subject', 'subjectSignTool'), - '1.2.643.100.112': ('Signing Tool of Issuer', 'issuerSignTool'), - '1.2.804': ('ISO-UA', ), - '1.2.804.2.1.1.1': ('ua-pki', ), - '1.2.804.2.1.1.1.1.1.1': ('DSTU Gost 28147-2009', 'dstu28147'), - '1.2.804.2.1.1.1.1.1.1.2': ('DSTU Gost 28147-2009 OFB mode', 'dstu28147-ofb'), - '1.2.804.2.1.1.1.1.1.1.3': ('DSTU Gost 28147-2009 CFB mode', 'dstu28147-cfb'), - '1.2.804.2.1.1.1.1.1.1.5': ('DSTU Gost 28147-2009 key wrap', 'dstu28147-wrap'), - '1.2.804.2.1.1.1.1.1.2': ('HMAC DSTU Gost 34311-95', 'hmacWithDstu34311'), - '1.2.804.2.1.1.1.1.2.1': ('DSTU Gost 34311-95', 'dstu34311'), - '1.2.804.2.1.1.1.1.3.1.1': ('DSTU 4145-2002 little endian', 'dstu4145le'), - '1.2.804.2.1.1.1.1.3.1.1.1.1': ('DSTU 4145-2002 big endian', 'dstu4145be'), - '1.2.804.2.1.1.1.1.3.1.1.2.0': ('DSTU curve 0', 'uacurve0'), - '1.2.804.2.1.1.1.1.3.1.1.2.1': ('DSTU curve 1', 'uacurve1'), - '1.2.804.2.1.1.1.1.3.1.1.2.2': ('DSTU curve 2', 'uacurve2'), - '1.2.804.2.1.1.1.1.3.1.1.2.3': ('DSTU curve 3', 'uacurve3'), - '1.2.804.2.1.1.1.1.3.1.1.2.4': ('DSTU curve 4', 'uacurve4'), - '1.2.804.2.1.1.1.1.3.1.1.2.5': ('DSTU curve 5', 'uacurve5'), - '1.2.804.2.1.1.1.1.3.1.1.2.6': ('DSTU curve 6', 'uacurve6'), - '1.2.804.2.1.1.1.1.3.1.1.2.7': ('DSTU curve 7', 'uacurve7'), - '1.2.804.2.1.1.1.1.3.1.1.2.8': ('DSTU curve 8', 'uacurve8'), - '1.2.804.2.1.1.1.1.3.1.1.2.9': ('DSTU curve 9', 'uacurve9'), - '1.2.840': ('ISO US Member Body', 'ISO-US'), - '1.2.840.10040': ('X9.57', 'X9-57'), - '1.2.840.10040.2': ('holdInstruction', ), - '1.2.840.10040.2.1': ('Hold Instruction None', 'holdInstructionNone'), - '1.2.840.10040.2.2': ('Hold Instruction Call Issuer', 'holdInstructionCallIssuer'), - '1.2.840.10040.2.3': ('Hold Instruction Reject', 'holdInstructionReject'), - '1.2.840.10040.4': ('X9.57 CM ?', 'X9cm'), - '1.2.840.10040.4.1': ('dsaEncryption', 'DSA'), - '1.2.840.10040.4.3': ('dsaWithSHA1', 'DSA-SHA1'), - '1.2.840.10045': ('ANSI X9.62', 'ansi-X9-62'), - '1.2.840.10045.1': ('id-fieldType', ), - '1.2.840.10045.1.1': ('prime-field', ), - '1.2.840.10045.1.2': ('characteristic-two-field', ), - '1.2.840.10045.1.2.3': ('id-characteristic-two-basis', ), - '1.2.840.10045.1.2.3.1': ('onBasis', ), - '1.2.840.10045.1.2.3.2': ('tpBasis', ), - '1.2.840.10045.1.2.3.3': ('ppBasis', ), - '1.2.840.10045.2': ('id-publicKeyType', ), - '1.2.840.10045.2.1': ('id-ecPublicKey', ), - '1.2.840.10045.3': ('ellipticCurve', ), - '1.2.840.10045.3.0': ('c-TwoCurve', ), - '1.2.840.10045.3.0.1': ('c2pnb163v1', ), - '1.2.840.10045.3.0.2': ('c2pnb163v2', ), - '1.2.840.10045.3.0.3': ('c2pnb163v3', ), - '1.2.840.10045.3.0.4': ('c2pnb176v1', ), - '1.2.840.10045.3.0.5': ('c2tnb191v1', ), - '1.2.840.10045.3.0.6': ('c2tnb191v2', ), - '1.2.840.10045.3.0.7': ('c2tnb191v3', ), - '1.2.840.10045.3.0.8': ('c2onb191v4', ), - '1.2.840.10045.3.0.9': ('c2onb191v5', ), - '1.2.840.10045.3.0.10': ('c2pnb208w1', ), - '1.2.840.10045.3.0.11': ('c2tnb239v1', ), - '1.2.840.10045.3.0.12': ('c2tnb239v2', ), - '1.2.840.10045.3.0.13': ('c2tnb239v3', ), - '1.2.840.10045.3.0.14': ('c2onb239v4', ), - '1.2.840.10045.3.0.15': ('c2onb239v5', ), - '1.2.840.10045.3.0.16': ('c2pnb272w1', ), - '1.2.840.10045.3.0.17': ('c2pnb304w1', ), - '1.2.840.10045.3.0.18': ('c2tnb359v1', ), - '1.2.840.10045.3.0.19': ('c2pnb368w1', ), - '1.2.840.10045.3.0.20': ('c2tnb431r1', ), - '1.2.840.10045.3.1': ('primeCurve', ), - '1.2.840.10045.3.1.1': ('prime192v1', ), - '1.2.840.10045.3.1.2': ('prime192v2', ), - '1.2.840.10045.3.1.3': ('prime192v3', ), - '1.2.840.10045.3.1.4': ('prime239v1', ), - '1.2.840.10045.3.1.5': ('prime239v2', ), - '1.2.840.10045.3.1.6': ('prime239v3', ), - '1.2.840.10045.3.1.7': ('prime256v1', ), - '1.2.840.10045.4': ('id-ecSigType', ), - '1.2.840.10045.4.1': ('ecdsa-with-SHA1', ), - '1.2.840.10045.4.2': ('ecdsa-with-Recommended', ), - '1.2.840.10045.4.3': ('ecdsa-with-Specified', ), - '1.2.840.10045.4.3.1': ('ecdsa-with-SHA224', ), - '1.2.840.10045.4.3.2': ('ecdsa-with-SHA256', ), - '1.2.840.10045.4.3.3': ('ecdsa-with-SHA384', ), - '1.2.840.10045.4.3.4': ('ecdsa-with-SHA512', ), - '1.2.840.10046.2.1': ('X9.42 DH', 'dhpublicnumber'), - '1.2.840.113533.7.66.10': ('cast5-cbc', 'CAST5-CBC'), - '1.2.840.113533.7.66.12': ('pbeWithMD5AndCast5CBC', ), - '1.2.840.113533.7.66.13': ('password based MAC', 'id-PasswordBasedMAC'), - '1.2.840.113533.7.66.30': ('Diffie-Hellman based MAC', 'id-DHBasedMac'), - '1.2.840.113549': ('RSA Data Security, Inc.', 'rsadsi'), - '1.2.840.113549.1': ('RSA Data Security, Inc. PKCS', 'pkcs'), - '1.2.840.113549.1.1': ('pkcs1', ), - '1.2.840.113549.1.1.1': ('rsaEncryption', ), - '1.2.840.113549.1.1.2': ('md2WithRSAEncryption', 'RSA-MD2'), - '1.2.840.113549.1.1.3': ('md4WithRSAEncryption', 'RSA-MD4'), - '1.2.840.113549.1.1.4': ('md5WithRSAEncryption', 'RSA-MD5'), - '1.2.840.113549.1.1.5': ('sha1WithRSAEncryption', 'RSA-SHA1'), - '1.2.840.113549.1.1.6': ('rsaOAEPEncryptionSET', ), - '1.2.840.113549.1.1.7': ('rsaesOaep', 'RSAES-OAEP'), - '1.2.840.113549.1.1.8': ('mgf1', 'MGF1'), - '1.2.840.113549.1.1.9': ('pSpecified', 'PSPECIFIED'), - '1.2.840.113549.1.1.10': ('rsassaPss', 'RSASSA-PSS'), - '1.2.840.113549.1.1.11': ('sha256WithRSAEncryption', 'RSA-SHA256'), - '1.2.840.113549.1.1.12': ('sha384WithRSAEncryption', 'RSA-SHA384'), - '1.2.840.113549.1.1.13': ('sha512WithRSAEncryption', 'RSA-SHA512'), - '1.2.840.113549.1.1.14': ('sha224WithRSAEncryption', 'RSA-SHA224'), - '1.2.840.113549.1.1.15': ('sha512-224WithRSAEncryption', 'RSA-SHA512/224'), - '1.2.840.113549.1.1.16': ('sha512-256WithRSAEncryption', 'RSA-SHA512/256'), - '1.2.840.113549.1.3': ('pkcs3', ), - '1.2.840.113549.1.3.1': ('dhKeyAgreement', ), - '1.2.840.113549.1.5': ('pkcs5', ), - '1.2.840.113549.1.5.1': ('pbeWithMD2AndDES-CBC', 'PBE-MD2-DES'), - '1.2.840.113549.1.5.3': ('pbeWithMD5AndDES-CBC', 'PBE-MD5-DES'), - '1.2.840.113549.1.5.4': ('pbeWithMD2AndRC2-CBC', 'PBE-MD2-RC2-64'), - '1.2.840.113549.1.5.6': ('pbeWithMD5AndRC2-CBC', 'PBE-MD5-RC2-64'), - '1.2.840.113549.1.5.10': ('pbeWithSHA1AndDES-CBC', 'PBE-SHA1-DES'), - '1.2.840.113549.1.5.11': ('pbeWithSHA1AndRC2-CBC', 'PBE-SHA1-RC2-64'), - '1.2.840.113549.1.5.12': ('PBKDF2', ), - '1.2.840.113549.1.5.13': ('PBES2', ), - '1.2.840.113549.1.5.14': ('PBMAC1', ), - '1.2.840.113549.1.7': ('pkcs7', ), - '1.2.840.113549.1.7.1': ('pkcs7-data', ), - '1.2.840.113549.1.7.2': ('pkcs7-signedData', ), - '1.2.840.113549.1.7.3': ('pkcs7-envelopedData', ), - '1.2.840.113549.1.7.4': ('pkcs7-signedAndEnvelopedData', ), - '1.2.840.113549.1.7.5': ('pkcs7-digestData', ), - '1.2.840.113549.1.7.6': ('pkcs7-encryptedData', ), - '1.2.840.113549.1.9': ('pkcs9', ), - '1.2.840.113549.1.9.1': ('emailAddress', ), - '1.2.840.113549.1.9.2': ('unstructuredName', ), - '1.2.840.113549.1.9.3': ('contentType', ), - '1.2.840.113549.1.9.4': ('messageDigest', ), - '1.2.840.113549.1.9.5': ('signingTime', ), - '1.2.840.113549.1.9.6': ('countersignature', ), - '1.2.840.113549.1.9.7': ('challengePassword', ), - '1.2.840.113549.1.9.8': ('unstructuredAddress', ), - '1.2.840.113549.1.9.9': ('extendedCertificateAttributes', ), - '1.2.840.113549.1.9.14': ('Extension Request', 'extReq'), - '1.2.840.113549.1.9.15': ('S/MIME Capabilities', 'SMIME-CAPS'), - '1.2.840.113549.1.9.16': ('S/MIME', 'SMIME'), - '1.2.840.113549.1.9.16.0': ('id-smime-mod', ), - '1.2.840.113549.1.9.16.0.1': ('id-smime-mod-cms', ), - '1.2.840.113549.1.9.16.0.2': ('id-smime-mod-ess', ), - '1.2.840.113549.1.9.16.0.3': ('id-smime-mod-oid', ), - '1.2.840.113549.1.9.16.0.4': ('id-smime-mod-msg-v3', ), - '1.2.840.113549.1.9.16.0.5': ('id-smime-mod-ets-eSignature-88', ), - '1.2.840.113549.1.9.16.0.6': ('id-smime-mod-ets-eSignature-97', ), - '1.2.840.113549.1.9.16.0.7': ('id-smime-mod-ets-eSigPolicy-88', ), - '1.2.840.113549.1.9.16.0.8': ('id-smime-mod-ets-eSigPolicy-97', ), - '1.2.840.113549.1.9.16.1': ('id-smime-ct', ), - '1.2.840.113549.1.9.16.1.1': ('id-smime-ct-receipt', ), - '1.2.840.113549.1.9.16.1.2': ('id-smime-ct-authData', ), - '1.2.840.113549.1.9.16.1.3': ('id-smime-ct-publishCert', ), - '1.2.840.113549.1.9.16.1.4': ('id-smime-ct-TSTInfo', ), - '1.2.840.113549.1.9.16.1.5': ('id-smime-ct-TDTInfo', ), - '1.2.840.113549.1.9.16.1.6': ('id-smime-ct-contentInfo', ), - '1.2.840.113549.1.9.16.1.7': ('id-smime-ct-DVCSRequestData', ), - '1.2.840.113549.1.9.16.1.8': ('id-smime-ct-DVCSResponseData', ), - '1.2.840.113549.1.9.16.1.9': ('id-smime-ct-compressedData', ), - '1.2.840.113549.1.9.16.1.19': ('id-smime-ct-contentCollection', ), - '1.2.840.113549.1.9.16.1.23': ('id-smime-ct-authEnvelopedData', ), - '1.2.840.113549.1.9.16.1.27': ('id-ct-asciiTextWithCRLF', ), - '1.2.840.113549.1.9.16.1.28': ('id-ct-xml', ), - '1.2.840.113549.1.9.16.2': ('id-smime-aa', ), - '1.2.840.113549.1.9.16.2.1': ('id-smime-aa-receiptRequest', ), - '1.2.840.113549.1.9.16.2.2': ('id-smime-aa-securityLabel', ), - '1.2.840.113549.1.9.16.2.3': ('id-smime-aa-mlExpandHistory', ), - '1.2.840.113549.1.9.16.2.4': ('id-smime-aa-contentHint', ), - '1.2.840.113549.1.9.16.2.5': ('id-smime-aa-msgSigDigest', ), - '1.2.840.113549.1.9.16.2.6': ('id-smime-aa-encapContentType', ), - '1.2.840.113549.1.9.16.2.7': ('id-smime-aa-contentIdentifier', ), - '1.2.840.113549.1.9.16.2.8': ('id-smime-aa-macValue', ), - '1.2.840.113549.1.9.16.2.9': ('id-smime-aa-equivalentLabels', ), - '1.2.840.113549.1.9.16.2.10': ('id-smime-aa-contentReference', ), - '1.2.840.113549.1.9.16.2.11': ('id-smime-aa-encrypKeyPref', ), - '1.2.840.113549.1.9.16.2.12': ('id-smime-aa-signingCertificate', ), - '1.2.840.113549.1.9.16.2.13': ('id-smime-aa-smimeEncryptCerts', ), - '1.2.840.113549.1.9.16.2.14': ('id-smime-aa-timeStampToken', ), - '1.2.840.113549.1.9.16.2.15': ('id-smime-aa-ets-sigPolicyId', ), - '1.2.840.113549.1.9.16.2.16': ('id-smime-aa-ets-commitmentType', ), - '1.2.840.113549.1.9.16.2.17': ('id-smime-aa-ets-signerLocation', ), - '1.2.840.113549.1.9.16.2.18': ('id-smime-aa-ets-signerAttr', ), - '1.2.840.113549.1.9.16.2.19': ('id-smime-aa-ets-otherSigCert', ), - '1.2.840.113549.1.9.16.2.20': ('id-smime-aa-ets-contentTimestamp', ), - '1.2.840.113549.1.9.16.2.21': ('id-smime-aa-ets-CertificateRefs', ), - '1.2.840.113549.1.9.16.2.22': ('id-smime-aa-ets-RevocationRefs', ), - '1.2.840.113549.1.9.16.2.23': ('id-smime-aa-ets-certValues', ), - '1.2.840.113549.1.9.16.2.24': ('id-smime-aa-ets-revocationValues', ), - '1.2.840.113549.1.9.16.2.25': ('id-smime-aa-ets-escTimeStamp', ), - '1.2.840.113549.1.9.16.2.26': ('id-smime-aa-ets-certCRLTimestamp', ), - '1.2.840.113549.1.9.16.2.27': ('id-smime-aa-ets-archiveTimeStamp', ), - '1.2.840.113549.1.9.16.2.28': ('id-smime-aa-signatureType', ), - '1.2.840.113549.1.9.16.2.29': ('id-smime-aa-dvcs-dvc', ), - '1.2.840.113549.1.9.16.2.47': ('id-smime-aa-signingCertificateV2', ), - '1.2.840.113549.1.9.16.3': ('id-smime-alg', ), - '1.2.840.113549.1.9.16.3.1': ('id-smime-alg-ESDHwith3DES', ), - '1.2.840.113549.1.9.16.3.2': ('id-smime-alg-ESDHwithRC2', ), - '1.2.840.113549.1.9.16.3.3': ('id-smime-alg-3DESwrap', ), - '1.2.840.113549.1.9.16.3.4': ('id-smime-alg-RC2wrap', ), - '1.2.840.113549.1.9.16.3.5': ('id-smime-alg-ESDH', ), - '1.2.840.113549.1.9.16.3.6': ('id-smime-alg-CMS3DESwrap', ), - '1.2.840.113549.1.9.16.3.7': ('id-smime-alg-CMSRC2wrap', ), - '1.2.840.113549.1.9.16.3.8': ('zlib compression', 'ZLIB'), - '1.2.840.113549.1.9.16.3.9': ('id-alg-PWRI-KEK', ), - '1.2.840.113549.1.9.16.4': ('id-smime-cd', ), - '1.2.840.113549.1.9.16.4.1': ('id-smime-cd-ldap', ), - '1.2.840.113549.1.9.16.5': ('id-smime-spq', ), - '1.2.840.113549.1.9.16.5.1': ('id-smime-spq-ets-sqt-uri', ), - '1.2.840.113549.1.9.16.5.2': ('id-smime-spq-ets-sqt-unotice', ), - '1.2.840.113549.1.9.16.6': ('id-smime-cti', ), - '1.2.840.113549.1.9.16.6.1': ('id-smime-cti-ets-proofOfOrigin', ), - '1.2.840.113549.1.9.16.6.2': ('id-smime-cti-ets-proofOfReceipt', ), - '1.2.840.113549.1.9.16.6.3': ('id-smime-cti-ets-proofOfDelivery', ), - '1.2.840.113549.1.9.16.6.4': ('id-smime-cti-ets-proofOfSender', ), - '1.2.840.113549.1.9.16.6.5': ('id-smime-cti-ets-proofOfApproval', ), - '1.2.840.113549.1.9.16.6.6': ('id-smime-cti-ets-proofOfCreation', ), - '1.2.840.113549.1.9.20': ('friendlyName', ), - '1.2.840.113549.1.9.21': ('localKeyID', ), - '1.2.840.113549.1.9.22': ('certTypes', ), - '1.2.840.113549.1.9.22.1': ('x509Certificate', ), - '1.2.840.113549.1.9.22.2': ('sdsiCertificate', ), - '1.2.840.113549.1.9.23': ('crlTypes', ), - '1.2.840.113549.1.9.23.1': ('x509Crl', ), - '1.2.840.113549.1.12': ('pkcs12', ), - '1.2.840.113549.1.12.1': ('pkcs12-pbeids', ), - '1.2.840.113549.1.12.1.1': ('pbeWithSHA1And128BitRC4', 'PBE-SHA1-RC4-128'), - '1.2.840.113549.1.12.1.2': ('pbeWithSHA1And40BitRC4', 'PBE-SHA1-RC4-40'), - '1.2.840.113549.1.12.1.3': ('pbeWithSHA1And3-KeyTripleDES-CBC', 'PBE-SHA1-3DES'), - '1.2.840.113549.1.12.1.4': ('pbeWithSHA1And2-KeyTripleDES-CBC', 'PBE-SHA1-2DES'), - '1.2.840.113549.1.12.1.5': ('pbeWithSHA1And128BitRC2-CBC', 'PBE-SHA1-RC2-128'), - '1.2.840.113549.1.12.1.6': ('pbeWithSHA1And40BitRC2-CBC', 'PBE-SHA1-RC2-40'), - '1.2.840.113549.1.12.10': ('pkcs12-Version1', ), - '1.2.840.113549.1.12.10.1': ('pkcs12-BagIds', ), - '1.2.840.113549.1.12.10.1.1': ('keyBag', ), - '1.2.840.113549.1.12.10.1.2': ('pkcs8ShroudedKeyBag', ), - '1.2.840.113549.1.12.10.1.3': ('certBag', ), - '1.2.840.113549.1.12.10.1.4': ('crlBag', ), - '1.2.840.113549.1.12.10.1.5': ('secretBag', ), - '1.2.840.113549.1.12.10.1.6': ('safeContentsBag', ), - '1.2.840.113549.2.2': ('md2', 'MD2'), - '1.2.840.113549.2.4': ('md4', 'MD4'), - '1.2.840.113549.2.5': ('md5', 'MD5'), - '1.2.840.113549.2.6': ('hmacWithMD5', ), - '1.2.840.113549.2.7': ('hmacWithSHA1', ), - '1.2.840.113549.2.8': ('hmacWithSHA224', ), - '1.2.840.113549.2.9': ('hmacWithSHA256', ), - '1.2.840.113549.2.10': ('hmacWithSHA384', ), - '1.2.840.113549.2.11': ('hmacWithSHA512', ), - '1.2.840.113549.2.12': ('hmacWithSHA512-224', ), - '1.2.840.113549.2.13': ('hmacWithSHA512-256', ), - '1.2.840.113549.3.2': ('rc2-cbc', 'RC2-CBC'), - '1.2.840.113549.3.4': ('rc4', 'RC4'), - '1.2.840.113549.3.7': ('des-ede3-cbc', 'DES-EDE3-CBC'), - '1.2.840.113549.3.8': ('rc5-cbc', 'RC5-CBC'), - '1.2.840.113549.3.10': ('des-cdmf', 'DES-CDMF'), - '1.3': ('identified-organization', 'org', 'ORG'), - '1.3.6': ('dod', 'DOD'), - '1.3.6.1': ('iana', 'IANA', 'internet'), - '1.3.6.1.1': ('Directory', 'directory'), - '1.3.6.1.2': ('Management', 'mgmt'), - '1.3.6.1.3': ('Experimental', 'experimental'), - '1.3.6.1.4': ('Private', 'private'), - '1.3.6.1.4.1': ('Enterprises', 'enterprises'), - '1.3.6.1.4.1.188.7.1.1.2': ('idea-cbc', 'IDEA-CBC'), - '1.3.6.1.4.1.311.2.1.14': ('Microsoft Extension Request', 'msExtReq'), - '1.3.6.1.4.1.311.2.1.21': ('Microsoft Individual Code Signing', 'msCodeInd'), - '1.3.6.1.4.1.311.2.1.22': ('Microsoft Commercial Code Signing', 'msCodeCom'), - '1.3.6.1.4.1.311.10.3.1': ('Microsoft Trust List Signing', 'msCTLSign'), - '1.3.6.1.4.1.311.10.3.3': ('Microsoft Server Gated Crypto', 'msSGC'), - '1.3.6.1.4.1.311.10.3.4': ('Microsoft Encrypted File System', 'msEFS'), - '1.3.6.1.4.1.311.17.1': ('Microsoft CSP Name', 'CSPName'), - '1.3.6.1.4.1.311.17.2': ('Microsoft Local Key set', 'LocalKeySet'), - '1.3.6.1.4.1.311.20.2.2': ('Microsoft Smartcardlogin', 'msSmartcardLogin'), - '1.3.6.1.4.1.311.20.2.3': ('Microsoft Universal Principal Name', 'msUPN'), - '1.3.6.1.4.1.311.60.2.1.1': ('jurisdictionLocalityName', 'jurisdictionL'), - '1.3.6.1.4.1.311.60.2.1.2': ('jurisdictionStateOrProvinceName', 'jurisdictionST'), - '1.3.6.1.4.1.311.60.2.1.3': ('jurisdictionCountryName', 'jurisdictionC'), - '1.3.6.1.4.1.1466.344': ('dcObject', 'dcobject'), - '1.3.6.1.4.1.1722.12.2.1.16': ('blake2b512', 'BLAKE2b512'), - '1.3.6.1.4.1.1722.12.2.2.8': ('blake2s256', 'BLAKE2s256'), - '1.3.6.1.4.1.3029.1.2': ('bf-cbc', 'BF-CBC'), - '1.3.6.1.4.1.11129.2.4.2': ('CT Precertificate SCTs', 'ct_precert_scts'), - '1.3.6.1.4.1.11129.2.4.3': ('CT Precertificate Poison', 'ct_precert_poison'), - '1.3.6.1.4.1.11129.2.4.4': ('CT Precertificate Signer', 'ct_precert_signer'), - '1.3.6.1.4.1.11129.2.4.5': ('CT Certificate SCTs', 'ct_cert_scts'), - '1.3.6.1.4.1.11591.4.11': ('scrypt', 'id-scrypt'), - '1.3.6.1.5': ('Security', 'security'), - '1.3.6.1.5.2.3': ('id-pkinit', ), - '1.3.6.1.5.2.3.4': ('PKINIT Client Auth', 'pkInitClientAuth'), - '1.3.6.1.5.2.3.5': ('Signing KDC Response', 'pkInitKDC'), - '1.3.6.1.5.5.7': ('PKIX', ), - '1.3.6.1.5.5.7.0': ('id-pkix-mod', ), - '1.3.6.1.5.5.7.0.1': ('id-pkix1-explicit-88', ), - '1.3.6.1.5.5.7.0.2': ('id-pkix1-implicit-88', ), - '1.3.6.1.5.5.7.0.3': ('id-pkix1-explicit-93', ), - '1.3.6.1.5.5.7.0.4': ('id-pkix1-implicit-93', ), - '1.3.6.1.5.5.7.0.5': ('id-mod-crmf', ), - '1.3.6.1.5.5.7.0.6': ('id-mod-cmc', ), - '1.3.6.1.5.5.7.0.7': ('id-mod-kea-profile-88', ), - '1.3.6.1.5.5.7.0.8': ('id-mod-kea-profile-93', ), - '1.3.6.1.5.5.7.0.9': ('id-mod-cmp', ), - '1.3.6.1.5.5.7.0.10': ('id-mod-qualified-cert-88', ), - '1.3.6.1.5.5.7.0.11': ('id-mod-qualified-cert-93', ), - '1.3.6.1.5.5.7.0.12': ('id-mod-attribute-cert', ), - '1.3.6.1.5.5.7.0.13': ('id-mod-timestamp-protocol', ), - '1.3.6.1.5.5.7.0.14': ('id-mod-ocsp', ), - '1.3.6.1.5.5.7.0.15': ('id-mod-dvcs', ), - '1.3.6.1.5.5.7.0.16': ('id-mod-cmp2000', ), - '1.3.6.1.5.5.7.1': ('id-pe', ), - '1.3.6.1.5.5.7.1.1': ('Authority Information Access', 'authorityInfoAccess'), - '1.3.6.1.5.5.7.1.2': ('Biometric Info', 'biometricInfo'), - '1.3.6.1.5.5.7.1.3': ('qcStatements', ), - '1.3.6.1.5.5.7.1.4': ('ac-auditEntity', ), - '1.3.6.1.5.5.7.1.5': ('ac-targeting', ), - '1.3.6.1.5.5.7.1.6': ('aaControls', ), - '1.3.6.1.5.5.7.1.7': ('sbgp-ipAddrBlock', ), - '1.3.6.1.5.5.7.1.8': ('sbgp-autonomousSysNum', ), - '1.3.6.1.5.5.7.1.9': ('sbgp-routerIdentifier', ), - '1.3.6.1.5.5.7.1.10': ('ac-proxying', ), - '1.3.6.1.5.5.7.1.11': ('Subject Information Access', 'subjectInfoAccess'), - '1.3.6.1.5.5.7.1.14': ('Proxy Certificate Information', 'proxyCertInfo'), - '1.3.6.1.5.5.7.1.24': ('TLS Feature', 'tlsfeature'), - '1.3.6.1.5.5.7.2': ('id-qt', ), - '1.3.6.1.5.5.7.2.1': ('Policy Qualifier CPS', 'id-qt-cps'), - '1.3.6.1.5.5.7.2.2': ('Policy Qualifier User Notice', 'id-qt-unotice'), - '1.3.6.1.5.5.7.2.3': ('textNotice', ), - '1.3.6.1.5.5.7.3': ('id-kp', ), - '1.3.6.1.5.5.7.3.1': ('TLS Web Server Authentication', 'serverAuth'), - '1.3.6.1.5.5.7.3.2': ('TLS Web Client Authentication', 'clientAuth'), - '1.3.6.1.5.5.7.3.3': ('Code Signing', 'codeSigning'), - '1.3.6.1.5.5.7.3.4': ('E-mail Protection', 'emailProtection'), - '1.3.6.1.5.5.7.3.5': ('IPSec End System', 'ipsecEndSystem'), - '1.3.6.1.5.5.7.3.6': ('IPSec Tunnel', 'ipsecTunnel'), - '1.3.6.1.5.5.7.3.7': ('IPSec User', 'ipsecUser'), - '1.3.6.1.5.5.7.3.8': ('Time Stamping', 'timeStamping'), - '1.3.6.1.5.5.7.3.9': ('OCSP Signing', 'OCSPSigning'), - '1.3.6.1.5.5.7.3.10': ('dvcs', 'DVCS'), - '1.3.6.1.5.5.7.3.17': ('ipsec Internet Key Exchange', 'ipsecIKE'), - '1.3.6.1.5.5.7.3.18': ('Ctrl/provision WAP Access', 'capwapAC'), - '1.3.6.1.5.5.7.3.19': ('Ctrl/Provision WAP Termination', 'capwapWTP'), - '1.3.6.1.5.5.7.3.21': ('SSH Client', 'secureShellClient'), - '1.3.6.1.5.5.7.3.22': ('SSH Server', 'secureShellServer'), - '1.3.6.1.5.5.7.3.23': ('Send Router', 'sendRouter'), - '1.3.6.1.5.5.7.3.24': ('Send Proxied Router', 'sendProxiedRouter'), - '1.3.6.1.5.5.7.3.25': ('Send Owner', 'sendOwner'), - '1.3.6.1.5.5.7.3.26': ('Send Proxied Owner', 'sendProxiedOwner'), - '1.3.6.1.5.5.7.3.27': ('CMC Certificate Authority', 'cmcCA'), - '1.3.6.1.5.5.7.3.28': ('CMC Registration Authority', 'cmcRA'), - '1.3.6.1.5.5.7.4': ('id-it', ), - '1.3.6.1.5.5.7.4.1': ('id-it-caProtEncCert', ), - '1.3.6.1.5.5.7.4.2': ('id-it-signKeyPairTypes', ), - '1.3.6.1.5.5.7.4.3': ('id-it-encKeyPairTypes', ), - '1.3.6.1.5.5.7.4.4': ('id-it-preferredSymmAlg', ), - '1.3.6.1.5.5.7.4.5': ('id-it-caKeyUpdateInfo', ), - '1.3.6.1.5.5.7.4.6': ('id-it-currentCRL', ), - '1.3.6.1.5.5.7.4.7': ('id-it-unsupportedOIDs', ), - '1.3.6.1.5.5.7.4.8': ('id-it-subscriptionRequest', ), - '1.3.6.1.5.5.7.4.9': ('id-it-subscriptionResponse', ), - '1.3.6.1.5.5.7.4.10': ('id-it-keyPairParamReq', ), - '1.3.6.1.5.5.7.4.11': ('id-it-keyPairParamRep', ), - '1.3.6.1.5.5.7.4.12': ('id-it-revPassphrase', ), - '1.3.6.1.5.5.7.4.13': ('id-it-implicitConfirm', ), - '1.3.6.1.5.5.7.4.14': ('id-it-confirmWaitTime', ), - '1.3.6.1.5.5.7.4.15': ('id-it-origPKIMessage', ), - '1.3.6.1.5.5.7.4.16': ('id-it-suppLangTags', ), - '1.3.6.1.5.5.7.5': ('id-pkip', ), - '1.3.6.1.5.5.7.5.1': ('id-regCtrl', ), - '1.3.6.1.5.5.7.5.1.1': ('id-regCtrl-regToken', ), - '1.3.6.1.5.5.7.5.1.2': ('id-regCtrl-authenticator', ), - '1.3.6.1.5.5.7.5.1.3': ('id-regCtrl-pkiPublicationInfo', ), - '1.3.6.1.5.5.7.5.1.4': ('id-regCtrl-pkiArchiveOptions', ), - '1.3.6.1.5.5.7.5.1.5': ('id-regCtrl-oldCertID', ), - '1.3.6.1.5.5.7.5.1.6': ('id-regCtrl-protocolEncrKey', ), - '1.3.6.1.5.5.7.5.2': ('id-regInfo', ), - '1.3.6.1.5.5.7.5.2.1': ('id-regInfo-utf8Pairs', ), - '1.3.6.1.5.5.7.5.2.2': ('id-regInfo-certReq', ), - '1.3.6.1.5.5.7.6': ('id-alg', ), - '1.3.6.1.5.5.7.6.1': ('id-alg-des40', ), - '1.3.6.1.5.5.7.6.2': ('id-alg-noSignature', ), - '1.3.6.1.5.5.7.6.3': ('id-alg-dh-sig-hmac-sha1', ), - '1.3.6.1.5.5.7.6.4': ('id-alg-dh-pop', ), - '1.3.6.1.5.5.7.7': ('id-cmc', ), - '1.3.6.1.5.5.7.7.1': ('id-cmc-statusInfo', ), - '1.3.6.1.5.5.7.7.2': ('id-cmc-identification', ), - '1.3.6.1.5.5.7.7.3': ('id-cmc-identityProof', ), - '1.3.6.1.5.5.7.7.4': ('id-cmc-dataReturn', ), - '1.3.6.1.5.5.7.7.5': ('id-cmc-transactionId', ), - '1.3.6.1.5.5.7.7.6': ('id-cmc-senderNonce', ), - '1.3.6.1.5.5.7.7.7': ('id-cmc-recipientNonce', ), - '1.3.6.1.5.5.7.7.8': ('id-cmc-addExtensions', ), - '1.3.6.1.5.5.7.7.9': ('id-cmc-encryptedPOP', ), - '1.3.6.1.5.5.7.7.10': ('id-cmc-decryptedPOP', ), - '1.3.6.1.5.5.7.7.11': ('id-cmc-lraPOPWitness', ), - '1.3.6.1.5.5.7.7.15': ('id-cmc-getCert', ), - '1.3.6.1.5.5.7.7.16': ('id-cmc-getCRL', ), - '1.3.6.1.5.5.7.7.17': ('id-cmc-revokeRequest', ), - '1.3.6.1.5.5.7.7.18': ('id-cmc-regInfo', ), - '1.3.6.1.5.5.7.7.19': ('id-cmc-responseInfo', ), - '1.3.6.1.5.5.7.7.21': ('id-cmc-queryPending', ), - '1.3.6.1.5.5.7.7.22': ('id-cmc-popLinkRandom', ), - '1.3.6.1.5.5.7.7.23': ('id-cmc-popLinkWitness', ), - '1.3.6.1.5.5.7.7.24': ('id-cmc-confirmCertAcceptance', ), - '1.3.6.1.5.5.7.8': ('id-on', ), - '1.3.6.1.5.5.7.8.1': ('id-on-personalData', ), - '1.3.6.1.5.5.7.8.3': ('Permanent Identifier', 'id-on-permanentIdentifier'), - '1.3.6.1.5.5.7.9': ('id-pda', ), - '1.3.6.1.5.5.7.9.1': ('id-pda-dateOfBirth', ), - '1.3.6.1.5.5.7.9.2': ('id-pda-placeOfBirth', ), - '1.3.6.1.5.5.7.9.3': ('id-pda-gender', ), - '1.3.6.1.5.5.7.9.4': ('id-pda-countryOfCitizenship', ), - '1.3.6.1.5.5.7.9.5': ('id-pda-countryOfResidence', ), - '1.3.6.1.5.5.7.10': ('id-aca', ), - '1.3.6.1.5.5.7.10.1': ('id-aca-authenticationInfo', ), - '1.3.6.1.5.5.7.10.2': ('id-aca-accessIdentity', ), - '1.3.6.1.5.5.7.10.3': ('id-aca-chargingIdentity', ), - '1.3.6.1.5.5.7.10.4': ('id-aca-group', ), - '1.3.6.1.5.5.7.10.5': ('id-aca-role', ), - '1.3.6.1.5.5.7.10.6': ('id-aca-encAttrs', ), - '1.3.6.1.5.5.7.11': ('id-qcs', ), - '1.3.6.1.5.5.7.11.1': ('id-qcs-pkixQCSyntax-v1', ), - '1.3.6.1.5.5.7.12': ('id-cct', ), - '1.3.6.1.5.5.7.12.1': ('id-cct-crs', ), - '1.3.6.1.5.5.7.12.2': ('id-cct-PKIData', ), - '1.3.6.1.5.5.7.12.3': ('id-cct-PKIResponse', ), - '1.3.6.1.5.5.7.21': ('id-ppl', ), - '1.3.6.1.5.5.7.21.0': ('Any language', 'id-ppl-anyLanguage'), - '1.3.6.1.5.5.7.21.1': ('Inherit all', 'id-ppl-inheritAll'), - '1.3.6.1.5.5.7.21.2': ('Independent', 'id-ppl-independent'), - '1.3.6.1.5.5.7.48': ('id-ad', ), - '1.3.6.1.5.5.7.48.1': ('OCSP', 'OCSP', 'id-pkix-OCSP'), - '1.3.6.1.5.5.7.48.1.1': ('Basic OCSP Response', 'basicOCSPResponse'), - '1.3.6.1.5.5.7.48.1.2': ('OCSP Nonce', 'Nonce'), - '1.3.6.1.5.5.7.48.1.3': ('OCSP CRL ID', 'CrlID'), - '1.3.6.1.5.5.7.48.1.4': ('Acceptable OCSP Responses', 'acceptableResponses'), - '1.3.6.1.5.5.7.48.1.5': ('OCSP No Check', 'noCheck'), - '1.3.6.1.5.5.7.48.1.6': ('OCSP Archive Cutoff', 'archiveCutoff'), - '1.3.6.1.5.5.7.48.1.7': ('OCSP Service Locator', 'serviceLocator'), - '1.3.6.1.5.5.7.48.1.8': ('Extended OCSP Status', 'extendedStatus'), - '1.3.6.1.5.5.7.48.1.9': ('valid', ), - '1.3.6.1.5.5.7.48.1.10': ('path', ), - '1.3.6.1.5.5.7.48.1.11': ('Trust Root', 'trustRoot'), - '1.3.6.1.5.5.7.48.2': ('CA Issuers', 'caIssuers'), - '1.3.6.1.5.5.7.48.3': ('AD Time Stamping', 'ad_timestamping'), - '1.3.6.1.5.5.7.48.4': ('ad dvcs', 'AD_DVCS'), - '1.3.6.1.5.5.7.48.5': ('CA Repository', 'caRepository'), - '1.3.6.1.5.5.8.1.1': ('hmac-md5', 'HMAC-MD5'), - '1.3.6.1.5.5.8.1.2': ('hmac-sha1', 'HMAC-SHA1'), - '1.3.6.1.6': ('SNMPv2', 'snmpv2'), - '1.3.6.1.7': ('Mail', ), - '1.3.6.1.7.1': ('MIME MHS', 'mime-mhs'), - '1.3.6.1.7.1.1': ('mime-mhs-headings', 'mime-mhs-headings'), - '1.3.6.1.7.1.1.1': ('id-hex-partial-message', 'id-hex-partial-message'), - '1.3.6.1.7.1.1.2': ('id-hex-multipart-message', 'id-hex-multipart-message'), - '1.3.6.1.7.1.2': ('mime-mhs-bodies', 'mime-mhs-bodies'), - '1.3.14.3.2': ('algorithm', 'algorithm'), - '1.3.14.3.2.3': ('md5WithRSA', 'RSA-NP-MD5'), - '1.3.14.3.2.6': ('des-ecb', 'DES-ECB'), - '1.3.14.3.2.7': ('des-cbc', 'DES-CBC'), - '1.3.14.3.2.8': ('des-ofb', 'DES-OFB'), - '1.3.14.3.2.9': ('des-cfb', 'DES-CFB'), - '1.3.14.3.2.11': ('rsaSignature', ), - '1.3.14.3.2.12': ('dsaEncryption-old', 'DSA-old'), - '1.3.14.3.2.13': ('dsaWithSHA', 'DSA-SHA'), - '1.3.14.3.2.15': ('shaWithRSAEncryption', 'RSA-SHA'), - '1.3.14.3.2.17': ('des-ede', 'DES-EDE'), - '1.3.14.3.2.18': ('sha', 'SHA'), - '1.3.14.3.2.26': ('sha1', 'SHA1'), - '1.3.14.3.2.27': ('dsaWithSHA1-old', 'DSA-SHA1-old'), - '1.3.14.3.2.29': ('sha1WithRSA', 'RSA-SHA1-2'), - '1.3.36.3.2.1': ('ripemd160', 'RIPEMD160'), - '1.3.36.3.3.1.2': ('ripemd160WithRSA', 'RSA-RIPEMD160'), - '1.3.36.3.3.2.8.1.1.1': ('brainpoolP160r1', ), - '1.3.36.3.3.2.8.1.1.2': ('brainpoolP160t1', ), - '1.3.36.3.3.2.8.1.1.3': ('brainpoolP192r1', ), - '1.3.36.3.3.2.8.1.1.4': ('brainpoolP192t1', ), - '1.3.36.3.3.2.8.1.1.5': ('brainpoolP224r1', ), - '1.3.36.3.3.2.8.1.1.6': ('brainpoolP224t1', ), - '1.3.36.3.3.2.8.1.1.7': ('brainpoolP256r1', ), - '1.3.36.3.3.2.8.1.1.8': ('brainpoolP256t1', ), - '1.3.36.3.3.2.8.1.1.9': ('brainpoolP320r1', ), - '1.3.36.3.3.2.8.1.1.10': ('brainpoolP320t1', ), - '1.3.36.3.3.2.8.1.1.11': ('brainpoolP384r1', ), - '1.3.36.3.3.2.8.1.1.12': ('brainpoolP384t1', ), - '1.3.36.3.3.2.8.1.1.13': ('brainpoolP512r1', ), - '1.3.36.3.3.2.8.1.1.14': ('brainpoolP512t1', ), - '1.3.36.8.3.3': ('Professional Information or basis for Admission', 'x509ExtAdmission'), - '1.3.101.1.4.1': ('Strong Extranet ID', 'SXNetID'), - '1.3.101.110': ('X25519', ), - '1.3.101.111': ('X448', ), - '1.3.101.112': ('ED25519', ), - '1.3.101.113': ('ED448', ), - '1.3.111': ('ieee', ), - '1.3.111.2.1619': ('IEEE Security in Storage Working Group', 'ieee-siswg'), - '1.3.111.2.1619.0.1.1': ('aes-128-xts', 'AES-128-XTS'), - '1.3.111.2.1619.0.1.2': ('aes-256-xts', 'AES-256-XTS'), - '1.3.132': ('certicom-arc', ), - '1.3.132.0': ('secg_ellipticCurve', ), - '1.3.132.0.1': ('sect163k1', ), - '1.3.132.0.2': ('sect163r1', ), - '1.3.132.0.3': ('sect239k1', ), - '1.3.132.0.4': ('sect113r1', ), - '1.3.132.0.5': ('sect113r2', ), - '1.3.132.0.6': ('secp112r1', ), - '1.3.132.0.7': ('secp112r2', ), - '1.3.132.0.8': ('secp160r1', ), - '1.3.132.0.9': ('secp160k1', ), - '1.3.132.0.10': ('secp256k1', ), - '1.3.132.0.15': ('sect163r2', ), - '1.3.132.0.16': ('sect283k1', ), - '1.3.132.0.17': ('sect283r1', ), - '1.3.132.0.22': ('sect131r1', ), - '1.3.132.0.23': ('sect131r2', ), - '1.3.132.0.24': ('sect193r1', ), - '1.3.132.0.25': ('sect193r2', ), - '1.3.132.0.26': ('sect233k1', ), - '1.3.132.0.27': ('sect233r1', ), - '1.3.132.0.28': ('secp128r1', ), - '1.3.132.0.29': ('secp128r2', ), - '1.3.132.0.30': ('secp160r2', ), - '1.3.132.0.31': ('secp192k1', ), - '1.3.132.0.32': ('secp224k1', ), - '1.3.132.0.33': ('secp224r1', ), - '1.3.132.0.34': ('secp384r1', ), - '1.3.132.0.35': ('secp521r1', ), - '1.3.132.0.36': ('sect409k1', ), - '1.3.132.0.37': ('sect409r1', ), - '1.3.132.0.38': ('sect571k1', ), - '1.3.132.0.39': ('sect571r1', ), - '1.3.132.1': ('secg-scheme', ), - '1.3.132.1.11.0': ('dhSinglePass-stdDH-sha224kdf-scheme', ), - '1.3.132.1.11.1': ('dhSinglePass-stdDH-sha256kdf-scheme', ), - '1.3.132.1.11.2': ('dhSinglePass-stdDH-sha384kdf-scheme', ), - '1.3.132.1.11.3': ('dhSinglePass-stdDH-sha512kdf-scheme', ), - '1.3.132.1.14.0': ('dhSinglePass-cofactorDH-sha224kdf-scheme', ), - '1.3.132.1.14.1': ('dhSinglePass-cofactorDH-sha256kdf-scheme', ), - '1.3.132.1.14.2': ('dhSinglePass-cofactorDH-sha384kdf-scheme', ), - '1.3.132.1.14.3': ('dhSinglePass-cofactorDH-sha512kdf-scheme', ), - '1.3.133.16.840.63.0': ('x9-63-scheme', ), - '1.3.133.16.840.63.0.2': ('dhSinglePass-stdDH-sha1kdf-scheme', ), - '1.3.133.16.840.63.0.3': ('dhSinglePass-cofactorDH-sha1kdf-scheme', ), - '2': ('joint-iso-itu-t', 'JOINT-ISO-ITU-T', 'joint-iso-ccitt'), - '2.5': ('directory services (X.500)', 'X500'), - '2.5.1.5': ('Selected Attribute Types', 'selected-attribute-types'), - '2.5.1.5.55': ('clearance', ), - '2.5.4': ('X509', ), - '2.5.4.3': ('commonName', 'CN'), - '2.5.4.4': ('surname', 'SN'), - '2.5.4.5': ('serialNumber', ), - '2.5.4.6': ('countryName', 'C'), - '2.5.4.7': ('localityName', 'L'), - '2.5.4.8': ('stateOrProvinceName', 'ST'), - '2.5.4.9': ('streetAddress', 'street'), - '2.5.4.10': ('organizationName', 'O'), - '2.5.4.11': ('organizationalUnitName', 'OU'), - '2.5.4.12': ('title', 'title'), - '2.5.4.13': ('description', ), - '2.5.4.14': ('searchGuide', ), - '2.5.4.15': ('businessCategory', ), - '2.5.4.16': ('postalAddress', ), - '2.5.4.17': ('postalCode', ), - '2.5.4.18': ('postOfficeBox', ), - '2.5.4.19': ('physicalDeliveryOfficeName', ), - '2.5.4.20': ('telephoneNumber', ), - '2.5.4.21': ('telexNumber', ), - '2.5.4.22': ('teletexTerminalIdentifier', ), - '2.5.4.23': ('facsimileTelephoneNumber', ), - '2.5.4.24': ('x121Address', ), - '2.5.4.25': ('internationaliSDNNumber', ), - '2.5.4.26': ('registeredAddress', ), - '2.5.4.27': ('destinationIndicator', ), - '2.5.4.28': ('preferredDeliveryMethod', ), - '2.5.4.29': ('presentationAddress', ), - '2.5.4.30': ('supportedApplicationContext', ), - '2.5.4.31': ('member', ), - '2.5.4.32': ('owner', ), - '2.5.4.33': ('roleOccupant', ), - '2.5.4.34': ('seeAlso', ), - '2.5.4.35': ('userPassword', ), - '2.5.4.36': ('userCertificate', ), - '2.5.4.37': ('cACertificate', ), - '2.5.4.38': ('authorityRevocationList', ), - '2.5.4.39': ('certificateRevocationList', ), - '2.5.4.40': ('crossCertificatePair', ), - '2.5.4.41': ('name', 'name'), - '2.5.4.42': ('givenName', 'GN'), - '2.5.4.43': ('initials', 'initials'), - '2.5.4.44': ('generationQualifier', ), - '2.5.4.45': ('x500UniqueIdentifier', ), - '2.5.4.46': ('dnQualifier', 'dnQualifier'), - '2.5.4.47': ('enhancedSearchGuide', ), - '2.5.4.48': ('protocolInformation', ), - '2.5.4.49': ('distinguishedName', ), - '2.5.4.50': ('uniqueMember', ), - '2.5.4.51': ('houseIdentifier', ), - '2.5.4.52': ('supportedAlgorithms', ), - '2.5.4.53': ('deltaRevocationList', ), - '2.5.4.54': ('dmdName', ), - '2.5.4.65': ('pseudonym', ), - '2.5.4.72': ('role', 'role'), - '2.5.4.97': ('organizationIdentifier', ), - '2.5.4.98': ('countryCode3c', 'c3'), - '2.5.4.99': ('countryCode3n', 'n3'), - '2.5.4.100': ('dnsName', ), - '2.5.8': ('directory services - algorithms', 'X500algorithms'), - '2.5.8.1.1': ('rsa', 'RSA'), - '2.5.8.3.100': ('mdc2WithRSA', 'RSA-MDC2'), - '2.5.8.3.101': ('mdc2', 'MDC2'), - '2.5.29': ('id-ce', ), - '2.5.29.9': ('X509v3 Subject Directory Attributes', 'subjectDirectoryAttributes'), - '2.5.29.14': ('X509v3 Subject Key Identifier', 'subjectKeyIdentifier'), - '2.5.29.15': ('X509v3 Key Usage', 'keyUsage'), - '2.5.29.16': ('X509v3 Private Key Usage Period', 'privateKeyUsagePeriod'), - '2.5.29.17': ('X509v3 Subject Alternative Name', 'subjectAltName'), - '2.5.29.18': ('X509v3 Issuer Alternative Name', 'issuerAltName'), - '2.5.29.19': ('X509v3 Basic Constraints', 'basicConstraints'), - '2.5.29.20': ('X509v3 CRL Number', 'crlNumber'), - '2.5.29.21': ('X509v3 CRL Reason Code', 'CRLReason'), - '2.5.29.23': ('Hold Instruction Code', 'holdInstructionCode'), - '2.5.29.24': ('Invalidity Date', 'invalidityDate'), - '2.5.29.27': ('X509v3 Delta CRL Indicator', 'deltaCRL'), - '2.5.29.28': ('X509v3 Issuing Distribution Point', 'issuingDistributionPoint'), - '2.5.29.29': ('X509v3 Certificate Issuer', 'certificateIssuer'), - '2.5.29.30': ('X509v3 Name Constraints', 'nameConstraints'), - '2.5.29.31': ('X509v3 CRL Distribution Points', 'crlDistributionPoints'), - '2.5.29.32': ('X509v3 Certificate Policies', 'certificatePolicies'), - '2.5.29.32.0': ('X509v3 Any Policy', 'anyPolicy'), - '2.5.29.33': ('X509v3 Policy Mappings', 'policyMappings'), - '2.5.29.35': ('X509v3 Authority Key Identifier', 'authorityKeyIdentifier'), - '2.5.29.36': ('X509v3 Policy Constraints', 'policyConstraints'), - '2.5.29.37': ('X509v3 Extended Key Usage', 'extendedKeyUsage'), - '2.5.29.37.0': ('Any Extended Key Usage', 'anyExtendedKeyUsage'), - '2.5.29.46': ('X509v3 Freshest CRL', 'freshestCRL'), - '2.5.29.54': ('X509v3 Inhibit Any Policy', 'inhibitAnyPolicy'), - '2.5.29.55': ('X509v3 AC Targeting', 'targetInformation'), - '2.5.29.56': ('X509v3 No Revocation Available', 'noRevAvail'), - '2.16.840.1.101.3': ('csor', ), - '2.16.840.1.101.3.4': ('nistAlgorithms', ), - '2.16.840.1.101.3.4.1': ('aes', ), - '2.16.840.1.101.3.4.1.1': ('aes-128-ecb', 'AES-128-ECB'), - '2.16.840.1.101.3.4.1.2': ('aes-128-cbc', 'AES-128-CBC'), - '2.16.840.1.101.3.4.1.3': ('aes-128-ofb', 'AES-128-OFB'), - '2.16.840.1.101.3.4.1.4': ('aes-128-cfb', 'AES-128-CFB'), - '2.16.840.1.101.3.4.1.5': ('id-aes128-wrap', ), - '2.16.840.1.101.3.4.1.6': ('aes-128-gcm', 'id-aes128-GCM'), - '2.16.840.1.101.3.4.1.7': ('aes-128-ccm', 'id-aes128-CCM'), - '2.16.840.1.101.3.4.1.8': ('id-aes128-wrap-pad', ), - '2.16.840.1.101.3.4.1.21': ('aes-192-ecb', 'AES-192-ECB'), - '2.16.840.1.101.3.4.1.22': ('aes-192-cbc', 'AES-192-CBC'), - '2.16.840.1.101.3.4.1.23': ('aes-192-ofb', 'AES-192-OFB'), - '2.16.840.1.101.3.4.1.24': ('aes-192-cfb', 'AES-192-CFB'), - '2.16.840.1.101.3.4.1.25': ('id-aes192-wrap', ), - '2.16.840.1.101.3.4.1.26': ('aes-192-gcm', 'id-aes192-GCM'), - '2.16.840.1.101.3.4.1.27': ('aes-192-ccm', 'id-aes192-CCM'), - '2.16.840.1.101.3.4.1.28': ('id-aes192-wrap-pad', ), - '2.16.840.1.101.3.4.1.41': ('aes-256-ecb', 'AES-256-ECB'), - '2.16.840.1.101.3.4.1.42': ('aes-256-cbc', 'AES-256-CBC'), - '2.16.840.1.101.3.4.1.43': ('aes-256-ofb', 'AES-256-OFB'), - '2.16.840.1.101.3.4.1.44': ('aes-256-cfb', 'AES-256-CFB'), - '2.16.840.1.101.3.4.1.45': ('id-aes256-wrap', ), - '2.16.840.1.101.3.4.1.46': ('aes-256-gcm', 'id-aes256-GCM'), - '2.16.840.1.101.3.4.1.47': ('aes-256-ccm', 'id-aes256-CCM'), - '2.16.840.1.101.3.4.1.48': ('id-aes256-wrap-pad', ), - '2.16.840.1.101.3.4.2': ('nist_hashalgs', ), - '2.16.840.1.101.3.4.2.1': ('sha256', 'SHA256'), - '2.16.840.1.101.3.4.2.2': ('sha384', 'SHA384'), - '2.16.840.1.101.3.4.2.3': ('sha512', 'SHA512'), - '2.16.840.1.101.3.4.2.4': ('sha224', 'SHA224'), - '2.16.840.1.101.3.4.2.5': ('sha512-224', 'SHA512-224'), - '2.16.840.1.101.3.4.2.6': ('sha512-256', 'SHA512-256'), - '2.16.840.1.101.3.4.2.7': ('sha3-224', 'SHA3-224'), - '2.16.840.1.101.3.4.2.8': ('sha3-256', 'SHA3-256'), - '2.16.840.1.101.3.4.2.9': ('sha3-384', 'SHA3-384'), - '2.16.840.1.101.3.4.2.10': ('sha3-512', 'SHA3-512'), - '2.16.840.1.101.3.4.2.11': ('shake128', 'SHAKE128'), - '2.16.840.1.101.3.4.2.12': ('shake256', 'SHAKE256'), - '2.16.840.1.101.3.4.2.13': ('hmac-sha3-224', 'id-hmacWithSHA3-224'), - '2.16.840.1.101.3.4.2.14': ('hmac-sha3-256', 'id-hmacWithSHA3-256'), - '2.16.840.1.101.3.4.2.15': ('hmac-sha3-384', 'id-hmacWithSHA3-384'), - '2.16.840.1.101.3.4.2.16': ('hmac-sha3-512', 'id-hmacWithSHA3-512'), - '2.16.840.1.101.3.4.3': ('dsa_with_sha2', 'sigAlgs'), - '2.16.840.1.101.3.4.3.1': ('dsa_with_SHA224', ), - '2.16.840.1.101.3.4.3.2': ('dsa_with_SHA256', ), - '2.16.840.1.101.3.4.3.3': ('dsa_with_SHA384', 'id-dsa-with-sha384'), - '2.16.840.1.101.3.4.3.4': ('dsa_with_SHA512', 'id-dsa-with-sha512'), - '2.16.840.1.101.3.4.3.5': ('dsa_with_SHA3-224', 'id-dsa-with-sha3-224'), - '2.16.840.1.101.3.4.3.6': ('dsa_with_SHA3-256', 'id-dsa-with-sha3-256'), - '2.16.840.1.101.3.4.3.7': ('dsa_with_SHA3-384', 'id-dsa-with-sha3-384'), - '2.16.840.1.101.3.4.3.8': ('dsa_with_SHA3-512', 'id-dsa-with-sha3-512'), - '2.16.840.1.101.3.4.3.9': ('ecdsa_with_SHA3-224', 'id-ecdsa-with-sha3-224'), - '2.16.840.1.101.3.4.3.10': ('ecdsa_with_SHA3-256', 'id-ecdsa-with-sha3-256'), - '2.16.840.1.101.3.4.3.11': ('ecdsa_with_SHA3-384', 'id-ecdsa-with-sha3-384'), - '2.16.840.1.101.3.4.3.12': ('ecdsa_with_SHA3-512', 'id-ecdsa-with-sha3-512'), - '2.16.840.1.101.3.4.3.13': ('RSA-SHA3-224', 'id-rsassa-pkcs1-v1_5-with-sha3-224'), - '2.16.840.1.101.3.4.3.14': ('RSA-SHA3-256', 'id-rsassa-pkcs1-v1_5-with-sha3-256'), - '2.16.840.1.101.3.4.3.15': ('RSA-SHA3-384', 'id-rsassa-pkcs1-v1_5-with-sha3-384'), - '2.16.840.1.101.3.4.3.16': ('RSA-SHA3-512', 'id-rsassa-pkcs1-v1_5-with-sha3-512'), - '2.16.840.1.113730': ('Netscape Communications Corp.', 'Netscape'), - '2.16.840.1.113730.1': ('Netscape Certificate Extension', 'nsCertExt'), - '2.16.840.1.113730.1.1': ('Netscape Cert Type', 'nsCertType'), - '2.16.840.1.113730.1.2': ('Netscape Base Url', 'nsBaseUrl'), - '2.16.840.1.113730.1.3': ('Netscape Revocation Url', 'nsRevocationUrl'), - '2.16.840.1.113730.1.4': ('Netscape CA Revocation Url', 'nsCaRevocationUrl'), - '2.16.840.1.113730.1.7': ('Netscape Renewal Url', 'nsRenewalUrl'), - '2.16.840.1.113730.1.8': ('Netscape CA Policy Url', 'nsCaPolicyUrl'), - '2.16.840.1.113730.1.12': ('Netscape SSL Server Name', 'nsSslServerName'), - '2.16.840.1.113730.1.13': ('Netscape Comment', 'nsComment'), - '2.16.840.1.113730.2': ('Netscape Data Type', 'nsDataType'), - '2.16.840.1.113730.2.5': ('Netscape Certificate Sequence', 'nsCertSequence'), - '2.16.840.1.113730.4.1': ('Netscape Server Gated Crypto', 'nsSGC'), - '2.23': ('International Organizations', 'international-organizations'), - '2.23.42': ('Secure Electronic Transactions', 'id-set'), - '2.23.42.0': ('content types', 'set-ctype'), - '2.23.42.0.0': ('setct-PANData', ), - '2.23.42.0.1': ('setct-PANToken', ), - '2.23.42.0.2': ('setct-PANOnly', ), - '2.23.42.0.3': ('setct-OIData', ), - '2.23.42.0.4': ('setct-PI', ), - '2.23.42.0.5': ('setct-PIData', ), - '2.23.42.0.6': ('setct-PIDataUnsigned', ), - '2.23.42.0.7': ('setct-HODInput', ), - '2.23.42.0.8': ('setct-AuthResBaggage', ), - '2.23.42.0.9': ('setct-AuthRevReqBaggage', ), - '2.23.42.0.10': ('setct-AuthRevResBaggage', ), - '2.23.42.0.11': ('setct-CapTokenSeq', ), - '2.23.42.0.12': ('setct-PInitResData', ), - '2.23.42.0.13': ('setct-PI-TBS', ), - '2.23.42.0.14': ('setct-PResData', ), - '2.23.42.0.16': ('setct-AuthReqTBS', ), - '2.23.42.0.17': ('setct-AuthResTBS', ), - '2.23.42.0.18': ('setct-AuthResTBSX', ), - '2.23.42.0.19': ('setct-AuthTokenTBS', ), - '2.23.42.0.20': ('setct-CapTokenData', ), - '2.23.42.0.21': ('setct-CapTokenTBS', ), - '2.23.42.0.22': ('setct-AcqCardCodeMsg', ), - '2.23.42.0.23': ('setct-AuthRevReqTBS', ), - '2.23.42.0.24': ('setct-AuthRevResData', ), - '2.23.42.0.25': ('setct-AuthRevResTBS', ), - '2.23.42.0.26': ('setct-CapReqTBS', ), - '2.23.42.0.27': ('setct-CapReqTBSX', ), - '2.23.42.0.28': ('setct-CapResData', ), - '2.23.42.0.29': ('setct-CapRevReqTBS', ), - '2.23.42.0.30': ('setct-CapRevReqTBSX', ), - '2.23.42.0.31': ('setct-CapRevResData', ), - '2.23.42.0.32': ('setct-CredReqTBS', ), - '2.23.42.0.33': ('setct-CredReqTBSX', ), - '2.23.42.0.34': ('setct-CredResData', ), - '2.23.42.0.35': ('setct-CredRevReqTBS', ), - '2.23.42.0.36': ('setct-CredRevReqTBSX', ), - '2.23.42.0.37': ('setct-CredRevResData', ), - '2.23.42.0.38': ('setct-PCertReqData', ), - '2.23.42.0.39': ('setct-PCertResTBS', ), - '2.23.42.0.40': ('setct-BatchAdminReqData', ), - '2.23.42.0.41': ('setct-BatchAdminResData', ), - '2.23.42.0.42': ('setct-CardCInitResTBS', ), - '2.23.42.0.43': ('setct-MeAqCInitResTBS', ), - '2.23.42.0.44': ('setct-RegFormResTBS', ), - '2.23.42.0.45': ('setct-CertReqData', ), - '2.23.42.0.46': ('setct-CertReqTBS', ), - '2.23.42.0.47': ('setct-CertResData', ), - '2.23.42.0.48': ('setct-CertInqReqTBS', ), - '2.23.42.0.49': ('setct-ErrorTBS', ), - '2.23.42.0.50': ('setct-PIDualSignedTBE', ), - '2.23.42.0.51': ('setct-PIUnsignedTBE', ), - '2.23.42.0.52': ('setct-AuthReqTBE', ), - '2.23.42.0.53': ('setct-AuthResTBE', ), - '2.23.42.0.54': ('setct-AuthResTBEX', ), - '2.23.42.0.55': ('setct-AuthTokenTBE', ), - '2.23.42.0.56': ('setct-CapTokenTBE', ), - '2.23.42.0.57': ('setct-CapTokenTBEX', ), - '2.23.42.0.58': ('setct-AcqCardCodeMsgTBE', ), - '2.23.42.0.59': ('setct-AuthRevReqTBE', ), - '2.23.42.0.60': ('setct-AuthRevResTBE', ), - '2.23.42.0.61': ('setct-AuthRevResTBEB', ), - '2.23.42.0.62': ('setct-CapReqTBE', ), - '2.23.42.0.63': ('setct-CapReqTBEX', ), - '2.23.42.0.64': ('setct-CapResTBE', ), - '2.23.42.0.65': ('setct-CapRevReqTBE', ), - '2.23.42.0.66': ('setct-CapRevReqTBEX', ), - '2.23.42.0.67': ('setct-CapRevResTBE', ), - '2.23.42.0.68': ('setct-CredReqTBE', ), - '2.23.42.0.69': ('setct-CredReqTBEX', ), - '2.23.42.0.70': ('setct-CredResTBE', ), - '2.23.42.0.71': ('setct-CredRevReqTBE', ), - '2.23.42.0.72': ('setct-CredRevReqTBEX', ), - '2.23.42.0.73': ('setct-CredRevResTBE', ), - '2.23.42.0.74': ('setct-BatchAdminReqTBE', ), - '2.23.42.0.75': ('setct-BatchAdminResTBE', ), - '2.23.42.0.76': ('setct-RegFormReqTBE', ), - '2.23.42.0.77': ('setct-CertReqTBE', ), - '2.23.42.0.78': ('setct-CertReqTBEX', ), - '2.23.42.0.79': ('setct-CertResTBE', ), - '2.23.42.0.80': ('setct-CRLNotificationTBS', ), - '2.23.42.0.81': ('setct-CRLNotificationResTBS', ), - '2.23.42.0.82': ('setct-BCIDistributionTBS', ), - '2.23.42.1': ('message extensions', 'set-msgExt'), - '2.23.42.1.1': ('generic cryptogram', 'setext-genCrypt'), - '2.23.42.1.3': ('merchant initiated auth', 'setext-miAuth'), - '2.23.42.1.4': ('setext-pinSecure', ), - '2.23.42.1.5': ('setext-pinAny', ), - '2.23.42.1.7': ('setext-track2', ), - '2.23.42.1.8': ('additional verification', 'setext-cv'), - '2.23.42.3': ('set-attr', ), - '2.23.42.3.0': ('setAttr-Cert', ), - '2.23.42.3.0.0': ('set-rootKeyThumb', ), - '2.23.42.3.0.1': ('set-addPolicy', ), - '2.23.42.3.1': ('payment gateway capabilities', 'setAttr-PGWYcap'), - '2.23.42.3.2': ('setAttr-TokenType', ), - '2.23.42.3.2.1': ('setAttr-Token-EMV', ), - '2.23.42.3.2.2': ('setAttr-Token-B0Prime', ), - '2.23.42.3.3': ('issuer capabilities', 'setAttr-IssCap'), - '2.23.42.3.3.3': ('setAttr-IssCap-CVM', ), - '2.23.42.3.3.3.1': ('generate cryptogram', 'setAttr-GenCryptgrm'), - '2.23.42.3.3.4': ('setAttr-IssCap-T2', ), - '2.23.42.3.3.4.1': ('encrypted track 2', 'setAttr-T2Enc'), - '2.23.42.3.3.4.2': ('cleartext track 2', 'setAttr-T2cleartxt'), - '2.23.42.3.3.5': ('setAttr-IssCap-Sig', ), - '2.23.42.3.3.5.1': ('ICC or token signature', 'setAttr-TokICCsig'), - '2.23.42.3.3.5.2': ('secure device signature', 'setAttr-SecDevSig'), - '2.23.42.5': ('set-policy', ), - '2.23.42.5.0': ('set-policy-root', ), - '2.23.42.7': ('certificate extensions', 'set-certExt'), - '2.23.42.7.0': ('setCext-hashedRoot', ), - '2.23.42.7.1': ('setCext-certType', ), - '2.23.42.7.2': ('setCext-merchData', ), - '2.23.42.7.3': ('setCext-cCertRequired', ), - '2.23.42.7.4': ('setCext-tunneling', ), - '2.23.42.7.5': ('setCext-setExt', ), - '2.23.42.7.6': ('setCext-setQualf', ), - '2.23.42.7.7': ('setCext-PGWYcapabilities', ), - '2.23.42.7.8': ('setCext-TokenIdentifier', ), - '2.23.42.7.9': ('setCext-Track2Data', ), - '2.23.42.7.10': ('setCext-TokenType', ), - '2.23.42.7.11': ('setCext-IssuerCapabilities', ), - '2.23.42.8': ('set-brand', ), - '2.23.42.8.1': ('set-brand-IATA-ATA', ), - '2.23.42.8.4': ('set-brand-Visa', ), - '2.23.42.8.5': ('set-brand-MasterCard', ), - '2.23.42.8.30': ('set-brand-Diners', ), - '2.23.42.8.34': ('set-brand-AmericanExpress', ), - '2.23.42.8.35': ('set-brand-JCB', ), - '2.23.42.8.6011': ('set-brand-Novus', ), - '2.23.43': ('wap', ), - '2.23.43.1': ('wap-wsg', ), - '2.23.43.1.4': ('wap-wsg-idm-ecid', ), - '2.23.43.1.4.1': ('wap-wsg-idm-ecid-wtls1', ), - '2.23.43.1.4.3': ('wap-wsg-idm-ecid-wtls3', ), - '2.23.43.1.4.4': ('wap-wsg-idm-ecid-wtls4', ), - '2.23.43.1.4.5': ('wap-wsg-idm-ecid-wtls5', ), - '2.23.43.1.4.6': ('wap-wsg-idm-ecid-wtls6', ), - '2.23.43.1.4.7': ('wap-wsg-idm-ecid-wtls7', ), - '2.23.43.1.4.8': ('wap-wsg-idm-ecid-wtls8', ), - '2.23.43.1.4.9': ('wap-wsg-idm-ecid-wtls9', ), - '2.23.43.1.4.10': ('wap-wsg-idm-ecid-wtls10', ), - '2.23.43.1.4.11': ('wap-wsg-idm-ecid-wtls11', ), - '2.23.43.1.4.12': ('wap-wsg-idm-ecid-wtls12', ), + "0": ("itu-t", "ITU-T", "ccitt"), + "0.3.4401.5": ("ntt-ds",), + "0.3.4401.5.3.1.9": ("camellia",), + "0.3.4401.5.3.1.9.1": ("camellia-128-ecb", "CAMELLIA-128-ECB"), + "0.3.4401.5.3.1.9.3": ("camellia-128-ofb", "CAMELLIA-128-OFB"), + "0.3.4401.5.3.1.9.4": ("camellia-128-cfb", "CAMELLIA-128-CFB"), + "0.3.4401.5.3.1.9.6": ("camellia-128-gcm", "CAMELLIA-128-GCM"), + "0.3.4401.5.3.1.9.7": ("camellia-128-ccm", "CAMELLIA-128-CCM"), + "0.3.4401.5.3.1.9.9": ("camellia-128-ctr", "CAMELLIA-128-CTR"), + "0.3.4401.5.3.1.9.10": ("camellia-128-cmac", "CAMELLIA-128-CMAC"), + "0.3.4401.5.3.1.9.21": ("camellia-192-ecb", "CAMELLIA-192-ECB"), + "0.3.4401.5.3.1.9.23": ("camellia-192-ofb", "CAMELLIA-192-OFB"), + "0.3.4401.5.3.1.9.24": ("camellia-192-cfb", "CAMELLIA-192-CFB"), + "0.3.4401.5.3.1.9.26": ("camellia-192-gcm", "CAMELLIA-192-GCM"), + "0.3.4401.5.3.1.9.27": ("camellia-192-ccm", "CAMELLIA-192-CCM"), + "0.3.4401.5.3.1.9.29": ("camellia-192-ctr", "CAMELLIA-192-CTR"), + "0.3.4401.5.3.1.9.30": ("camellia-192-cmac", "CAMELLIA-192-CMAC"), + "0.3.4401.5.3.1.9.41": ("camellia-256-ecb", "CAMELLIA-256-ECB"), + "0.3.4401.5.3.1.9.43": ("camellia-256-ofb", "CAMELLIA-256-OFB"), + "0.3.4401.5.3.1.9.44": ("camellia-256-cfb", "CAMELLIA-256-CFB"), + "0.3.4401.5.3.1.9.46": ("camellia-256-gcm", "CAMELLIA-256-GCM"), + "0.3.4401.5.3.1.9.47": ("camellia-256-ccm", "CAMELLIA-256-CCM"), + "0.3.4401.5.3.1.9.49": ("camellia-256-ctr", "CAMELLIA-256-CTR"), + "0.3.4401.5.3.1.9.50": ("camellia-256-cmac", "CAMELLIA-256-CMAC"), + "0.9": ("data",), + "0.9.2342": ("pss",), + "0.9.2342.19200300": ("ucl",), + "0.9.2342.19200300.100": ("pilot",), + "0.9.2342.19200300.100.1": ("pilotAttributeType",), + "0.9.2342.19200300.100.1.1": ("userId", "UID"), + "0.9.2342.19200300.100.1.2": ("textEncodedORAddress",), + "0.9.2342.19200300.100.1.3": ("rfc822Mailbox", "mail"), + "0.9.2342.19200300.100.1.4": ("info",), + "0.9.2342.19200300.100.1.5": ("favouriteDrink",), + "0.9.2342.19200300.100.1.6": ("roomNumber",), + "0.9.2342.19200300.100.1.7": ("photo",), + "0.9.2342.19200300.100.1.8": ("userClass",), + "0.9.2342.19200300.100.1.9": ("host",), + "0.9.2342.19200300.100.1.10": ("manager",), + "0.9.2342.19200300.100.1.11": ("documentIdentifier",), + "0.9.2342.19200300.100.1.12": ("documentTitle",), + "0.9.2342.19200300.100.1.13": ("documentVersion",), + "0.9.2342.19200300.100.1.14": ("documentAuthor",), + "0.9.2342.19200300.100.1.15": ("documentLocation",), + "0.9.2342.19200300.100.1.20": ("homeTelephoneNumber",), + "0.9.2342.19200300.100.1.21": ("secretary",), + "0.9.2342.19200300.100.1.22": ("otherMailbox",), + "0.9.2342.19200300.100.1.23": ("lastModifiedTime",), + "0.9.2342.19200300.100.1.24": ("lastModifiedBy",), + "0.9.2342.19200300.100.1.25": ("domainComponent", "DC"), + "0.9.2342.19200300.100.1.26": ("aRecord",), + "0.9.2342.19200300.100.1.27": ("pilotAttributeType27",), + "0.9.2342.19200300.100.1.28": ("mXRecord",), + "0.9.2342.19200300.100.1.29": ("nSRecord",), + "0.9.2342.19200300.100.1.30": ("sOARecord",), + "0.9.2342.19200300.100.1.31": ("cNAMERecord",), + "0.9.2342.19200300.100.1.37": ("associatedDomain",), + "0.9.2342.19200300.100.1.38": ("associatedName",), + "0.9.2342.19200300.100.1.39": ("homePostalAddress",), + "0.9.2342.19200300.100.1.40": ("personalTitle",), + "0.9.2342.19200300.100.1.41": ("mobileTelephoneNumber",), + "0.9.2342.19200300.100.1.42": ("pagerTelephoneNumber",), + "0.9.2342.19200300.100.1.43": ("friendlyCountryName",), + "0.9.2342.19200300.100.1.44": ("uniqueIdentifier", "uid"), + "0.9.2342.19200300.100.1.45": ("organizationalStatus",), + "0.9.2342.19200300.100.1.46": ("janetMailbox",), + "0.9.2342.19200300.100.1.47": ("mailPreferenceOption",), + "0.9.2342.19200300.100.1.48": ("buildingName",), + "0.9.2342.19200300.100.1.49": ("dSAQuality",), + "0.9.2342.19200300.100.1.50": ("singleLevelQuality",), + "0.9.2342.19200300.100.1.51": ("subtreeMinimumQuality",), + "0.9.2342.19200300.100.1.52": ("subtreeMaximumQuality",), + "0.9.2342.19200300.100.1.53": ("personalSignature",), + "0.9.2342.19200300.100.1.54": ("dITRedirect",), + "0.9.2342.19200300.100.1.55": ("audio",), + "0.9.2342.19200300.100.1.56": ("documentPublisher",), + "0.9.2342.19200300.100.3": ("pilotAttributeSyntax",), + "0.9.2342.19200300.100.3.4": ("iA5StringSyntax",), + "0.9.2342.19200300.100.3.5": ("caseIgnoreIA5StringSyntax",), + "0.9.2342.19200300.100.4": ("pilotObjectClass",), + "0.9.2342.19200300.100.4.3": ("pilotObject",), + "0.9.2342.19200300.100.4.4": ("pilotPerson",), + "0.9.2342.19200300.100.4.5": ("account",), + "0.9.2342.19200300.100.4.6": ("document",), + "0.9.2342.19200300.100.4.7": ("room",), + "0.9.2342.19200300.100.4.9": ("documentSeries",), + "0.9.2342.19200300.100.4.13": ("Domain", "domain"), + "0.9.2342.19200300.100.4.14": ("rFC822localPart",), + "0.9.2342.19200300.100.4.15": ("dNSDomain",), + "0.9.2342.19200300.100.4.17": ("domainRelatedObject",), + "0.9.2342.19200300.100.4.18": ("friendlyCountry",), + "0.9.2342.19200300.100.4.19": ("simpleSecurityObject",), + "0.9.2342.19200300.100.4.20": ("pilotOrganization",), + "0.9.2342.19200300.100.4.21": ("pilotDSA",), + "0.9.2342.19200300.100.4.22": ("qualityLabelledData",), + "0.9.2342.19200300.100.10": ("pilotGroups",), + "1": ("iso", "ISO"), + "1.0.9797.3.4": ("gmac", "GMAC"), + "1.0.10118.3.0.55": ("whirlpool",), + "1.2": ("ISO Member Body", "member-body"), + "1.2.156": ("ISO CN Member Body", "ISO-CN"), + "1.2.156.10197": ("oscca",), + "1.2.156.10197.1": ("sm-scheme",), + "1.2.156.10197.1.104.1": ("sm4-ecb", "SM4-ECB"), + "1.2.156.10197.1.104.2": ("sm4-cbc", "SM4-CBC"), + "1.2.156.10197.1.104.3": ("sm4-ofb", "SM4-OFB"), + "1.2.156.10197.1.104.4": ("sm4-cfb", "SM4-CFB"), + "1.2.156.10197.1.104.5": ("sm4-cfb1", "SM4-CFB1"), + "1.2.156.10197.1.104.6": ("sm4-cfb8", "SM4-CFB8"), + "1.2.156.10197.1.104.7": ("sm4-ctr", "SM4-CTR"), + "1.2.156.10197.1.301": ("sm2", "SM2"), + "1.2.156.10197.1.401": ("sm3", "SM3"), + "1.2.156.10197.1.501": ("SM2-with-SM3", "SM2-SM3"), + "1.2.156.10197.1.504": ("sm3WithRSAEncryption", "RSA-SM3"), + "1.2.392.200011.61.1.1.1.2": ("camellia-128-cbc", "CAMELLIA-128-CBC"), + "1.2.392.200011.61.1.1.1.3": ("camellia-192-cbc", "CAMELLIA-192-CBC"), + "1.2.392.200011.61.1.1.1.4": ("camellia-256-cbc", "CAMELLIA-256-CBC"), + "1.2.392.200011.61.1.1.3.2": ("id-camellia128-wrap",), + "1.2.392.200011.61.1.1.3.3": ("id-camellia192-wrap",), + "1.2.392.200011.61.1.1.3.4": ("id-camellia256-wrap",), + "1.2.410.200004": ("kisa", "KISA"), + "1.2.410.200004.1.3": ("seed-ecb", "SEED-ECB"), + "1.2.410.200004.1.4": ("seed-cbc", "SEED-CBC"), + "1.2.410.200004.1.5": ("seed-cfb", "SEED-CFB"), + "1.2.410.200004.1.6": ("seed-ofb", "SEED-OFB"), + "1.2.410.200046.1.1": ("aria",), + "1.2.410.200046.1.1.1": ("aria-128-ecb", "ARIA-128-ECB"), + "1.2.410.200046.1.1.2": ("aria-128-cbc", "ARIA-128-CBC"), + "1.2.410.200046.1.1.3": ("aria-128-cfb", "ARIA-128-CFB"), + "1.2.410.200046.1.1.4": ("aria-128-ofb", "ARIA-128-OFB"), + "1.2.410.200046.1.1.5": ("aria-128-ctr", "ARIA-128-CTR"), + "1.2.410.200046.1.1.6": ("aria-192-ecb", "ARIA-192-ECB"), + "1.2.410.200046.1.1.7": ("aria-192-cbc", "ARIA-192-CBC"), + "1.2.410.200046.1.1.8": ("aria-192-cfb", "ARIA-192-CFB"), + "1.2.410.200046.1.1.9": ("aria-192-ofb", "ARIA-192-OFB"), + "1.2.410.200046.1.1.10": ("aria-192-ctr", "ARIA-192-CTR"), + "1.2.410.200046.1.1.11": ("aria-256-ecb", "ARIA-256-ECB"), + "1.2.410.200046.1.1.12": ("aria-256-cbc", "ARIA-256-CBC"), + "1.2.410.200046.1.1.13": ("aria-256-cfb", "ARIA-256-CFB"), + "1.2.410.200046.1.1.14": ("aria-256-ofb", "ARIA-256-OFB"), + "1.2.410.200046.1.1.15": ("aria-256-ctr", "ARIA-256-CTR"), + "1.2.410.200046.1.1.34": ("aria-128-gcm", "ARIA-128-GCM"), + "1.2.410.200046.1.1.35": ("aria-192-gcm", "ARIA-192-GCM"), + "1.2.410.200046.1.1.36": ("aria-256-gcm", "ARIA-256-GCM"), + "1.2.410.200046.1.1.37": ("aria-128-ccm", "ARIA-128-CCM"), + "1.2.410.200046.1.1.38": ("aria-192-ccm", "ARIA-192-CCM"), + "1.2.410.200046.1.1.39": ("aria-256-ccm", "ARIA-256-CCM"), + "1.2.643.2.2": ("cryptopro",), + "1.2.643.2.2.3": ( + "GOST R 34.11-94 with GOST R 34.10-2001", + "id-GostR3411-94-with-GostR3410-2001", + ), + "1.2.643.2.2.4": ( + "GOST R 34.11-94 with GOST R 34.10-94", + "id-GostR3411-94-with-GostR3410-94", + ), + "1.2.643.2.2.9": ("GOST R 34.11-94", "md_gost94"), + "1.2.643.2.2.10": ("HMAC GOST 34.11-94", "id-HMACGostR3411-94"), + "1.2.643.2.2.14.0": ("id-Gost28147-89-None-KeyMeshing",), + "1.2.643.2.2.14.1": ("id-Gost28147-89-CryptoPro-KeyMeshing",), + "1.2.643.2.2.19": ("GOST R 34.10-2001", "gost2001"), + "1.2.643.2.2.20": ("GOST R 34.10-94", "gost94"), + "1.2.643.2.2.20.1": ("id-GostR3410-94-a",), + "1.2.643.2.2.20.2": ("id-GostR3410-94-aBis",), + "1.2.643.2.2.20.3": ("id-GostR3410-94-b",), + "1.2.643.2.2.20.4": ("id-GostR3410-94-bBis",), + "1.2.643.2.2.21": ("GOST 28147-89", "gost89"), + "1.2.643.2.2.22": ("GOST 28147-89 MAC", "gost-mac"), + "1.2.643.2.2.23": ("GOST R 34.11-94 PRF", "prf-gostr3411-94"), + "1.2.643.2.2.30.0": ("id-GostR3411-94-TestParamSet",), + "1.2.643.2.2.30.1": ("id-GostR3411-94-CryptoProParamSet",), + "1.2.643.2.2.31.0": ("id-Gost28147-89-TestParamSet",), + "1.2.643.2.2.31.1": ("id-Gost28147-89-CryptoPro-A-ParamSet",), + "1.2.643.2.2.31.2": ("id-Gost28147-89-CryptoPro-B-ParamSet",), + "1.2.643.2.2.31.3": ("id-Gost28147-89-CryptoPro-C-ParamSet",), + "1.2.643.2.2.31.4": ("id-Gost28147-89-CryptoPro-D-ParamSet",), + "1.2.643.2.2.31.5": ("id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet",), + "1.2.643.2.2.31.6": ("id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet",), + "1.2.643.2.2.31.7": ("id-Gost28147-89-CryptoPro-RIC-1-ParamSet",), + "1.2.643.2.2.32.0": ("id-GostR3410-94-TestParamSet",), + "1.2.643.2.2.32.2": ("id-GostR3410-94-CryptoPro-A-ParamSet",), + "1.2.643.2.2.32.3": ("id-GostR3410-94-CryptoPro-B-ParamSet",), + "1.2.643.2.2.32.4": ("id-GostR3410-94-CryptoPro-C-ParamSet",), + "1.2.643.2.2.32.5": ("id-GostR3410-94-CryptoPro-D-ParamSet",), + "1.2.643.2.2.33.1": ("id-GostR3410-94-CryptoPro-XchA-ParamSet",), + "1.2.643.2.2.33.2": ("id-GostR3410-94-CryptoPro-XchB-ParamSet",), + "1.2.643.2.2.33.3": ("id-GostR3410-94-CryptoPro-XchC-ParamSet",), + "1.2.643.2.2.35.0": ("id-GostR3410-2001-TestParamSet",), + "1.2.643.2.2.35.1": ("id-GostR3410-2001-CryptoPro-A-ParamSet",), + "1.2.643.2.2.35.2": ("id-GostR3410-2001-CryptoPro-B-ParamSet",), + "1.2.643.2.2.35.3": ("id-GostR3410-2001-CryptoPro-C-ParamSet",), + "1.2.643.2.2.36.0": ("id-GostR3410-2001-CryptoPro-XchA-ParamSet",), + "1.2.643.2.2.36.1": ("id-GostR3410-2001-CryptoPro-XchB-ParamSet",), + "1.2.643.2.2.98": ("GOST R 34.10-2001 DH", "id-GostR3410-2001DH"), + "1.2.643.2.2.99": ("GOST R 34.10-94 DH", "id-GostR3410-94DH"), + "1.2.643.2.9": ("cryptocom",), + "1.2.643.2.9.1.3.3": ( + "GOST R 34.11-94 with GOST R 34.10-94 Cryptocom", + "id-GostR3411-94-with-GostR3410-94-cc", + ), + "1.2.643.2.9.1.3.4": ( + "GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom", + "id-GostR3411-94-with-GostR3410-2001-cc", + ), + "1.2.643.2.9.1.5.3": ("GOST 34.10-94 Cryptocom", "gost94cc"), + "1.2.643.2.9.1.5.4": ("GOST 34.10-2001 Cryptocom", "gost2001cc"), + "1.2.643.2.9.1.6.1": ("GOST 28147-89 Cryptocom ParamSet", "id-Gost28147-89-cc"), + "1.2.643.2.9.1.8.1": ( + "GOST R 3410-2001 Parameter Set Cryptocom", + "id-GostR3410-2001-ParamSet-cc", + ), + "1.2.643.3.131.1.1": ("INN", "INN"), + "1.2.643.7.1": ("id-tc26",), + "1.2.643.7.1.1": ("id-tc26-algorithms",), + "1.2.643.7.1.1.1": ("id-tc26-sign",), + "1.2.643.7.1.1.1.1": ("GOST R 34.10-2012 with 256 bit modulus", "gost2012_256"), + "1.2.643.7.1.1.1.2": ("GOST R 34.10-2012 with 512 bit modulus", "gost2012_512"), + "1.2.643.7.1.1.2": ("id-tc26-digest",), + "1.2.643.7.1.1.2.2": ("GOST R 34.11-2012 with 256 bit hash", "md_gost12_256"), + "1.2.643.7.1.1.2.3": ("GOST R 34.11-2012 with 512 bit hash", "md_gost12_512"), + "1.2.643.7.1.1.3": ("id-tc26-signwithdigest",), + "1.2.643.7.1.1.3.2": ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)", + "id-tc26-signwithdigest-gost3410-2012-256", + ), + "1.2.643.7.1.1.3.3": ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)", + "id-tc26-signwithdigest-gost3410-2012-512", + ), + "1.2.643.7.1.1.4": ("id-tc26-mac",), + "1.2.643.7.1.1.4.1": ( + "HMAC GOST 34.11-2012 256 bit", + "id-tc26-hmac-gost-3411-2012-256", + ), + "1.2.643.7.1.1.4.2": ( + "HMAC GOST 34.11-2012 512 bit", + "id-tc26-hmac-gost-3411-2012-512", + ), + "1.2.643.7.1.1.5": ("id-tc26-cipher",), + "1.2.643.7.1.1.5.1": ("id-tc26-cipher-gostr3412-2015-magma",), + "1.2.643.7.1.1.5.1.1": ("id-tc26-cipher-gostr3412-2015-magma-ctracpkm",), + "1.2.643.7.1.1.5.1.2": ("id-tc26-cipher-gostr3412-2015-magma-ctracpkm-omac",), + "1.2.643.7.1.1.5.2": ("id-tc26-cipher-gostr3412-2015-kuznyechik",), + "1.2.643.7.1.1.5.2.1": ("id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm",), + "1.2.643.7.1.1.5.2.2": ("id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm-omac",), + "1.2.643.7.1.1.6": ("id-tc26-agreement",), + "1.2.643.7.1.1.6.1": ("id-tc26-agreement-gost-3410-2012-256",), + "1.2.643.7.1.1.6.2": ("id-tc26-agreement-gost-3410-2012-512",), + "1.2.643.7.1.1.7": ("id-tc26-wrap",), + "1.2.643.7.1.1.7.1": ("id-tc26-wrap-gostr3412-2015-magma",), + "1.2.643.7.1.1.7.1.1": ( + "id-tc26-wrap-gostr3412-2015-magma-kexp15", + "id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15", + ), + "1.2.643.7.1.1.7.2": ("id-tc26-wrap-gostr3412-2015-kuznyechik",), + "1.2.643.7.1.2": ("id-tc26-constants",), + "1.2.643.7.1.2.1": ("id-tc26-sign-constants",), + "1.2.643.7.1.2.1.1": ("id-tc26-gost-3410-2012-256-constants",), + "1.2.643.7.1.2.1.1.1": ( + "GOST R 34.10-2012 (256 bit) ParamSet A", + "id-tc26-gost-3410-2012-256-paramSetA", + ), + "1.2.643.7.1.2.1.1.2": ( + "GOST R 34.10-2012 (256 bit) ParamSet B", + "id-tc26-gost-3410-2012-256-paramSetB", + ), + "1.2.643.7.1.2.1.1.3": ( + "GOST R 34.10-2012 (256 bit) ParamSet C", + "id-tc26-gost-3410-2012-256-paramSetC", + ), + "1.2.643.7.1.2.1.1.4": ( + "GOST R 34.10-2012 (256 bit) ParamSet D", + "id-tc26-gost-3410-2012-256-paramSetD", + ), + "1.2.643.7.1.2.1.2": ("id-tc26-gost-3410-2012-512-constants",), + "1.2.643.7.1.2.1.2.0": ( + "GOST R 34.10-2012 (512 bit) testing parameter set", + "id-tc26-gost-3410-2012-512-paramSetTest", + ), + "1.2.643.7.1.2.1.2.1": ( + "GOST R 34.10-2012 (512 bit) ParamSet A", + "id-tc26-gost-3410-2012-512-paramSetA", + ), + "1.2.643.7.1.2.1.2.2": ( + "GOST R 34.10-2012 (512 bit) ParamSet B", + "id-tc26-gost-3410-2012-512-paramSetB", + ), + "1.2.643.7.1.2.1.2.3": ( + "GOST R 34.10-2012 (512 bit) ParamSet C", + "id-tc26-gost-3410-2012-512-paramSetC", + ), + "1.2.643.7.1.2.2": ("id-tc26-digest-constants",), + "1.2.643.7.1.2.5": ("id-tc26-cipher-constants",), + "1.2.643.7.1.2.5.1": ("id-tc26-gost-28147-constants",), + "1.2.643.7.1.2.5.1.1": ( + "GOST 28147-89 TC26 parameter set", + "id-tc26-gost-28147-param-Z", + ), + "1.2.643.100.1": ("OGRN", "OGRN"), + "1.2.643.100.3": ("SNILS", "SNILS"), + "1.2.643.100.111": ("Signing Tool of Subject", "subjectSignTool"), + "1.2.643.100.112": ("Signing Tool of Issuer", "issuerSignTool"), + "1.2.804": ("ISO-UA",), + "1.2.804.2.1.1.1": ("ua-pki",), + "1.2.804.2.1.1.1.1.1.1": ("DSTU Gost 28147-2009", "dstu28147"), + "1.2.804.2.1.1.1.1.1.1.2": ("DSTU Gost 28147-2009 OFB mode", "dstu28147-ofb"), + "1.2.804.2.1.1.1.1.1.1.3": ("DSTU Gost 28147-2009 CFB mode", "dstu28147-cfb"), + "1.2.804.2.1.1.1.1.1.1.5": ("DSTU Gost 28147-2009 key wrap", "dstu28147-wrap"), + "1.2.804.2.1.1.1.1.1.2": ("HMAC DSTU Gost 34311-95", "hmacWithDstu34311"), + "1.2.804.2.1.1.1.1.2.1": ("DSTU Gost 34311-95", "dstu34311"), + "1.2.804.2.1.1.1.1.3.1.1": ("DSTU 4145-2002 little endian", "dstu4145le"), + "1.2.804.2.1.1.1.1.3.1.1.1.1": ("DSTU 4145-2002 big endian", "dstu4145be"), + "1.2.804.2.1.1.1.1.3.1.1.2.0": ("DSTU curve 0", "uacurve0"), + "1.2.804.2.1.1.1.1.3.1.1.2.1": ("DSTU curve 1", "uacurve1"), + "1.2.804.2.1.1.1.1.3.1.1.2.2": ("DSTU curve 2", "uacurve2"), + "1.2.804.2.1.1.1.1.3.1.1.2.3": ("DSTU curve 3", "uacurve3"), + "1.2.804.2.1.1.1.1.3.1.1.2.4": ("DSTU curve 4", "uacurve4"), + "1.2.804.2.1.1.1.1.3.1.1.2.5": ("DSTU curve 5", "uacurve5"), + "1.2.804.2.1.1.1.1.3.1.1.2.6": ("DSTU curve 6", "uacurve6"), + "1.2.804.2.1.1.1.1.3.1.1.2.7": ("DSTU curve 7", "uacurve7"), + "1.2.804.2.1.1.1.1.3.1.1.2.8": ("DSTU curve 8", "uacurve8"), + "1.2.804.2.1.1.1.1.3.1.1.2.9": ("DSTU curve 9", "uacurve9"), + "1.2.840": ("ISO US Member Body", "ISO-US"), + "1.2.840.10040": ("X9.57", "X9-57"), + "1.2.840.10040.2": ("holdInstruction",), + "1.2.840.10040.2.1": ("Hold Instruction None", "holdInstructionNone"), + "1.2.840.10040.2.2": ("Hold Instruction Call Issuer", "holdInstructionCallIssuer"), + "1.2.840.10040.2.3": ("Hold Instruction Reject", "holdInstructionReject"), + "1.2.840.10040.4": ("X9.57 CM ?", "X9cm"), + "1.2.840.10040.4.1": ("dsaEncryption", "DSA"), + "1.2.840.10040.4.3": ("dsaWithSHA1", "DSA-SHA1"), + "1.2.840.10045": ("ANSI X9.62", "ansi-X9-62"), + "1.2.840.10045.1": ("id-fieldType",), + "1.2.840.10045.1.1": ("prime-field",), + "1.2.840.10045.1.2": ("characteristic-two-field",), + "1.2.840.10045.1.2.3": ("id-characteristic-two-basis",), + "1.2.840.10045.1.2.3.1": ("onBasis",), + "1.2.840.10045.1.2.3.2": ("tpBasis",), + "1.2.840.10045.1.2.3.3": ("ppBasis",), + "1.2.840.10045.2": ("id-publicKeyType",), + "1.2.840.10045.2.1": ("id-ecPublicKey",), + "1.2.840.10045.3": ("ellipticCurve",), + "1.2.840.10045.3.0": ("c-TwoCurve",), + "1.2.840.10045.3.0.1": ("c2pnb163v1",), + "1.2.840.10045.3.0.2": ("c2pnb163v2",), + "1.2.840.10045.3.0.3": ("c2pnb163v3",), + "1.2.840.10045.3.0.4": ("c2pnb176v1",), + "1.2.840.10045.3.0.5": ("c2tnb191v1",), + "1.2.840.10045.3.0.6": ("c2tnb191v2",), + "1.2.840.10045.3.0.7": ("c2tnb191v3",), + "1.2.840.10045.3.0.8": ("c2onb191v4",), + "1.2.840.10045.3.0.9": ("c2onb191v5",), + "1.2.840.10045.3.0.10": ("c2pnb208w1",), + "1.2.840.10045.3.0.11": ("c2tnb239v1",), + "1.2.840.10045.3.0.12": ("c2tnb239v2",), + "1.2.840.10045.3.0.13": ("c2tnb239v3",), + "1.2.840.10045.3.0.14": ("c2onb239v4",), + "1.2.840.10045.3.0.15": ("c2onb239v5",), + "1.2.840.10045.3.0.16": ("c2pnb272w1",), + "1.2.840.10045.3.0.17": ("c2pnb304w1",), + "1.2.840.10045.3.0.18": ("c2tnb359v1",), + "1.2.840.10045.3.0.19": ("c2pnb368w1",), + "1.2.840.10045.3.0.20": ("c2tnb431r1",), + "1.2.840.10045.3.1": ("primeCurve",), + "1.2.840.10045.3.1.1": ("prime192v1",), + "1.2.840.10045.3.1.2": ("prime192v2",), + "1.2.840.10045.3.1.3": ("prime192v3",), + "1.2.840.10045.3.1.4": ("prime239v1",), + "1.2.840.10045.3.1.5": ("prime239v2",), + "1.2.840.10045.3.1.6": ("prime239v3",), + "1.2.840.10045.3.1.7": ("prime256v1",), + "1.2.840.10045.4": ("id-ecSigType",), + "1.2.840.10045.4.1": ("ecdsa-with-SHA1",), + "1.2.840.10045.4.2": ("ecdsa-with-Recommended",), + "1.2.840.10045.4.3": ("ecdsa-with-Specified",), + "1.2.840.10045.4.3.1": ("ecdsa-with-SHA224",), + "1.2.840.10045.4.3.2": ("ecdsa-with-SHA256",), + "1.2.840.10045.4.3.3": ("ecdsa-with-SHA384",), + "1.2.840.10045.4.3.4": ("ecdsa-with-SHA512",), + "1.2.840.10046.2.1": ("X9.42 DH", "dhpublicnumber"), + "1.2.840.113533.7.66.10": ("cast5-cbc", "CAST5-CBC"), + "1.2.840.113533.7.66.12": ("pbeWithMD5AndCast5CBC",), + "1.2.840.113533.7.66.13": ("password based MAC", "id-PasswordBasedMAC"), + "1.2.840.113533.7.66.30": ("Diffie-Hellman based MAC", "id-DHBasedMac"), + "1.2.840.113549": ("RSA Data Security, Inc.", "rsadsi"), + "1.2.840.113549.1": ("RSA Data Security, Inc. PKCS", "pkcs"), + "1.2.840.113549.1.1": ("pkcs1",), + "1.2.840.113549.1.1.1": ("rsaEncryption",), + "1.2.840.113549.1.1.2": ("md2WithRSAEncryption", "RSA-MD2"), + "1.2.840.113549.1.1.3": ("md4WithRSAEncryption", "RSA-MD4"), + "1.2.840.113549.1.1.4": ("md5WithRSAEncryption", "RSA-MD5"), + "1.2.840.113549.1.1.5": ("sha1WithRSAEncryption", "RSA-SHA1"), + "1.2.840.113549.1.1.6": ("rsaOAEPEncryptionSET",), + "1.2.840.113549.1.1.7": ("rsaesOaep", "RSAES-OAEP"), + "1.2.840.113549.1.1.8": ("mgf1", "MGF1"), + "1.2.840.113549.1.1.9": ("pSpecified", "PSPECIFIED"), + "1.2.840.113549.1.1.10": ("rsassaPss", "RSASSA-PSS"), + "1.2.840.113549.1.1.11": ("sha256WithRSAEncryption", "RSA-SHA256"), + "1.2.840.113549.1.1.12": ("sha384WithRSAEncryption", "RSA-SHA384"), + "1.2.840.113549.1.1.13": ("sha512WithRSAEncryption", "RSA-SHA512"), + "1.2.840.113549.1.1.14": ("sha224WithRSAEncryption", "RSA-SHA224"), + "1.2.840.113549.1.1.15": ("sha512-224WithRSAEncryption", "RSA-SHA512/224"), + "1.2.840.113549.1.1.16": ("sha512-256WithRSAEncryption", "RSA-SHA512/256"), + "1.2.840.113549.1.3": ("pkcs3",), + "1.2.840.113549.1.3.1": ("dhKeyAgreement",), + "1.2.840.113549.1.5": ("pkcs5",), + "1.2.840.113549.1.5.1": ("pbeWithMD2AndDES-CBC", "PBE-MD2-DES"), + "1.2.840.113549.1.5.3": ("pbeWithMD5AndDES-CBC", "PBE-MD5-DES"), + "1.2.840.113549.1.5.4": ("pbeWithMD2AndRC2-CBC", "PBE-MD2-RC2-64"), + "1.2.840.113549.1.5.6": ("pbeWithMD5AndRC2-CBC", "PBE-MD5-RC2-64"), + "1.2.840.113549.1.5.10": ("pbeWithSHA1AndDES-CBC", "PBE-SHA1-DES"), + "1.2.840.113549.1.5.11": ("pbeWithSHA1AndRC2-CBC", "PBE-SHA1-RC2-64"), + "1.2.840.113549.1.5.12": ("PBKDF2",), + "1.2.840.113549.1.5.13": ("PBES2",), + "1.2.840.113549.1.5.14": ("PBMAC1",), + "1.2.840.113549.1.7": ("pkcs7",), + "1.2.840.113549.1.7.1": ("pkcs7-data",), + "1.2.840.113549.1.7.2": ("pkcs7-signedData",), + "1.2.840.113549.1.7.3": ("pkcs7-envelopedData",), + "1.2.840.113549.1.7.4": ("pkcs7-signedAndEnvelopedData",), + "1.2.840.113549.1.7.5": ("pkcs7-digestData",), + "1.2.840.113549.1.7.6": ("pkcs7-encryptedData",), + "1.2.840.113549.1.9": ("pkcs9",), + "1.2.840.113549.1.9.1": ("emailAddress",), + "1.2.840.113549.1.9.2": ("unstructuredName",), + "1.2.840.113549.1.9.3": ("contentType",), + "1.2.840.113549.1.9.4": ("messageDigest",), + "1.2.840.113549.1.9.5": ("signingTime",), + "1.2.840.113549.1.9.6": ("countersignature",), + "1.2.840.113549.1.9.7": ("challengePassword",), + "1.2.840.113549.1.9.8": ("unstructuredAddress",), + "1.2.840.113549.1.9.9": ("extendedCertificateAttributes",), + "1.2.840.113549.1.9.14": ("Extension Request", "extReq"), + "1.2.840.113549.1.9.15": ("S/MIME Capabilities", "SMIME-CAPS"), + "1.2.840.113549.1.9.16": ("S/MIME", "SMIME"), + "1.2.840.113549.1.9.16.0": ("id-smime-mod",), + "1.2.840.113549.1.9.16.0.1": ("id-smime-mod-cms",), + "1.2.840.113549.1.9.16.0.2": ("id-smime-mod-ess",), + "1.2.840.113549.1.9.16.0.3": ("id-smime-mod-oid",), + "1.2.840.113549.1.9.16.0.4": ("id-smime-mod-msg-v3",), + "1.2.840.113549.1.9.16.0.5": ("id-smime-mod-ets-eSignature-88",), + "1.2.840.113549.1.9.16.0.6": ("id-smime-mod-ets-eSignature-97",), + "1.2.840.113549.1.9.16.0.7": ("id-smime-mod-ets-eSigPolicy-88",), + "1.2.840.113549.1.9.16.0.8": ("id-smime-mod-ets-eSigPolicy-97",), + "1.2.840.113549.1.9.16.1": ("id-smime-ct",), + "1.2.840.113549.1.9.16.1.1": ("id-smime-ct-receipt",), + "1.2.840.113549.1.9.16.1.2": ("id-smime-ct-authData",), + "1.2.840.113549.1.9.16.1.3": ("id-smime-ct-publishCert",), + "1.2.840.113549.1.9.16.1.4": ("id-smime-ct-TSTInfo",), + "1.2.840.113549.1.9.16.1.5": ("id-smime-ct-TDTInfo",), + "1.2.840.113549.1.9.16.1.6": ("id-smime-ct-contentInfo",), + "1.2.840.113549.1.9.16.1.7": ("id-smime-ct-DVCSRequestData",), + "1.2.840.113549.1.9.16.1.8": ("id-smime-ct-DVCSResponseData",), + "1.2.840.113549.1.9.16.1.9": ("id-smime-ct-compressedData",), + "1.2.840.113549.1.9.16.1.19": ("id-smime-ct-contentCollection",), + "1.2.840.113549.1.9.16.1.23": ("id-smime-ct-authEnvelopedData",), + "1.2.840.113549.1.9.16.1.27": ("id-ct-asciiTextWithCRLF",), + "1.2.840.113549.1.9.16.1.28": ("id-ct-xml",), + "1.2.840.113549.1.9.16.2": ("id-smime-aa",), + "1.2.840.113549.1.9.16.2.1": ("id-smime-aa-receiptRequest",), + "1.2.840.113549.1.9.16.2.2": ("id-smime-aa-securityLabel",), + "1.2.840.113549.1.9.16.2.3": ("id-smime-aa-mlExpandHistory",), + "1.2.840.113549.1.9.16.2.4": ("id-smime-aa-contentHint",), + "1.2.840.113549.1.9.16.2.5": ("id-smime-aa-msgSigDigest",), + "1.2.840.113549.1.9.16.2.6": ("id-smime-aa-encapContentType",), + "1.2.840.113549.1.9.16.2.7": ("id-smime-aa-contentIdentifier",), + "1.2.840.113549.1.9.16.2.8": ("id-smime-aa-macValue",), + "1.2.840.113549.1.9.16.2.9": ("id-smime-aa-equivalentLabels",), + "1.2.840.113549.1.9.16.2.10": ("id-smime-aa-contentReference",), + "1.2.840.113549.1.9.16.2.11": ("id-smime-aa-encrypKeyPref",), + "1.2.840.113549.1.9.16.2.12": ("id-smime-aa-signingCertificate",), + "1.2.840.113549.1.9.16.2.13": ("id-smime-aa-smimeEncryptCerts",), + "1.2.840.113549.1.9.16.2.14": ("id-smime-aa-timeStampToken",), + "1.2.840.113549.1.9.16.2.15": ("id-smime-aa-ets-sigPolicyId",), + "1.2.840.113549.1.9.16.2.16": ("id-smime-aa-ets-commitmentType",), + "1.2.840.113549.1.9.16.2.17": ("id-smime-aa-ets-signerLocation",), + "1.2.840.113549.1.9.16.2.18": ("id-smime-aa-ets-signerAttr",), + "1.2.840.113549.1.9.16.2.19": ("id-smime-aa-ets-otherSigCert",), + "1.2.840.113549.1.9.16.2.20": ("id-smime-aa-ets-contentTimestamp",), + "1.2.840.113549.1.9.16.2.21": ("id-smime-aa-ets-CertificateRefs",), + "1.2.840.113549.1.9.16.2.22": ("id-smime-aa-ets-RevocationRefs",), + "1.2.840.113549.1.9.16.2.23": ("id-smime-aa-ets-certValues",), + "1.2.840.113549.1.9.16.2.24": ("id-smime-aa-ets-revocationValues",), + "1.2.840.113549.1.9.16.2.25": ("id-smime-aa-ets-escTimeStamp",), + "1.2.840.113549.1.9.16.2.26": ("id-smime-aa-ets-certCRLTimestamp",), + "1.2.840.113549.1.9.16.2.27": ("id-smime-aa-ets-archiveTimeStamp",), + "1.2.840.113549.1.9.16.2.28": ("id-smime-aa-signatureType",), + "1.2.840.113549.1.9.16.2.29": ("id-smime-aa-dvcs-dvc",), + "1.2.840.113549.1.9.16.2.47": ("id-smime-aa-signingCertificateV2",), + "1.2.840.113549.1.9.16.3": ("id-smime-alg",), + "1.2.840.113549.1.9.16.3.1": ("id-smime-alg-ESDHwith3DES",), + "1.2.840.113549.1.9.16.3.2": ("id-smime-alg-ESDHwithRC2",), + "1.2.840.113549.1.9.16.3.3": ("id-smime-alg-3DESwrap",), + "1.2.840.113549.1.9.16.3.4": ("id-smime-alg-RC2wrap",), + "1.2.840.113549.1.9.16.3.5": ("id-smime-alg-ESDH",), + "1.2.840.113549.1.9.16.3.6": ("id-smime-alg-CMS3DESwrap",), + "1.2.840.113549.1.9.16.3.7": ("id-smime-alg-CMSRC2wrap",), + "1.2.840.113549.1.9.16.3.8": ("zlib compression", "ZLIB"), + "1.2.840.113549.1.9.16.3.9": ("id-alg-PWRI-KEK",), + "1.2.840.113549.1.9.16.4": ("id-smime-cd",), + "1.2.840.113549.1.9.16.4.1": ("id-smime-cd-ldap",), + "1.2.840.113549.1.9.16.5": ("id-smime-spq",), + "1.2.840.113549.1.9.16.5.1": ("id-smime-spq-ets-sqt-uri",), + "1.2.840.113549.1.9.16.5.2": ("id-smime-spq-ets-sqt-unotice",), + "1.2.840.113549.1.9.16.6": ("id-smime-cti",), + "1.2.840.113549.1.9.16.6.1": ("id-smime-cti-ets-proofOfOrigin",), + "1.2.840.113549.1.9.16.6.2": ("id-smime-cti-ets-proofOfReceipt",), + "1.2.840.113549.1.9.16.6.3": ("id-smime-cti-ets-proofOfDelivery",), + "1.2.840.113549.1.9.16.6.4": ("id-smime-cti-ets-proofOfSender",), + "1.2.840.113549.1.9.16.6.5": ("id-smime-cti-ets-proofOfApproval",), + "1.2.840.113549.1.9.16.6.6": ("id-smime-cti-ets-proofOfCreation",), + "1.2.840.113549.1.9.20": ("friendlyName",), + "1.2.840.113549.1.9.21": ("localKeyID",), + "1.2.840.113549.1.9.22": ("certTypes",), + "1.2.840.113549.1.9.22.1": ("x509Certificate",), + "1.2.840.113549.1.9.22.2": ("sdsiCertificate",), + "1.2.840.113549.1.9.23": ("crlTypes",), + "1.2.840.113549.1.9.23.1": ("x509Crl",), + "1.2.840.113549.1.12": ("pkcs12",), + "1.2.840.113549.1.12.1": ("pkcs12-pbeids",), + "1.2.840.113549.1.12.1.1": ("pbeWithSHA1And128BitRC4", "PBE-SHA1-RC4-128"), + "1.2.840.113549.1.12.1.2": ("pbeWithSHA1And40BitRC4", "PBE-SHA1-RC4-40"), + "1.2.840.113549.1.12.1.3": ("pbeWithSHA1And3-KeyTripleDES-CBC", "PBE-SHA1-3DES"), + "1.2.840.113549.1.12.1.4": ("pbeWithSHA1And2-KeyTripleDES-CBC", "PBE-SHA1-2DES"), + "1.2.840.113549.1.12.1.5": ("pbeWithSHA1And128BitRC2-CBC", "PBE-SHA1-RC2-128"), + "1.2.840.113549.1.12.1.6": ("pbeWithSHA1And40BitRC2-CBC", "PBE-SHA1-RC2-40"), + "1.2.840.113549.1.12.10": ("pkcs12-Version1",), + "1.2.840.113549.1.12.10.1": ("pkcs12-BagIds",), + "1.2.840.113549.1.12.10.1.1": ("keyBag",), + "1.2.840.113549.1.12.10.1.2": ("pkcs8ShroudedKeyBag",), + "1.2.840.113549.1.12.10.1.3": ("certBag",), + "1.2.840.113549.1.12.10.1.4": ("crlBag",), + "1.2.840.113549.1.12.10.1.5": ("secretBag",), + "1.2.840.113549.1.12.10.1.6": ("safeContentsBag",), + "1.2.840.113549.2.2": ("md2", "MD2"), + "1.2.840.113549.2.4": ("md4", "MD4"), + "1.2.840.113549.2.5": ("md5", "MD5"), + "1.2.840.113549.2.6": ("hmacWithMD5",), + "1.2.840.113549.2.7": ("hmacWithSHA1",), + "1.2.840.113549.2.8": ("hmacWithSHA224",), + "1.2.840.113549.2.9": ("hmacWithSHA256",), + "1.2.840.113549.2.10": ("hmacWithSHA384",), + "1.2.840.113549.2.11": ("hmacWithSHA512",), + "1.2.840.113549.2.12": ("hmacWithSHA512-224",), + "1.2.840.113549.2.13": ("hmacWithSHA512-256",), + "1.2.840.113549.3.2": ("rc2-cbc", "RC2-CBC"), + "1.2.840.113549.3.4": ("rc4", "RC4"), + "1.2.840.113549.3.7": ("des-ede3-cbc", "DES-EDE3-CBC"), + "1.2.840.113549.3.8": ("rc5-cbc", "RC5-CBC"), + "1.2.840.113549.3.10": ("des-cdmf", "DES-CDMF"), + "1.3": ("identified-organization", "org", "ORG"), + "1.3.6": ("dod", "DOD"), + "1.3.6.1": ("iana", "IANA", "internet"), + "1.3.6.1.1": ("Directory", "directory"), + "1.3.6.1.2": ("Management", "mgmt"), + "1.3.6.1.3": ("Experimental", "experimental"), + "1.3.6.1.4": ("Private", "private"), + "1.3.6.1.4.1": ("Enterprises", "enterprises"), + "1.3.6.1.4.1.188.7.1.1.2": ("idea-cbc", "IDEA-CBC"), + "1.3.6.1.4.1.311.2.1.14": ("Microsoft Extension Request", "msExtReq"), + "1.3.6.1.4.1.311.2.1.21": ("Microsoft Individual Code Signing", "msCodeInd"), + "1.3.6.1.4.1.311.2.1.22": ("Microsoft Commercial Code Signing", "msCodeCom"), + "1.3.6.1.4.1.311.10.3.1": ("Microsoft Trust List Signing", "msCTLSign"), + "1.3.6.1.4.1.311.10.3.3": ("Microsoft Server Gated Crypto", "msSGC"), + "1.3.6.1.4.1.311.10.3.4": ("Microsoft Encrypted File System", "msEFS"), + "1.3.6.1.4.1.311.17.1": ("Microsoft CSP Name", "CSPName"), + "1.3.6.1.4.1.311.17.2": ("Microsoft Local Key set", "LocalKeySet"), + "1.3.6.1.4.1.311.20.2.2": ("Microsoft Smartcardlogin", "msSmartcardLogin"), + "1.3.6.1.4.1.311.20.2.3": ("Microsoft Universal Principal Name", "msUPN"), + "1.3.6.1.4.1.311.60.2.1.1": ("jurisdictionLocalityName", "jurisdictionL"), + "1.3.6.1.4.1.311.60.2.1.2": ("jurisdictionStateOrProvinceName", "jurisdictionST"), + "1.3.6.1.4.1.311.60.2.1.3": ("jurisdictionCountryName", "jurisdictionC"), + "1.3.6.1.4.1.1466.344": ("dcObject", "dcobject"), + "1.3.6.1.4.1.1722.12.2.1.16": ("blake2b512", "BLAKE2b512"), + "1.3.6.1.4.1.1722.12.2.2.8": ("blake2s256", "BLAKE2s256"), + "1.3.6.1.4.1.3029.1.2": ("bf-cbc", "BF-CBC"), + "1.3.6.1.4.1.11129.2.4.2": ("CT Precertificate SCTs", "ct_precert_scts"), + "1.3.6.1.4.1.11129.2.4.3": ("CT Precertificate Poison", "ct_precert_poison"), + "1.3.6.1.4.1.11129.2.4.4": ("CT Precertificate Signer", "ct_precert_signer"), + "1.3.6.1.4.1.11129.2.4.5": ("CT Certificate SCTs", "ct_cert_scts"), + "1.3.6.1.4.1.11591.4.11": ("scrypt", "id-scrypt"), + "1.3.6.1.5": ("Security", "security"), + "1.3.6.1.5.2.3": ("id-pkinit",), + "1.3.6.1.5.2.3.4": ("PKINIT Client Auth", "pkInitClientAuth"), + "1.3.6.1.5.2.3.5": ("Signing KDC Response", "pkInitKDC"), + "1.3.6.1.5.5.7": ("PKIX",), + "1.3.6.1.5.5.7.0": ("id-pkix-mod",), + "1.3.6.1.5.5.7.0.1": ("id-pkix1-explicit-88",), + "1.3.6.1.5.5.7.0.2": ("id-pkix1-implicit-88",), + "1.3.6.1.5.5.7.0.3": ("id-pkix1-explicit-93",), + "1.3.6.1.5.5.7.0.4": ("id-pkix1-implicit-93",), + "1.3.6.1.5.5.7.0.5": ("id-mod-crmf",), + "1.3.6.1.5.5.7.0.6": ("id-mod-cmc",), + "1.3.6.1.5.5.7.0.7": ("id-mod-kea-profile-88",), + "1.3.6.1.5.5.7.0.8": ("id-mod-kea-profile-93",), + "1.3.6.1.5.5.7.0.9": ("id-mod-cmp",), + "1.3.6.1.5.5.7.0.10": ("id-mod-qualified-cert-88",), + "1.3.6.1.5.5.7.0.11": ("id-mod-qualified-cert-93",), + "1.3.6.1.5.5.7.0.12": ("id-mod-attribute-cert",), + "1.3.6.1.5.5.7.0.13": ("id-mod-timestamp-protocol",), + "1.3.6.1.5.5.7.0.14": ("id-mod-ocsp",), + "1.3.6.1.5.5.7.0.15": ("id-mod-dvcs",), + "1.3.6.1.5.5.7.0.16": ("id-mod-cmp2000",), + "1.3.6.1.5.5.7.1": ("id-pe",), + "1.3.6.1.5.5.7.1.1": ("Authority Information Access", "authorityInfoAccess"), + "1.3.6.1.5.5.7.1.2": ("Biometric Info", "biometricInfo"), + "1.3.6.1.5.5.7.1.3": ("qcStatements",), + "1.3.6.1.5.5.7.1.4": ("ac-auditEntity",), + "1.3.6.1.5.5.7.1.5": ("ac-targeting",), + "1.3.6.1.5.5.7.1.6": ("aaControls",), + "1.3.6.1.5.5.7.1.7": ("sbgp-ipAddrBlock",), + "1.3.6.1.5.5.7.1.8": ("sbgp-autonomousSysNum",), + "1.3.6.1.5.5.7.1.9": ("sbgp-routerIdentifier",), + "1.3.6.1.5.5.7.1.10": ("ac-proxying",), + "1.3.6.1.5.5.7.1.11": ("Subject Information Access", "subjectInfoAccess"), + "1.3.6.1.5.5.7.1.14": ("Proxy Certificate Information", "proxyCertInfo"), + "1.3.6.1.5.5.7.1.24": ("TLS Feature", "tlsfeature"), + "1.3.6.1.5.5.7.2": ("id-qt",), + "1.3.6.1.5.5.7.2.1": ("Policy Qualifier CPS", "id-qt-cps"), + "1.3.6.1.5.5.7.2.2": ("Policy Qualifier User Notice", "id-qt-unotice"), + "1.3.6.1.5.5.7.2.3": ("textNotice",), + "1.3.6.1.5.5.7.3": ("id-kp",), + "1.3.6.1.5.5.7.3.1": ("TLS Web Server Authentication", "serverAuth"), + "1.3.6.1.5.5.7.3.2": ("TLS Web Client Authentication", "clientAuth"), + "1.3.6.1.5.5.7.3.3": ("Code Signing", "codeSigning"), + "1.3.6.1.5.5.7.3.4": ("E-mail Protection", "emailProtection"), + "1.3.6.1.5.5.7.3.5": ("IPSec End System", "ipsecEndSystem"), + "1.3.6.1.5.5.7.3.6": ("IPSec Tunnel", "ipsecTunnel"), + "1.3.6.1.5.5.7.3.7": ("IPSec User", "ipsecUser"), + "1.3.6.1.5.5.7.3.8": ("Time Stamping", "timeStamping"), + "1.3.6.1.5.5.7.3.9": ("OCSP Signing", "OCSPSigning"), + "1.3.6.1.5.5.7.3.10": ("dvcs", "DVCS"), + "1.3.6.1.5.5.7.3.17": ("ipsec Internet Key Exchange", "ipsecIKE"), + "1.3.6.1.5.5.7.3.18": ("Ctrl/provision WAP Access", "capwapAC"), + "1.3.6.1.5.5.7.3.19": ("Ctrl/Provision WAP Termination", "capwapWTP"), + "1.3.6.1.5.5.7.3.21": ("SSH Client", "secureShellClient"), + "1.3.6.1.5.5.7.3.22": ("SSH Server", "secureShellServer"), + "1.3.6.1.5.5.7.3.23": ("Send Router", "sendRouter"), + "1.3.6.1.5.5.7.3.24": ("Send Proxied Router", "sendProxiedRouter"), + "1.3.6.1.5.5.7.3.25": ("Send Owner", "sendOwner"), + "1.3.6.1.5.5.7.3.26": ("Send Proxied Owner", "sendProxiedOwner"), + "1.3.6.1.5.5.7.3.27": ("CMC Certificate Authority", "cmcCA"), + "1.3.6.1.5.5.7.3.28": ("CMC Registration Authority", "cmcRA"), + "1.3.6.1.5.5.7.4": ("id-it",), + "1.3.6.1.5.5.7.4.1": ("id-it-caProtEncCert",), + "1.3.6.1.5.5.7.4.2": ("id-it-signKeyPairTypes",), + "1.3.6.1.5.5.7.4.3": ("id-it-encKeyPairTypes",), + "1.3.6.1.5.5.7.4.4": ("id-it-preferredSymmAlg",), + "1.3.6.1.5.5.7.4.5": ("id-it-caKeyUpdateInfo",), + "1.3.6.1.5.5.7.4.6": ("id-it-currentCRL",), + "1.3.6.1.5.5.7.4.7": ("id-it-unsupportedOIDs",), + "1.3.6.1.5.5.7.4.8": ("id-it-subscriptionRequest",), + "1.3.6.1.5.5.7.4.9": ("id-it-subscriptionResponse",), + "1.3.6.1.5.5.7.4.10": ("id-it-keyPairParamReq",), + "1.3.6.1.5.5.7.4.11": ("id-it-keyPairParamRep",), + "1.3.6.1.5.5.7.4.12": ("id-it-revPassphrase",), + "1.3.6.1.5.5.7.4.13": ("id-it-implicitConfirm",), + "1.3.6.1.5.5.7.4.14": ("id-it-confirmWaitTime",), + "1.3.6.1.5.5.7.4.15": ("id-it-origPKIMessage",), + "1.3.6.1.5.5.7.4.16": ("id-it-suppLangTags",), + "1.3.6.1.5.5.7.5": ("id-pkip",), + "1.3.6.1.5.5.7.5.1": ("id-regCtrl",), + "1.3.6.1.5.5.7.5.1.1": ("id-regCtrl-regToken",), + "1.3.6.1.5.5.7.5.1.2": ("id-regCtrl-authenticator",), + "1.3.6.1.5.5.7.5.1.3": ("id-regCtrl-pkiPublicationInfo",), + "1.3.6.1.5.5.7.5.1.4": ("id-regCtrl-pkiArchiveOptions",), + "1.3.6.1.5.5.7.5.1.5": ("id-regCtrl-oldCertID",), + "1.3.6.1.5.5.7.5.1.6": ("id-regCtrl-protocolEncrKey",), + "1.3.6.1.5.5.7.5.2": ("id-regInfo",), + "1.3.6.1.5.5.7.5.2.1": ("id-regInfo-utf8Pairs",), + "1.3.6.1.5.5.7.5.2.2": ("id-regInfo-certReq",), + "1.3.6.1.5.5.7.6": ("id-alg",), + "1.3.6.1.5.5.7.6.1": ("id-alg-des40",), + "1.3.6.1.5.5.7.6.2": ("id-alg-noSignature",), + "1.3.6.1.5.5.7.6.3": ("id-alg-dh-sig-hmac-sha1",), + "1.3.6.1.5.5.7.6.4": ("id-alg-dh-pop",), + "1.3.6.1.5.5.7.7": ("id-cmc",), + "1.3.6.1.5.5.7.7.1": ("id-cmc-statusInfo",), + "1.3.6.1.5.5.7.7.2": ("id-cmc-identification",), + "1.3.6.1.5.5.7.7.3": ("id-cmc-identityProof",), + "1.3.6.1.5.5.7.7.4": ("id-cmc-dataReturn",), + "1.3.6.1.5.5.7.7.5": ("id-cmc-transactionId",), + "1.3.6.1.5.5.7.7.6": ("id-cmc-senderNonce",), + "1.3.6.1.5.5.7.7.7": ("id-cmc-recipientNonce",), + "1.3.6.1.5.5.7.7.8": ("id-cmc-addExtensions",), + "1.3.6.1.5.5.7.7.9": ("id-cmc-encryptedPOP",), + "1.3.6.1.5.5.7.7.10": ("id-cmc-decryptedPOP",), + "1.3.6.1.5.5.7.7.11": ("id-cmc-lraPOPWitness",), + "1.3.6.1.5.5.7.7.15": ("id-cmc-getCert",), + "1.3.6.1.5.5.7.7.16": ("id-cmc-getCRL",), + "1.3.6.1.5.5.7.7.17": ("id-cmc-revokeRequest",), + "1.3.6.1.5.5.7.7.18": ("id-cmc-regInfo",), + "1.3.6.1.5.5.7.7.19": ("id-cmc-responseInfo",), + "1.3.6.1.5.5.7.7.21": ("id-cmc-queryPending",), + "1.3.6.1.5.5.7.7.22": ("id-cmc-popLinkRandom",), + "1.3.6.1.5.5.7.7.23": ("id-cmc-popLinkWitness",), + "1.3.6.1.5.5.7.7.24": ("id-cmc-confirmCertAcceptance",), + "1.3.6.1.5.5.7.8": ("id-on",), + "1.3.6.1.5.5.7.8.1": ("id-on-personalData",), + "1.3.6.1.5.5.7.8.3": ("Permanent Identifier", "id-on-permanentIdentifier"), + "1.3.6.1.5.5.7.9": ("id-pda",), + "1.3.6.1.5.5.7.9.1": ("id-pda-dateOfBirth",), + "1.3.6.1.5.5.7.9.2": ("id-pda-placeOfBirth",), + "1.3.6.1.5.5.7.9.3": ("id-pda-gender",), + "1.3.6.1.5.5.7.9.4": ("id-pda-countryOfCitizenship",), + "1.3.6.1.5.5.7.9.5": ("id-pda-countryOfResidence",), + "1.3.6.1.5.5.7.10": ("id-aca",), + "1.3.6.1.5.5.7.10.1": ("id-aca-authenticationInfo",), + "1.3.6.1.5.5.7.10.2": ("id-aca-accessIdentity",), + "1.3.6.1.5.5.7.10.3": ("id-aca-chargingIdentity",), + "1.3.6.1.5.5.7.10.4": ("id-aca-group",), + "1.3.6.1.5.5.7.10.5": ("id-aca-role",), + "1.3.6.1.5.5.7.10.6": ("id-aca-encAttrs",), + "1.3.6.1.5.5.7.11": ("id-qcs",), + "1.3.6.1.5.5.7.11.1": ("id-qcs-pkixQCSyntax-v1",), + "1.3.6.1.5.5.7.12": ("id-cct",), + "1.3.6.1.5.5.7.12.1": ("id-cct-crs",), + "1.3.6.1.5.5.7.12.2": ("id-cct-PKIData",), + "1.3.6.1.5.5.7.12.3": ("id-cct-PKIResponse",), + "1.3.6.1.5.5.7.21": ("id-ppl",), + "1.3.6.1.5.5.7.21.0": ("Any language", "id-ppl-anyLanguage"), + "1.3.6.1.5.5.7.21.1": ("Inherit all", "id-ppl-inheritAll"), + "1.3.6.1.5.5.7.21.2": ("Independent", "id-ppl-independent"), + "1.3.6.1.5.5.7.48": ("id-ad",), + "1.3.6.1.5.5.7.48.1": ("OCSP", "OCSP", "id-pkix-OCSP"), + "1.3.6.1.5.5.7.48.1.1": ("Basic OCSP Response", "basicOCSPResponse"), + "1.3.6.1.5.5.7.48.1.2": ("OCSP Nonce", "Nonce"), + "1.3.6.1.5.5.7.48.1.3": ("OCSP CRL ID", "CrlID"), + "1.3.6.1.5.5.7.48.1.4": ("Acceptable OCSP Responses", "acceptableResponses"), + "1.3.6.1.5.5.7.48.1.5": ("OCSP No Check", "noCheck"), + "1.3.6.1.5.5.7.48.1.6": ("OCSP Archive Cutoff", "archiveCutoff"), + "1.3.6.1.5.5.7.48.1.7": ("OCSP Service Locator", "serviceLocator"), + "1.3.6.1.5.5.7.48.1.8": ("Extended OCSP Status", "extendedStatus"), + "1.3.6.1.5.5.7.48.1.9": ("valid",), + "1.3.6.1.5.5.7.48.1.10": ("path",), + "1.3.6.1.5.5.7.48.1.11": ("Trust Root", "trustRoot"), + "1.3.6.1.5.5.7.48.2": ("CA Issuers", "caIssuers"), + "1.3.6.1.5.5.7.48.3": ("AD Time Stamping", "ad_timestamping"), + "1.3.6.1.5.5.7.48.4": ("ad dvcs", "AD_DVCS"), + "1.3.6.1.5.5.7.48.5": ("CA Repository", "caRepository"), + "1.3.6.1.5.5.8.1.1": ("hmac-md5", "HMAC-MD5"), + "1.3.6.1.5.5.8.1.2": ("hmac-sha1", "HMAC-SHA1"), + "1.3.6.1.6": ("SNMPv2", "snmpv2"), + "1.3.6.1.7": ("Mail",), + "1.3.6.1.7.1": ("MIME MHS", "mime-mhs"), + "1.3.6.1.7.1.1": ("mime-mhs-headings", "mime-mhs-headings"), + "1.3.6.1.7.1.1.1": ("id-hex-partial-message", "id-hex-partial-message"), + "1.3.6.1.7.1.1.2": ("id-hex-multipart-message", "id-hex-multipart-message"), + "1.3.6.1.7.1.2": ("mime-mhs-bodies", "mime-mhs-bodies"), + "1.3.14.3.2": ("algorithm", "algorithm"), + "1.3.14.3.2.3": ("md5WithRSA", "RSA-NP-MD5"), + "1.3.14.3.2.6": ("des-ecb", "DES-ECB"), + "1.3.14.3.2.7": ("des-cbc", "DES-CBC"), + "1.3.14.3.2.8": ("des-ofb", "DES-OFB"), + "1.3.14.3.2.9": ("des-cfb", "DES-CFB"), + "1.3.14.3.2.11": ("rsaSignature",), + "1.3.14.3.2.12": ("dsaEncryption-old", "DSA-old"), + "1.3.14.3.2.13": ("dsaWithSHA", "DSA-SHA"), + "1.3.14.3.2.15": ("shaWithRSAEncryption", "RSA-SHA"), + "1.3.14.3.2.17": ("des-ede", "DES-EDE"), + "1.3.14.3.2.18": ("sha", "SHA"), + "1.3.14.3.2.26": ("sha1", "SHA1"), + "1.3.14.3.2.27": ("dsaWithSHA1-old", "DSA-SHA1-old"), + "1.3.14.3.2.29": ("sha1WithRSA", "RSA-SHA1-2"), + "1.3.36.3.2.1": ("ripemd160", "RIPEMD160"), + "1.3.36.3.3.1.2": ("ripemd160WithRSA", "RSA-RIPEMD160"), + "1.3.36.3.3.2.8.1.1.1": ("brainpoolP160r1",), + "1.3.36.3.3.2.8.1.1.2": ("brainpoolP160t1",), + "1.3.36.3.3.2.8.1.1.3": ("brainpoolP192r1",), + "1.3.36.3.3.2.8.1.1.4": ("brainpoolP192t1",), + "1.3.36.3.3.2.8.1.1.5": ("brainpoolP224r1",), + "1.3.36.3.3.2.8.1.1.6": ("brainpoolP224t1",), + "1.3.36.3.3.2.8.1.1.7": ("brainpoolP256r1",), + "1.3.36.3.3.2.8.1.1.8": ("brainpoolP256t1",), + "1.3.36.3.3.2.8.1.1.9": ("brainpoolP320r1",), + "1.3.36.3.3.2.8.1.1.10": ("brainpoolP320t1",), + "1.3.36.3.3.2.8.1.1.11": ("brainpoolP384r1",), + "1.3.36.3.3.2.8.1.1.12": ("brainpoolP384t1",), + "1.3.36.3.3.2.8.1.1.13": ("brainpoolP512r1",), + "1.3.36.3.3.2.8.1.1.14": ("brainpoolP512t1",), + "1.3.36.8.3.3": ( + "Professional Information or basis for Admission", + "x509ExtAdmission", + ), + "1.3.101.1.4.1": ("Strong Extranet ID", "SXNetID"), + "1.3.101.110": ("X25519",), + "1.3.101.111": ("X448",), + "1.3.101.112": ("ED25519",), + "1.3.101.113": ("ED448",), + "1.3.111": ("ieee",), + "1.3.111.2.1619": ("IEEE Security in Storage Working Group", "ieee-siswg"), + "1.3.111.2.1619.0.1.1": ("aes-128-xts", "AES-128-XTS"), + "1.3.111.2.1619.0.1.2": ("aes-256-xts", "AES-256-XTS"), + "1.3.132": ("certicom-arc",), + "1.3.132.0": ("secg_ellipticCurve",), + "1.3.132.0.1": ("sect163k1",), + "1.3.132.0.2": ("sect163r1",), + "1.3.132.0.3": ("sect239k1",), + "1.3.132.0.4": ("sect113r1",), + "1.3.132.0.5": ("sect113r2",), + "1.3.132.0.6": ("secp112r1",), + "1.3.132.0.7": ("secp112r2",), + "1.3.132.0.8": ("secp160r1",), + "1.3.132.0.9": ("secp160k1",), + "1.3.132.0.10": ("secp256k1",), + "1.3.132.0.15": ("sect163r2",), + "1.3.132.0.16": ("sect283k1",), + "1.3.132.0.17": ("sect283r1",), + "1.3.132.0.22": ("sect131r1",), + "1.3.132.0.23": ("sect131r2",), + "1.3.132.0.24": ("sect193r1",), + "1.3.132.0.25": ("sect193r2",), + "1.3.132.0.26": ("sect233k1",), + "1.3.132.0.27": ("sect233r1",), + "1.3.132.0.28": ("secp128r1",), + "1.3.132.0.29": ("secp128r2",), + "1.3.132.0.30": ("secp160r2",), + "1.3.132.0.31": ("secp192k1",), + "1.3.132.0.32": ("secp224k1",), + "1.3.132.0.33": ("secp224r1",), + "1.3.132.0.34": ("secp384r1",), + "1.3.132.0.35": ("secp521r1",), + "1.3.132.0.36": ("sect409k1",), + "1.3.132.0.37": ("sect409r1",), + "1.3.132.0.38": ("sect571k1",), + "1.3.132.0.39": ("sect571r1",), + "1.3.132.1": ("secg-scheme",), + "1.3.132.1.11.0": ("dhSinglePass-stdDH-sha224kdf-scheme",), + "1.3.132.1.11.1": ("dhSinglePass-stdDH-sha256kdf-scheme",), + "1.3.132.1.11.2": ("dhSinglePass-stdDH-sha384kdf-scheme",), + "1.3.132.1.11.3": ("dhSinglePass-stdDH-sha512kdf-scheme",), + "1.3.132.1.14.0": ("dhSinglePass-cofactorDH-sha224kdf-scheme",), + "1.3.132.1.14.1": ("dhSinglePass-cofactorDH-sha256kdf-scheme",), + "1.3.132.1.14.2": ("dhSinglePass-cofactorDH-sha384kdf-scheme",), + "1.3.132.1.14.3": ("dhSinglePass-cofactorDH-sha512kdf-scheme",), + "1.3.133.16.840.63.0": ("x9-63-scheme",), + "1.3.133.16.840.63.0.2": ("dhSinglePass-stdDH-sha1kdf-scheme",), + "1.3.133.16.840.63.0.3": ("dhSinglePass-cofactorDH-sha1kdf-scheme",), + "2": ("joint-iso-itu-t", "JOINT-ISO-ITU-T", "joint-iso-ccitt"), + "2.5": ("directory services (X.500)", "X500"), + "2.5.1.5": ("Selected Attribute Types", "selected-attribute-types"), + "2.5.1.5.55": ("clearance",), + "2.5.4": ("X509",), + "2.5.4.3": ("commonName", "CN"), + "2.5.4.4": ("surname", "SN"), + "2.5.4.5": ("serialNumber",), + "2.5.4.6": ("countryName", "C"), + "2.5.4.7": ("localityName", "L"), + "2.5.4.8": ("stateOrProvinceName", "ST"), + "2.5.4.9": ("streetAddress", "street"), + "2.5.4.10": ("organizationName", "O"), + "2.5.4.11": ("organizationalUnitName", "OU"), + "2.5.4.12": ("title", "title"), + "2.5.4.13": ("description",), + "2.5.4.14": ("searchGuide",), + "2.5.4.15": ("businessCategory",), + "2.5.4.16": ("postalAddress",), + "2.5.4.17": ("postalCode",), + "2.5.4.18": ("postOfficeBox",), + "2.5.4.19": ("physicalDeliveryOfficeName",), + "2.5.4.20": ("telephoneNumber",), + "2.5.4.21": ("telexNumber",), + "2.5.4.22": ("teletexTerminalIdentifier",), + "2.5.4.23": ("facsimileTelephoneNumber",), + "2.5.4.24": ("x121Address",), + "2.5.4.25": ("internationaliSDNNumber",), + "2.5.4.26": ("registeredAddress",), + "2.5.4.27": ("destinationIndicator",), + "2.5.4.28": ("preferredDeliveryMethod",), + "2.5.4.29": ("presentationAddress",), + "2.5.4.30": ("supportedApplicationContext",), + "2.5.4.31": ("member",), + "2.5.4.32": ("owner",), + "2.5.4.33": ("roleOccupant",), + "2.5.4.34": ("seeAlso",), + "2.5.4.35": ("userPassword",), + "2.5.4.36": ("userCertificate",), + "2.5.4.37": ("cACertificate",), + "2.5.4.38": ("authorityRevocationList",), + "2.5.4.39": ("certificateRevocationList",), + "2.5.4.40": ("crossCertificatePair",), + "2.5.4.41": ("name", "name"), + "2.5.4.42": ("givenName", "GN"), + "2.5.4.43": ("initials", "initials"), + "2.5.4.44": ("generationQualifier",), + "2.5.4.45": ("x500UniqueIdentifier",), + "2.5.4.46": ("dnQualifier", "dnQualifier"), + "2.5.4.47": ("enhancedSearchGuide",), + "2.5.4.48": ("protocolInformation",), + "2.5.4.49": ("distinguishedName",), + "2.5.4.50": ("uniqueMember",), + "2.5.4.51": ("houseIdentifier",), + "2.5.4.52": ("supportedAlgorithms",), + "2.5.4.53": ("deltaRevocationList",), + "2.5.4.54": ("dmdName",), + "2.5.4.65": ("pseudonym",), + "2.5.4.72": ("role", "role"), + "2.5.4.97": ("organizationIdentifier",), + "2.5.4.98": ("countryCode3c", "c3"), + "2.5.4.99": ("countryCode3n", "n3"), + "2.5.4.100": ("dnsName",), + "2.5.8": ("directory services - algorithms", "X500algorithms"), + "2.5.8.1.1": ("rsa", "RSA"), + "2.5.8.3.100": ("mdc2WithRSA", "RSA-MDC2"), + "2.5.8.3.101": ("mdc2", "MDC2"), + "2.5.29": ("id-ce",), + "2.5.29.9": ("X509v3 Subject Directory Attributes", "subjectDirectoryAttributes"), + "2.5.29.14": ("X509v3 Subject Key Identifier", "subjectKeyIdentifier"), + "2.5.29.15": ("X509v3 Key Usage", "keyUsage"), + "2.5.29.16": ("X509v3 Private Key Usage Period", "privateKeyUsagePeriod"), + "2.5.29.17": ("X509v3 Subject Alternative Name", "subjectAltName"), + "2.5.29.18": ("X509v3 Issuer Alternative Name", "issuerAltName"), + "2.5.29.19": ("X509v3 Basic Constraints", "basicConstraints"), + "2.5.29.20": ("X509v3 CRL Number", "crlNumber"), + "2.5.29.21": ("X509v3 CRL Reason Code", "CRLReason"), + "2.5.29.23": ("Hold Instruction Code", "holdInstructionCode"), + "2.5.29.24": ("Invalidity Date", "invalidityDate"), + "2.5.29.27": ("X509v3 Delta CRL Indicator", "deltaCRL"), + "2.5.29.28": ("X509v3 Issuing Distribution Point", "issuingDistributionPoint"), + "2.5.29.29": ("X509v3 Certificate Issuer", "certificateIssuer"), + "2.5.29.30": ("X509v3 Name Constraints", "nameConstraints"), + "2.5.29.31": ("X509v3 CRL Distribution Points", "crlDistributionPoints"), + "2.5.29.32": ("X509v3 Certificate Policies", "certificatePolicies"), + "2.5.29.32.0": ("X509v3 Any Policy", "anyPolicy"), + "2.5.29.33": ("X509v3 Policy Mappings", "policyMappings"), + "2.5.29.35": ("X509v3 Authority Key Identifier", "authorityKeyIdentifier"), + "2.5.29.36": ("X509v3 Policy Constraints", "policyConstraints"), + "2.5.29.37": ("X509v3 Extended Key Usage", "extendedKeyUsage"), + "2.5.29.37.0": ("Any Extended Key Usage", "anyExtendedKeyUsage"), + "2.5.29.46": ("X509v3 Freshest CRL", "freshestCRL"), + "2.5.29.54": ("X509v3 Inhibit Any Policy", "inhibitAnyPolicy"), + "2.5.29.55": ("X509v3 AC Targeting", "targetInformation"), + "2.5.29.56": ("X509v3 No Revocation Available", "noRevAvail"), + "2.16.840.1.101.3": ("csor",), + "2.16.840.1.101.3.4": ("nistAlgorithms",), + "2.16.840.1.101.3.4.1": ("aes",), + "2.16.840.1.101.3.4.1.1": ("aes-128-ecb", "AES-128-ECB"), + "2.16.840.1.101.3.4.1.2": ("aes-128-cbc", "AES-128-CBC"), + "2.16.840.1.101.3.4.1.3": ("aes-128-ofb", "AES-128-OFB"), + "2.16.840.1.101.3.4.1.4": ("aes-128-cfb", "AES-128-CFB"), + "2.16.840.1.101.3.4.1.5": ("id-aes128-wrap",), + "2.16.840.1.101.3.4.1.6": ("aes-128-gcm", "id-aes128-GCM"), + "2.16.840.1.101.3.4.1.7": ("aes-128-ccm", "id-aes128-CCM"), + "2.16.840.1.101.3.4.1.8": ("id-aes128-wrap-pad",), + "2.16.840.1.101.3.4.1.21": ("aes-192-ecb", "AES-192-ECB"), + "2.16.840.1.101.3.4.1.22": ("aes-192-cbc", "AES-192-CBC"), + "2.16.840.1.101.3.4.1.23": ("aes-192-ofb", "AES-192-OFB"), + "2.16.840.1.101.3.4.1.24": ("aes-192-cfb", "AES-192-CFB"), + "2.16.840.1.101.3.4.1.25": ("id-aes192-wrap",), + "2.16.840.1.101.3.4.1.26": ("aes-192-gcm", "id-aes192-GCM"), + "2.16.840.1.101.3.4.1.27": ("aes-192-ccm", "id-aes192-CCM"), + "2.16.840.1.101.3.4.1.28": ("id-aes192-wrap-pad",), + "2.16.840.1.101.3.4.1.41": ("aes-256-ecb", "AES-256-ECB"), + "2.16.840.1.101.3.4.1.42": ("aes-256-cbc", "AES-256-CBC"), + "2.16.840.1.101.3.4.1.43": ("aes-256-ofb", "AES-256-OFB"), + "2.16.840.1.101.3.4.1.44": ("aes-256-cfb", "AES-256-CFB"), + "2.16.840.1.101.3.4.1.45": ("id-aes256-wrap",), + "2.16.840.1.101.3.4.1.46": ("aes-256-gcm", "id-aes256-GCM"), + "2.16.840.1.101.3.4.1.47": ("aes-256-ccm", "id-aes256-CCM"), + "2.16.840.1.101.3.4.1.48": ("id-aes256-wrap-pad",), + "2.16.840.1.101.3.4.2": ("nist_hashalgs",), + "2.16.840.1.101.3.4.2.1": ("sha256", "SHA256"), + "2.16.840.1.101.3.4.2.2": ("sha384", "SHA384"), + "2.16.840.1.101.3.4.2.3": ("sha512", "SHA512"), + "2.16.840.1.101.3.4.2.4": ("sha224", "SHA224"), + "2.16.840.1.101.3.4.2.5": ("sha512-224", "SHA512-224"), + "2.16.840.1.101.3.4.2.6": ("sha512-256", "SHA512-256"), + "2.16.840.1.101.3.4.2.7": ("sha3-224", "SHA3-224"), + "2.16.840.1.101.3.4.2.8": ("sha3-256", "SHA3-256"), + "2.16.840.1.101.3.4.2.9": ("sha3-384", "SHA3-384"), + "2.16.840.1.101.3.4.2.10": ("sha3-512", "SHA3-512"), + "2.16.840.1.101.3.4.2.11": ("shake128", "SHAKE128"), + "2.16.840.1.101.3.4.2.12": ("shake256", "SHAKE256"), + "2.16.840.1.101.3.4.2.13": ("hmac-sha3-224", "id-hmacWithSHA3-224"), + "2.16.840.1.101.3.4.2.14": ("hmac-sha3-256", "id-hmacWithSHA3-256"), + "2.16.840.1.101.3.4.2.15": ("hmac-sha3-384", "id-hmacWithSHA3-384"), + "2.16.840.1.101.3.4.2.16": ("hmac-sha3-512", "id-hmacWithSHA3-512"), + "2.16.840.1.101.3.4.3": ("dsa_with_sha2", "sigAlgs"), + "2.16.840.1.101.3.4.3.1": ("dsa_with_SHA224",), + "2.16.840.1.101.3.4.3.2": ("dsa_with_SHA256",), + "2.16.840.1.101.3.4.3.3": ("dsa_with_SHA384", "id-dsa-with-sha384"), + "2.16.840.1.101.3.4.3.4": ("dsa_with_SHA512", "id-dsa-with-sha512"), + "2.16.840.1.101.3.4.3.5": ("dsa_with_SHA3-224", "id-dsa-with-sha3-224"), + "2.16.840.1.101.3.4.3.6": ("dsa_with_SHA3-256", "id-dsa-with-sha3-256"), + "2.16.840.1.101.3.4.3.7": ("dsa_with_SHA3-384", "id-dsa-with-sha3-384"), + "2.16.840.1.101.3.4.3.8": ("dsa_with_SHA3-512", "id-dsa-with-sha3-512"), + "2.16.840.1.101.3.4.3.9": ("ecdsa_with_SHA3-224", "id-ecdsa-with-sha3-224"), + "2.16.840.1.101.3.4.3.10": ("ecdsa_with_SHA3-256", "id-ecdsa-with-sha3-256"), + "2.16.840.1.101.3.4.3.11": ("ecdsa_with_SHA3-384", "id-ecdsa-with-sha3-384"), + "2.16.840.1.101.3.4.3.12": ("ecdsa_with_SHA3-512", "id-ecdsa-with-sha3-512"), + "2.16.840.1.101.3.4.3.13": ("RSA-SHA3-224", "id-rsassa-pkcs1-v1_5-with-sha3-224"), + "2.16.840.1.101.3.4.3.14": ("RSA-SHA3-256", "id-rsassa-pkcs1-v1_5-with-sha3-256"), + "2.16.840.1.101.3.4.3.15": ("RSA-SHA3-384", "id-rsassa-pkcs1-v1_5-with-sha3-384"), + "2.16.840.1.101.3.4.3.16": ("RSA-SHA3-512", "id-rsassa-pkcs1-v1_5-with-sha3-512"), + "2.16.840.1.113730": ("Netscape Communications Corp.", "Netscape"), + "2.16.840.1.113730.1": ("Netscape Certificate Extension", "nsCertExt"), + "2.16.840.1.113730.1.1": ("Netscape Cert Type", "nsCertType"), + "2.16.840.1.113730.1.2": ("Netscape Base Url", "nsBaseUrl"), + "2.16.840.1.113730.1.3": ("Netscape Revocation Url", "nsRevocationUrl"), + "2.16.840.1.113730.1.4": ("Netscape CA Revocation Url", "nsCaRevocationUrl"), + "2.16.840.1.113730.1.7": ("Netscape Renewal Url", "nsRenewalUrl"), + "2.16.840.1.113730.1.8": ("Netscape CA Policy Url", "nsCaPolicyUrl"), + "2.16.840.1.113730.1.12": ("Netscape SSL Server Name", "nsSslServerName"), + "2.16.840.1.113730.1.13": ("Netscape Comment", "nsComment"), + "2.16.840.1.113730.2": ("Netscape Data Type", "nsDataType"), + "2.16.840.1.113730.2.5": ("Netscape Certificate Sequence", "nsCertSequence"), + "2.16.840.1.113730.4.1": ("Netscape Server Gated Crypto", "nsSGC"), + "2.23": ("International Organizations", "international-organizations"), + "2.23.42": ("Secure Electronic Transactions", "id-set"), + "2.23.42.0": ("content types", "set-ctype"), + "2.23.42.0.0": ("setct-PANData",), + "2.23.42.0.1": ("setct-PANToken",), + "2.23.42.0.2": ("setct-PANOnly",), + "2.23.42.0.3": ("setct-OIData",), + "2.23.42.0.4": ("setct-PI",), + "2.23.42.0.5": ("setct-PIData",), + "2.23.42.0.6": ("setct-PIDataUnsigned",), + "2.23.42.0.7": ("setct-HODInput",), + "2.23.42.0.8": ("setct-AuthResBaggage",), + "2.23.42.0.9": ("setct-AuthRevReqBaggage",), + "2.23.42.0.10": ("setct-AuthRevResBaggage",), + "2.23.42.0.11": ("setct-CapTokenSeq",), + "2.23.42.0.12": ("setct-PInitResData",), + "2.23.42.0.13": ("setct-PI-TBS",), + "2.23.42.0.14": ("setct-PResData",), + "2.23.42.0.16": ("setct-AuthReqTBS",), + "2.23.42.0.17": ("setct-AuthResTBS",), + "2.23.42.0.18": ("setct-AuthResTBSX",), + "2.23.42.0.19": ("setct-AuthTokenTBS",), + "2.23.42.0.20": ("setct-CapTokenData",), + "2.23.42.0.21": ("setct-CapTokenTBS",), + "2.23.42.0.22": ("setct-AcqCardCodeMsg",), + "2.23.42.0.23": ("setct-AuthRevReqTBS",), + "2.23.42.0.24": ("setct-AuthRevResData",), + "2.23.42.0.25": ("setct-AuthRevResTBS",), + "2.23.42.0.26": ("setct-CapReqTBS",), + "2.23.42.0.27": ("setct-CapReqTBSX",), + "2.23.42.0.28": ("setct-CapResData",), + "2.23.42.0.29": ("setct-CapRevReqTBS",), + "2.23.42.0.30": ("setct-CapRevReqTBSX",), + "2.23.42.0.31": ("setct-CapRevResData",), + "2.23.42.0.32": ("setct-CredReqTBS",), + "2.23.42.0.33": ("setct-CredReqTBSX",), + "2.23.42.0.34": ("setct-CredResData",), + "2.23.42.0.35": ("setct-CredRevReqTBS",), + "2.23.42.0.36": ("setct-CredRevReqTBSX",), + "2.23.42.0.37": ("setct-CredRevResData",), + "2.23.42.0.38": ("setct-PCertReqData",), + "2.23.42.0.39": ("setct-PCertResTBS",), + "2.23.42.0.40": ("setct-BatchAdminReqData",), + "2.23.42.0.41": ("setct-BatchAdminResData",), + "2.23.42.0.42": ("setct-CardCInitResTBS",), + "2.23.42.0.43": ("setct-MeAqCInitResTBS",), + "2.23.42.0.44": ("setct-RegFormResTBS",), + "2.23.42.0.45": ("setct-CertReqData",), + "2.23.42.0.46": ("setct-CertReqTBS",), + "2.23.42.0.47": ("setct-CertResData",), + "2.23.42.0.48": ("setct-CertInqReqTBS",), + "2.23.42.0.49": ("setct-ErrorTBS",), + "2.23.42.0.50": ("setct-PIDualSignedTBE",), + "2.23.42.0.51": ("setct-PIUnsignedTBE",), + "2.23.42.0.52": ("setct-AuthReqTBE",), + "2.23.42.0.53": ("setct-AuthResTBE",), + "2.23.42.0.54": ("setct-AuthResTBEX",), + "2.23.42.0.55": ("setct-AuthTokenTBE",), + "2.23.42.0.56": ("setct-CapTokenTBE",), + "2.23.42.0.57": ("setct-CapTokenTBEX",), + "2.23.42.0.58": ("setct-AcqCardCodeMsgTBE",), + "2.23.42.0.59": ("setct-AuthRevReqTBE",), + "2.23.42.0.60": ("setct-AuthRevResTBE",), + "2.23.42.0.61": ("setct-AuthRevResTBEB",), + "2.23.42.0.62": ("setct-CapReqTBE",), + "2.23.42.0.63": ("setct-CapReqTBEX",), + "2.23.42.0.64": ("setct-CapResTBE",), + "2.23.42.0.65": ("setct-CapRevReqTBE",), + "2.23.42.0.66": ("setct-CapRevReqTBEX",), + "2.23.42.0.67": ("setct-CapRevResTBE",), + "2.23.42.0.68": ("setct-CredReqTBE",), + "2.23.42.0.69": ("setct-CredReqTBEX",), + "2.23.42.0.70": ("setct-CredResTBE",), + "2.23.42.0.71": ("setct-CredRevReqTBE",), + "2.23.42.0.72": ("setct-CredRevReqTBEX",), + "2.23.42.0.73": ("setct-CredRevResTBE",), + "2.23.42.0.74": ("setct-BatchAdminReqTBE",), + "2.23.42.0.75": ("setct-BatchAdminResTBE",), + "2.23.42.0.76": ("setct-RegFormReqTBE",), + "2.23.42.0.77": ("setct-CertReqTBE",), + "2.23.42.0.78": ("setct-CertReqTBEX",), + "2.23.42.0.79": ("setct-CertResTBE",), + "2.23.42.0.80": ("setct-CRLNotificationTBS",), + "2.23.42.0.81": ("setct-CRLNotificationResTBS",), + "2.23.42.0.82": ("setct-BCIDistributionTBS",), + "2.23.42.1": ("message extensions", "set-msgExt"), + "2.23.42.1.1": ("generic cryptogram", "setext-genCrypt"), + "2.23.42.1.3": ("merchant initiated auth", "setext-miAuth"), + "2.23.42.1.4": ("setext-pinSecure",), + "2.23.42.1.5": ("setext-pinAny",), + "2.23.42.1.7": ("setext-track2",), + "2.23.42.1.8": ("additional verification", "setext-cv"), + "2.23.42.3": ("set-attr",), + "2.23.42.3.0": ("setAttr-Cert",), + "2.23.42.3.0.0": ("set-rootKeyThumb",), + "2.23.42.3.0.1": ("set-addPolicy",), + "2.23.42.3.1": ("payment gateway capabilities", "setAttr-PGWYcap"), + "2.23.42.3.2": ("setAttr-TokenType",), + "2.23.42.3.2.1": ("setAttr-Token-EMV",), + "2.23.42.3.2.2": ("setAttr-Token-B0Prime",), + "2.23.42.3.3": ("issuer capabilities", "setAttr-IssCap"), + "2.23.42.3.3.3": ("setAttr-IssCap-CVM",), + "2.23.42.3.3.3.1": ("generate cryptogram", "setAttr-GenCryptgrm"), + "2.23.42.3.3.4": ("setAttr-IssCap-T2",), + "2.23.42.3.3.4.1": ("encrypted track 2", "setAttr-T2Enc"), + "2.23.42.3.3.4.2": ("cleartext track 2", "setAttr-T2cleartxt"), + "2.23.42.3.3.5": ("setAttr-IssCap-Sig",), + "2.23.42.3.3.5.1": ("ICC or token signature", "setAttr-TokICCsig"), + "2.23.42.3.3.5.2": ("secure device signature", "setAttr-SecDevSig"), + "2.23.42.5": ("set-policy",), + "2.23.42.5.0": ("set-policy-root",), + "2.23.42.7": ("certificate extensions", "set-certExt"), + "2.23.42.7.0": ("setCext-hashedRoot",), + "2.23.42.7.1": ("setCext-certType",), + "2.23.42.7.2": ("setCext-merchData",), + "2.23.42.7.3": ("setCext-cCertRequired",), + "2.23.42.7.4": ("setCext-tunneling",), + "2.23.42.7.5": ("setCext-setExt",), + "2.23.42.7.6": ("setCext-setQualf",), + "2.23.42.7.7": ("setCext-PGWYcapabilities",), + "2.23.42.7.8": ("setCext-TokenIdentifier",), + "2.23.42.7.9": ("setCext-Track2Data",), + "2.23.42.7.10": ("setCext-TokenType",), + "2.23.42.7.11": ("setCext-IssuerCapabilities",), + "2.23.42.8": ("set-brand",), + "2.23.42.8.1": ("set-brand-IATA-ATA",), + "2.23.42.8.4": ("set-brand-Visa",), + "2.23.42.8.5": ("set-brand-MasterCard",), + "2.23.42.8.30": ("set-brand-Diners",), + "2.23.42.8.34": ("set-brand-AmericanExpress",), + "2.23.42.8.35": ("set-brand-JCB",), + "2.23.42.8.6011": ("set-brand-Novus",), + "2.23.43": ("wap",), + "2.23.43.1": ("wap-wsg",), + "2.23.43.1.4": ("wap-wsg-idm-ecid",), + "2.23.43.1.4.1": ("wap-wsg-idm-ecid-wtls1",), + "2.23.43.1.4.3": ("wap-wsg-idm-ecid-wtls3",), + "2.23.43.1.4.4": ("wap-wsg-idm-ecid-wtls4",), + "2.23.43.1.4.5": ("wap-wsg-idm-ecid-wtls5",), + "2.23.43.1.4.6": ("wap-wsg-idm-ecid-wtls6",), + "2.23.43.1.4.7": ("wap-wsg-idm-ecid-wtls7",), + "2.23.43.1.4.8": ("wap-wsg-idm-ecid-wtls8",), + "2.23.43.1.4.9": ("wap-wsg-idm-ecid-wtls9",), + "2.23.43.1.4.10": ("wap-wsg-idm-ecid-wtls10",), + "2.23.43.1.4.11": ("wap-wsg-idm-ecid-wtls11",), + "2.23.43.1.4.12": ("wap-wsg-idm-ecid-wtls12",), } diff --git a/plugins/module_utils/crypto/basic.py b/plugins/module_utils/crypto/basic.py index 8c6715ec..fc000e34 100644 --- a/plugins/module_utils/crypto/basic.py +++ b/plugins/module_utils/crypto/basic.py @@ -27,7 +27,7 @@ try: # actually doing that in x509_certificate, and potentially in other code, # we need to monkey-patch __hash__ for these classes to make sure our code # works fine. - if LooseVersion(cryptography.__version__) < LooseVersion('2.1'): + if LooseVersion(cryptography.__version__) < LooseVersion("2.1"): # A very simply hash function which relies on the representation # of an object to be implemented. This is the case since at least # cryptography 1.0, see @@ -44,7 +44,7 @@ try: x509.OtherName.__hash__ = simple_hash x509.RegisteredID.__hash__ = simple_hash - if LooseVersion(cryptography.__version__) < LooseVersion('1.2'): + if LooseVersion(cryptography.__version__) < LooseVersion("1.2"): # The hash functions for the following types were added for cryptography 1.2: # https://github.com/pyca/cryptography/commit/b642deed88a8696e5f01ce6855ccf89985fc35d0 # https://github.com/pyca/cryptography/commit/d1b5681f6db2bde7a14625538bd7907b08dfb486 @@ -55,6 +55,7 @@ try: try: # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/ import cryptography.hazmat.primitives.asymmetric.dsa + CRYPTOGRAPHY_HAS_DSA = True try: # added later in 1.5 @@ -68,6 +69,7 @@ try: try: # added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/ import cryptography.hazmat.primitives.asymmetric.ed25519 + CRYPTOGRAPHY_HAS_ED25519 = True try: # added with the primitive in 2.6 @@ -81,6 +83,7 @@ try: try: # added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/ import cryptography.hazmat.primitives.asymmetric.ed448 + CRYPTOGRAPHY_HAS_ED448 = True try: # added with the primitive in 2.6 @@ -94,6 +97,7 @@ try: try: # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ import cryptography.hazmat.primitives.asymmetric.ec + CRYPTOGRAPHY_HAS_EC = True try: # added later in 1.5 @@ -107,6 +111,7 @@ try: try: # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/ import cryptography.hazmat.primitives.asymmetric.rsa + CRYPTOGRAPHY_HAS_RSA = True try: # added later in 1.4 @@ -120,6 +125,7 @@ try: try: # added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/ import cryptography.hazmat.primitives.asymmetric.x25519 + CRYPTOGRAPHY_HAS_X25519 = True try: # added later in 2.5 @@ -133,6 +139,7 @@ try: try: # added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/ import cryptography.hazmat.primitives.asymmetric.x448 + CRYPTOGRAPHY_HAS_X448 = True except ImportError: CRYPTOGRAPHY_HAS_X448 = False diff --git a/plugins/module_utils/crypto/cryptography_crl.py b/plugins/module_utils/crypto/cryptography_crl.py index 0beaad43..3f979189 100644 --- a/plugins/module_utils/crypto/cryptography_crl.py +++ b/plugins/module_utils/crypto/cryptography_crl.py @@ -32,23 +32,25 @@ from .cryptography_support import CRYPTOGRAPHY_TIMEZONE, cryptography_decode_nam # (https://github.com/pyca/cryptography/issues/10818) CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = False if HAS_CRYPTOGRAPHY: - CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = _LooseVersion(cryptography.__version__) >= _LooseVersion('43.0.0') + CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = _LooseVersion( + cryptography.__version__ + ) >= _LooseVersion("43.0.0") TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" if HAS_CRYPTOGRAPHY: REVOCATION_REASON_MAP = { - 'unspecified': x509.ReasonFlags.unspecified, - 'key_compromise': x509.ReasonFlags.key_compromise, - 'ca_compromise': x509.ReasonFlags.ca_compromise, - 'affiliation_changed': x509.ReasonFlags.affiliation_changed, - 'superseded': x509.ReasonFlags.superseded, - 'cessation_of_operation': x509.ReasonFlags.cessation_of_operation, - 'certificate_hold': x509.ReasonFlags.certificate_hold, - 'privilege_withdrawn': x509.ReasonFlags.privilege_withdrawn, - 'aa_compromise': x509.ReasonFlags.aa_compromise, - 'remove_from_crl': x509.ReasonFlags.remove_from_crl, + "unspecified": x509.ReasonFlags.unspecified, + "key_compromise": x509.ReasonFlags.key_compromise, + "ca_compromise": x509.ReasonFlags.ca_compromise, + "affiliation_changed": x509.ReasonFlags.affiliation_changed, + "superseded": x509.ReasonFlags.superseded, + "cessation_of_operation": x509.ReasonFlags.cessation_of_operation, + "certificate_hold": x509.ReasonFlags.certificate_hold, + "privilege_withdrawn": x509.ReasonFlags.privilege_withdrawn, + "aa_compromise": x509.ReasonFlags.aa_compromise, + "remove_from_crl": x509.ReasonFlags.remove_from_crl, } REVOCATION_REASON_MAP_INVERSE = dict() for k, v in REVOCATION_REASON_MAP.items(): @@ -61,50 +63,61 @@ else: def cryptography_decode_revoked_certificate(cert): result = { - 'serial_number': cert.serial_number, - 'revocation_date': get_revocation_date(cert), - 'issuer': None, - 'issuer_critical': False, - 'reason': None, - 'reason_critical': False, - 'invalidity_date': None, - 'invalidity_date_critical': False, + "serial_number": cert.serial_number, + "revocation_date": get_revocation_date(cert), + "issuer": None, + "issuer_critical": False, + "reason": None, + "reason_critical": False, + "invalidity_date": None, + "invalidity_date_critical": False, } try: ext = cert.extensions.get_extension_for_class(x509.CertificateIssuer) - result['issuer'] = list(ext.value) - result['issuer_critical'] = ext.critical + result["issuer"] = list(ext.value) + result["issuer_critical"] = ext.critical except x509.ExtensionNotFound: pass try: ext = cert.extensions.get_extension_for_class(x509.CRLReason) - result['reason'] = ext.value.reason - result['reason_critical'] = ext.critical + result["reason"] = ext.value.reason + result["reason_critical"] = ext.critical except x509.ExtensionNotFound: pass try: ext = cert.extensions.get_extension_for_class(x509.InvalidityDate) - result['invalidity_date'] = get_invalidity_date(ext.value) - result['invalidity_date_critical'] = ext.critical + result["invalidity_date"] = get_invalidity_date(ext.value) + result["invalidity_date_critical"] = ext.critical except x509.ExtensionNotFound: pass return result -def cryptography_dump_revoked(entry, idn_rewrite='ignore'): +def cryptography_dump_revoked(entry, idn_rewrite="ignore"): return { - 'serial_number': entry['serial_number'], - 'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT), - 'issuer': - [cryptography_decode_name(issuer, idn_rewrite=idn_rewrite) for issuer in entry['issuer']] - if entry['issuer'] is not None else None, - 'issuer_critical': entry['issuer_critical'], - 'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None, - 'reason_critical': entry['reason_critical'], - 'invalidity_date': - entry['invalidity_date'].strftime(TIMESTAMP_FORMAT) - if entry['invalidity_date'] is not None else None, - 'invalidity_date_critical': entry['invalidity_date_critical'], + "serial_number": entry["serial_number"], + "revocation_date": entry["revocation_date"].strftime(TIMESTAMP_FORMAT), + "issuer": ( + [ + cryptography_decode_name(issuer, idn_rewrite=idn_rewrite) + for issuer in entry["issuer"] + ] + if entry["issuer"] is not None + else None + ), + "issuer_critical": entry["issuer_critical"], + "reason": ( + REVOCATION_REASON_MAP_INVERSE.get(entry["reason"]) + if entry["reason"] is not None + else None + ), + "reason_critical": entry["reason_critical"], + "invalidity_date": ( + entry["invalidity_date"].strftime(TIMESTAMP_FORMAT) + if entry["invalidity_date"] is not None + else None + ), + "invalidity_date_critical": entry["invalidity_date_critical"], } @@ -114,9 +127,7 @@ def cryptography_get_signature_algorithm_oid_from_crl(crl): except AttributeError: # Older cryptography versions do not have signature_algorithm_oid yet dotted = obj2txt( - crl._backend._lib, - crl._backend._ffi, - crl._x509_crl.sig_alg.algorithm + crl._backend._lib, crl._backend._ffi, crl._x509_crl.sig_alg.algorithm ) return x509.oid.ObjectIdentifier(dotted) diff --git a/plugins/module_utils/crypto/cryptography_support.py b/plugins/module_utils/crypto/cryptography_support.py index ccb0a203..62404ee0 100644 --- a/plugins/module_utils/crypto/cryptography_support.py +++ b/plugins/module_utils/crypto/cryptography_support.py @@ -38,6 +38,7 @@ try: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding + _HAS_CRYPTOGRAPHY = True except ImportError: _HAS_CRYPTOGRAPHY = False @@ -112,10 +113,12 @@ from .basic import ( CRYPTOGRAPHY_TIMEZONE = False if _HAS_CRYPTOGRAPHY: - CRYPTOGRAPHY_TIMEZONE = LooseVersion(cryptography.__version__) >= LooseVersion('42.0.0') + CRYPTOGRAPHY_TIMEZONE = LooseVersion(cryptography.__version__) >= LooseVersion( + "42.0.0" + ) -DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$') +DOTTED_OID = re.compile(r"^\d+(?:\.\d+)+$") def cryptography_get_extensions_from_cert(cert): @@ -150,7 +153,11 @@ def cryptography_get_extensions_from_cert(cert): value=to_native(base64.b64encode(der)), ) try: - oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext)) + oid = obj2txt( + backend._lib, + backend._ffi, + backend._lib.X509_EXTENSION_get_object(ext), + ) except AttributeError: oid = exts[i].oid.dotted_string result[oid] = entry @@ -189,8 +196,10 @@ def cryptography_get_extensions_from_csr(csr): extensions, lambda ext: backend._lib.sk_X509_EXTENSION_pop_free( ext, - backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free") - ) + backend._ffi.addressof( + backend._lib._original_lib, "X509_EXTENSION_free" + ), + ), ) # With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does @@ -210,7 +219,11 @@ def cryptography_get_extensions_from_csr(csr): value=to_native(base64.b64encode(der)), ) try: - oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext)) + oid = obj2txt( + backend._lib, + backend._ffi, + backend._lib.X509_EXTENSION_get_object(ext), + ) except AttributeError: oid = exts[i].oid.dotted_string result[oid] = entry @@ -246,7 +259,7 @@ def cryptography_oid_to_name(oid, short=False): name = names[0] else: name = oid._name - if name == 'Unknown OID': + if name == "Unknown OID": name = dotted_string if short: return NORMALIZE_NAMES_SHORT.get(name, name) @@ -258,104 +271,128 @@ def _get_hex(bytesstr): if bytesstr is None: return bytesstr data = binascii.hexlify(bytesstr) - data = to_text(b':'.join(data[i:i + 2] for i in range(0, len(data), 2))) + data = to_text(b":".join(data[i : i + 2] for i in range(0, len(data), 2))) return data def _parse_hex(bytesstr): if bytesstr is None: return bytesstr - data = ''.join([('0' * (2 - len(p)) + p) if len(p) < 2 else p for p in to_text(bytesstr).split(':')]) + data = "".join( + [ + ("0" * (2 - len(p)) + p) if len(p) < 2 else p + for p in to_text(bytesstr).split(":") + ] + ) data = binascii.unhexlify(data) return data -DN_COMPONENT_START_RE = re.compile(b'^ *([a-zA-z0-9.]+) *= *') -DN_HEX_LETTER = b'0123456789abcdef' +DN_COMPONENT_START_RE = re.compile(b"^ *([a-zA-z0-9.]+) *= *") +DN_HEX_LETTER = b"0123456789abcdef" if sys.version_info[0] < 3: _int_to_byte = chr else: + def _int_to_byte(value): - return bytes((value, )) + return bytes((value,)) -def _parse_dn_component(name, sep=b',', decode_remainder=True): +def _parse_dn_component(name, sep=b",", decode_remainder=True): m = DN_COMPONENT_START_RE.match(name) if not m: raise OpenSSLObjectError(u'cannot start part in "{0}"'.format(to_text(name))) oid = cryptography_name_to_oid(to_text(m.group(1))) idx = len(m.group(0)) decoded_name = [] - sep_str = sep + b'\\' + sep_str = sep + b"\\" if decode_remainder: length = len(name) - if length > idx and name[idx:idx + 1] == b'#': + if length > idx and name[idx : idx + 1] == b"#": # Decoding a hex string idx += 1 while idx + 1 < length: - ch1 = name[idx:idx + 1] - ch2 = name[idx + 1:idx + 2] + ch1 = name[idx : idx + 1] + ch2 = name[idx + 1 : idx + 2] idx1 = DN_HEX_LETTER.find(ch1.lower()) idx2 = DN_HEX_LETTER.find(ch2.lower()) if idx1 < 0 or idx2 < 0: - raise OpenSSLObjectError(u'Invalid hex sequence entry "{0}"'.format(to_text(ch1 + ch2))) + raise OpenSSLObjectError( + u'Invalid hex sequence entry "{0}"'.format(to_text(ch1 + ch2)) + ) idx += 2 decoded_name.append(_int_to_byte(idx1 * 16 + idx2)) else: # Decoding a regular string while idx < length: i = idx - while i < length and name[i:i + 1] not in sep_str: + while i < length and name[i : i + 1] not in sep_str: i += 1 if i > idx: decoded_name.append(name[idx:i]) idx = i - while idx + 1 < length and name[idx:idx + 1] == b'\\': - ch = name[idx + 1:idx + 2] + while idx + 1 < length and name[idx : idx + 1] == b"\\": + ch = name[idx + 1 : idx + 2] idx1 = DN_HEX_LETTER.find(ch.lower()) if idx1 >= 0: if idx + 2 >= length: - raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" incomplete at end of string'.format(to_text(ch))) - ch2 = name[idx + 2:idx + 3] + raise OpenSSLObjectError( + u'Hex escape sequence "\\{0}" incomplete at end of string'.format( + to_text(ch) + ) + ) + ch2 = name[idx + 2 : idx + 3] idx2 = DN_HEX_LETTER.find(ch2.lower()) if idx2 < 0: - raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" has invalid second letter'.format(to_text(ch + ch2))) + raise OpenSSLObjectError( + u'Hex escape sequence "\\{0}" has invalid second letter'.format( + to_text(ch + ch2) + ) + ) ch = _int_to_byte(idx1 * 16 + idx2) idx += 1 idx += 2 decoded_name.append(ch) - if idx < length and name[idx:idx + 1] == sep: + if idx < length and name[idx : idx + 1] == sep: break else: decoded_name.append(name[idx:]) idx = len(name) - return x509.NameAttribute(oid, to_text(b''.join(decoded_name))), name[idx:] + return x509.NameAttribute(oid, to_text(b"".join(decoded_name))), name[idx:] def _parse_dn(name): - ''' + """ Parse a Distinguished Name. Can be of the form ``CN=Test, O = Something`` or ``CN = Test,O= Something``. - ''' + """ original_name = name name = name.lstrip() - sep = b',' - if name.startswith(b'/'): - sep = b'/' + sep = b"," + if name.startswith(b"/"): + sep = b"/" name = name[1:] result = [] while name: try: attribute, name = _parse_dn_component(name, sep=sep) except OpenSSLObjectError as e: - raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": {1}'.format(to_text(original_name), e)) + raise OpenSSLObjectError( + u'Error while parsing distinguished name "{0}": {1}'.format( + to_text(original_name), e + ) + ) result.append(attribute) if name: if name[0:1] != sep or len(name) < 2: - raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": unexpected end of string'.format(to_text(original_name))) + raise OpenSSLObjectError( + u'Error while parsing distinguished name "{0}": unexpected end of string'.format( + to_text(original_name) + ) + ) name = name[1:] return result @@ -366,12 +403,16 @@ def cryptography_parse_relative_distinguished_name(rdn): try: names.append(_parse_dn_component(to_bytes(part), decode_remainder=False)[0]) except OpenSSLObjectError as e: - raise OpenSSLObjectError(u'Error while parsing relative distinguished name "{0}": {1}'.format(part, e)) + raise OpenSSLObjectError( + u'Error while parsing relative distinguished name "{0}": {1}'.format( + part, e + ) + ) return cryptography.x509.RelativeDistinguishedName(names) def _is_ascii(value): - '''Check whether the Unicode string `value` contains only ASCII characters.''' + """Check whether the Unicode string `value` contains only ASCII characters.""" try: value.encode("ascii") return True @@ -380,195 +421,244 @@ def _is_ascii(value): def _adjust_idn(value, idn_rewrite): - if idn_rewrite == 'ignore' or not value: + if idn_rewrite == "ignore" or not value: return value - if idn_rewrite == 'idna' and _is_ascii(value): + if idn_rewrite == "idna" and _is_ascii(value): return value - if idn_rewrite not in ('idna', 'unicode'): + if idn_rewrite not in ("idna", "unicode"): raise ValueError('Invalid value for idn_rewrite: "{0}"'.format(idn_rewrite)) if not HAS_IDNA: raise OpenSSLObjectError( - missing_required_lib('idna', reason='to transform {what} DNS name "{name}" to {dest}'.format( - name=value, - what='IDNA' if idn_rewrite == 'unicode' else 'Unicode', - dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA', - ))) + missing_required_lib( + "idna", + reason='to transform {what} DNS name "{name}" to {dest}'.format( + name=value, + what="IDNA" if idn_rewrite == "unicode" else "Unicode", + dest="Unicode" if idn_rewrite == "unicode" else "IDNA", + ), + ) + ) # Since IDNA does not like '*' or empty labels (except one empty label at the end), # we split and let IDNA only handle labels that are neither empty or '*'. - parts = value.split(u'.') + parts = value.split(u".") for index, part in enumerate(parts): - if part in (u'', u'*'): + if part in (u"", u"*"): continue try: - if idn_rewrite == 'idna': - parts[index] = idna.encode(part).decode('ascii') - elif idn_rewrite == 'unicode' and part.startswith(u'xn--'): + if idn_rewrite == "idna": + parts[index] = idna.encode(part).decode("ascii") + elif idn_rewrite == "unicode" and part.startswith(u"xn--"): parts[index] = idna.decode(part) except idna.IDNAError as exc2008: try: - if idn_rewrite == 'idna': - parts[index] = part.encode('idna').decode('ascii') - elif idn_rewrite == 'unicode' and part.startswith(u'xn--'): - parts[index] = part.encode('ascii').decode('idna') + if idn_rewrite == "idna": + parts[index] = part.encode("idna").decode("ascii") + elif idn_rewrite == "unicode" and part.startswith(u"xn--"): + parts[index] = part.encode("ascii").decode("idna") except Exception as exc2003: raise OpenSSLObjectError( u'Error while transforming part "{part}" of {what} DNS name "{name}" to {dest}.' u' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'.format( part=part, name=value, - what='IDNA' if idn_rewrite == 'unicode' else 'Unicode', - dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA', + what="IDNA" if idn_rewrite == "unicode" else "Unicode", + dest="Unicode" if idn_rewrite == "unicode" else "IDNA", exc2003=exc2003, exc2008=exc2008, - )) - return u'.'.join(parts) + ) + ) + return u".".join(parts) def _adjust_idn_email(value, idn_rewrite): - idx = value.find(u'@') + idx = value.find(u"@") if idx < 0: return value - return u'{0}@{1}'.format(value[:idx], _adjust_idn(value[idx + 1:], idn_rewrite)) + return u"{0}@{1}".format(value[:idx], _adjust_idn(value[idx + 1 :], idn_rewrite)) def _adjust_idn_url(value, idn_rewrite): url = urlparse(value) host = _adjust_idn(url.hostname, idn_rewrite) if url.username is not None and url.password is not None: - host = u'{0}:{1}@{2}'.format(url.username, url.password, host) + host = u"{0}:{1}@{2}".format(url.username, url.password, host) elif url.username is not None: - host = u'{0}@{1}'.format(url.username, host) + host = u"{0}@{1}".format(url.username, host) if url.port is not None: - host = u'{0}:{1}'.format(host, url.port) + host = u"{0}:{1}".format(host, url.port) return urlunparse( - ParseResult(scheme=url.scheme, netloc=host, path=url.path, params=url.params, query=url.query, fragment=url.fragment)) + ParseResult( + scheme=url.scheme, + netloc=host, + path=url.path, + params=url.params, + query=url.query, + fragment=url.fragment, + ) + ) -def cryptography_get_name(name, what='Subject Alternative Name'): - ''' +def cryptography_get_name(name, what="Subject Alternative Name"): + """ Given a name string, returns a cryptography x509.GeneralName object. Raises an OpenSSLObjectError if the name is unknown or cannot be parsed. - ''' + """ try: - if name.startswith('DNS:'): - return x509.DNSName(_adjust_idn(to_text(name[4:]), 'idna')) - if name.startswith('IP:'): + if name.startswith("DNS:"): + return x509.DNSName(_adjust_idn(to_text(name[4:]), "idna")) + if name.startswith("IP:"): address = to_text(name[3:]) - if '/' in address: + if "/" in address: return x509.IPAddress(ipaddress.ip_network(address)) return x509.IPAddress(ipaddress.ip_address(address)) - if name.startswith('email:'): - return x509.RFC822Name(_adjust_idn_email(to_text(name[6:]), 'idna')) - if name.startswith('URI:'): - return x509.UniformResourceIdentifier(_adjust_idn_url(to_text(name[4:]), 'idna')) - if name.startswith('RID:'): - m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:])) + if name.startswith("email:"): + return x509.RFC822Name(_adjust_idn_email(to_text(name[6:]), "idna")) + if name.startswith("URI:"): + return x509.UniformResourceIdentifier( + _adjust_idn_url(to_text(name[4:]), "idna") + ) + if name.startswith("RID:"): + m = re.match(r"^([0-9]+(?:\.[0-9]+)*)$", to_text(name[4:])) if not m: - raise OpenSSLObjectError('Cannot parse {what} "{name}"'.format(name=name, what=what)) + raise OpenSSLObjectError( + 'Cannot parse {what} "{name}"'.format(name=name, what=what) + ) return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1))) - if name.startswith('otherName:'): + if name.startswith("otherName:"): # otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with. - m = re.match(r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:])) + m = re.match( + r"^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$", + to_text(name[10:]), + ) if m: - return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2))) + return x509.OtherName( + x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2)) + ) # See https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html - Subject Alternative Name for more # defailts on the format expected. - name = to_text(name[10:], errors='surrogate_or_strict') - if ';' not in name: - raise OpenSSLObjectError('Cannot parse {what} otherName "{name}", must be in the ' - 'format "otherName:;" or ' - '"otherName:;"'.format(name=name, what=what)) + name = to_text(name[10:], errors="surrogate_or_strict") + if ";" not in name: + raise OpenSSLObjectError( + 'Cannot parse {what} otherName "{name}", must be in the ' + 'format "otherName:;" or ' + '"otherName:;"'.format(name=name, what=what) + ) - oid, value = name.split(';', 1) + oid, value = name.split(";", 1) b_value = serialize_asn1_string_as_der(value) return x509.OtherName(x509.ObjectIdentifier(oid), b_value) - if name.startswith('dirName:'): - return x509.DirectoryName(x509.Name(reversed(_parse_dn(to_bytes(name[8:]))))) + if name.startswith("dirName:"): + return x509.DirectoryName( + x509.Name(reversed(_parse_dn(to_bytes(name[8:])))) + ) except Exception as e: - raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e)) - if ':' not in name: - raise OpenSSLObjectError('Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'.format(name=name, what=what)) - raise OpenSSLObjectError('Cannot parse {what} "{name}" (potentially unsupported by cryptography backend)'.format(name=name, what=what)) + raise OpenSSLObjectError( + 'Cannot parse {what} "{name}": {error}'.format( + name=name, what=what, error=e + ) + ) + if ":" not in name: + raise OpenSSLObjectError( + 'Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'.format( + name=name, what=what + ) + ) + raise OpenSSLObjectError( + 'Cannot parse {what} "{name}" (potentially unsupported by cryptography backend)'.format( + name=name, what=what + ) + ) def _dn_escape_value(value): - ''' + """ Escape Distinguished Name's attribute value. - ''' - value = value.replace(u'\\', u'\\\\') - for ch in [u',', u'+', u'<', u'>', u';', u'"']: - value = value.replace(ch, u'\\%s' % ch) - value = value.replace(u'\0', u'\\00') - if value.startswith((u' ', u'#')): - value = u'\\%s' % value[0] + value[1:] - if value.endswith(u' '): - value = value[:-1] + u'\\ ' + """ + value = value.replace(u"\\", u"\\\\") + for ch in [u",", u"+", u"<", u">", u";", u'"']: + value = value.replace(ch, u"\\%s" % ch) + value = value.replace(u"\0", u"\\00") + if value.startswith((u" ", u"#")): + value = u"\\%s" % value[0] + value[1:] + if value.endswith(u" "): + value = value[:-1] + u"\\ " return value -def cryptography_decode_name(name, idn_rewrite='ignore'): - ''' +def cryptography_decode_name(name, idn_rewrite="ignore"): + """ Given a cryptography x509.GeneralName object, returns a string. Raises an OpenSSLObjectError if the name is not supported. - ''' - if idn_rewrite not in ('ignore', 'idna', 'unicode'): - raise AssertionError('idn_rewrite must be one of "ignore", "idna", or "unicode"') + """ + if idn_rewrite not in ("ignore", "idna", "unicode"): + raise AssertionError( + 'idn_rewrite must be one of "ignore", "idna", or "unicode"' + ) if isinstance(name, x509.DNSName): - return u'DNS:{0}'.format(_adjust_idn(name.value, idn_rewrite)) + return u"DNS:{0}".format(_adjust_idn(name.value, idn_rewrite)) if isinstance(name, x509.IPAddress): if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)): - return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen) - return u'IP:{0}'.format(name.value.compressed) + return u"IP:{0}/{1}".format( + name.value.network_address.compressed, name.value.prefixlen + ) + return u"IP:{0}".format(name.value.compressed) if isinstance(name, x509.RFC822Name): - return u'email:{0}'.format(_adjust_idn_email(name.value, idn_rewrite)) + return u"email:{0}".format(_adjust_idn_email(name.value, idn_rewrite)) if isinstance(name, x509.UniformResourceIdentifier): - return u'URI:{0}'.format(_adjust_idn_url(name.value, idn_rewrite)) + return u"URI:{0}".format(_adjust_idn_url(name.value, idn_rewrite)) if isinstance(name, x509.DirectoryName): # According to https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1 the # list needs to be reversed, and joined by commas - return u'dirName:' + ','.join([ - u'{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value)) - for attribute in reversed(list(name.value)) - ]) + return u"dirName:" + ",".join( + [ + u"{0}={1}".format( + to_text(cryptography_oid_to_name(attribute.oid, short=True)), + _dn_escape_value(attribute.value), + ) + for attribute in reversed(list(name.value)) + ] + ) if isinstance(name, x509.RegisteredID): - return u'RID:{0}'.format(name.value.dotted_string) + return u"RID:{0}".format(name.value.dotted_string) if isinstance(name, x509.OtherName): - return u'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value)) + return u"otherName:{0};{1}".format( + name.type_id.dotted_string, _get_hex(name.value) + ) raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name)) def _cryptography_get_keyusage(usage): - ''' + """ Given a key usage identifier string, returns the parameter name used by cryptography's x509.KeyUsage(). Raises an OpenSSLObjectError if the identifier is unknown. - ''' - if usage in ('Digital Signature', 'digitalSignature'): - return 'digital_signature' - if usage in ('Non Repudiation', 'nonRepudiation'): - return 'content_commitment' - if usage in ('Key Encipherment', 'keyEncipherment'): - return 'key_encipherment' - if usage in ('Data Encipherment', 'dataEncipherment'): - return 'data_encipherment' - if usage in ('Key Agreement', 'keyAgreement'): - return 'key_agreement' - if usage in ('Certificate Sign', 'keyCertSign'): - return 'key_cert_sign' - if usage in ('CRL Sign', 'cRLSign'): - return 'crl_sign' - if usage in ('Encipher Only', 'encipherOnly'): - return 'encipher_only' - if usage in ('Decipher Only', 'decipherOnly'): - return 'decipher_only' + """ + if usage in ("Digital Signature", "digitalSignature"): + return "digital_signature" + if usage in ("Non Repudiation", "nonRepudiation"): + return "content_commitment" + if usage in ("Key Encipherment", "keyEncipherment"): + return "key_encipherment" + if usage in ("Data Encipherment", "dataEncipherment"): + return "data_encipherment" + if usage in ("Key Agreement", "keyAgreement"): + return "key_agreement" + if usage in ("Certificate Sign", "keyCertSign"): + return "key_cert_sign" + if usage in ("CRL Sign", "cRLSign"): + return "crl_sign" + if usage in ("Encipher Only", "encipherOnly"): + return "encipher_only" + if usage in ("Decipher Only", "decipherOnly"): + return "decipher_only" raise OpenSSLObjectError('Unknown key usage "{0}"'.format(usage)) def cryptography_parse_key_usage_params(usages): - ''' + """ Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage(). Raises an OpenSSLObjectError if an identifier is unknown. - ''' + """ params = dict( digital_signature=False, content_commitment=False, @@ -586,40 +676,52 @@ def cryptography_parse_key_usage_params(usages): def cryptography_get_basic_constraints(constraints): - ''' + """ Given a list of constraints, returns a tuple (ca, path_length). Raises an OpenSSLObjectError if a constraint is unknown or cannot be parsed. - ''' + """ ca = False path_length = None if constraints: for constraint in constraints: - if constraint.startswith('CA:'): - if constraint == 'CA:TRUE': + if constraint.startswith("CA:"): + if constraint == "CA:TRUE": ca = True - elif constraint == 'CA:FALSE': + elif constraint == "CA:FALSE": ca = False else: - raise OpenSSLObjectError('Unknown basic constraint value "{0}" for CA'.format(constraint[3:])) - elif constraint.startswith('pathlen:'): - v = constraint[len('pathlen:'):] + raise OpenSSLObjectError( + 'Unknown basic constraint value "{0}" for CA'.format( + constraint[3:] + ) + ) + elif constraint.startswith("pathlen:"): + v = constraint[len("pathlen:") :] try: path_length = int(v) except Exception as e: - raise OpenSSLObjectError('Cannot parse path length constraint "{0}" ({1})'.format(v, e)) + raise OpenSSLObjectError( + 'Cannot parse path length constraint "{0}" ({1})'.format(v, e) + ) else: - raise OpenSSLObjectError('Unknown basic constraint "{0}"'.format(constraint)) + raise OpenSSLObjectError( + 'Unknown basic constraint "{0}"'.format(constraint) + ) return ca, path_length def cryptography_key_needs_digest_for_signing(key): - '''Tests whether the given private key requires a digest algorithm for signing. + """Tests whether the given private key requires a digest algorithm for signing. Ed25519 and Ed448 keys do not; they need None to be passed as the digest algorithm. - ''' - 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 + ): return False - 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 + ): return False return True @@ -637,16 +739,22 @@ def _compare_public_keys(key1, key2, clazz): def cryptography_compare_public_keys(key1, key2): - '''Tests whether two public keys are the same. + """Tests whether two public keys are the same. Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers(). - ''' + """ if CRYPTOGRAPHY_HAS_ED25519: - res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey) + res = _compare_public_keys( + key1, + key2, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey, + ) if res is not None: return res if CRYPTOGRAPHY_HAS_ED448: - res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey) + res = _compare_public_keys( + key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey + ) if res is not None: return res return key1.public_numbers() == key2.public_numbers() @@ -663,41 +771,61 @@ def _compare_private_keys(key1, key2, clazz, has_no_private_bytes=False): # We do not have the private_bytes() function - compare associated public keys return cryptography_compare_public_keys(a.public_key(), b.public_key()) encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption() - a = key1.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm) - b = key2.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm) + a = key1.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + encryption_algorithm=encryption_algorithm, + ) + b = key2.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + encryption_algorithm=encryption_algorithm, + ) return a == b def cryptography_compare_private_keys(key1, key2): - '''Tests whether two private keys are the same. + """Tests whether two private keys are the same. Needs special logic for Ed25519, X25519, and Ed448 keys, since they do not have private_numbers(). - ''' + """ if CRYPTOGRAPHY_HAS_ED25519: - res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey) + res = _compare_private_keys( + key1, + key2, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey, + ) if res is not None: return res if CRYPTOGRAPHY_HAS_X25519: res = _compare_private_keys( - key1, key2, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey, has_no_private_bytes=not CRYPTOGRAPHY_HAS_X25519_FULL) + key1, + key2, + cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey, + has_no_private_bytes=not CRYPTOGRAPHY_HAS_X25519_FULL, + ) if res is not None: return res if CRYPTOGRAPHY_HAS_ED448: - res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey) + res = _compare_private_keys( + key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey + ) if res is not None: return res if CRYPTOGRAPHY_HAS_X448: - res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey) + res = _compare_private_keys( + key1, key2, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey + ) if res is not None: return res return key1.private_numbers() == key2.private_numbers() def cryptography_serial_number_of_cert(cert): - '''Returns cert.serial_number. + """Returns cert.serial_number. Also works for old versions of cryptography. - ''' + """ try: return cert.serial_number except AttributeError: @@ -706,10 +834,11 @@ def cryptography_serial_number_of_cert(cert): def parse_pkcs12(pkcs12_bytes, passphrase=None): - '''Returns a tuple (private_key, certificate, additional_certificates, friendly_name). - ''' + """Returns a tuple (private_key, certificate, additional_certificates, friendly_name).""" if _load_pkcs12 is None and _load_key_and_certificates is None: - raise ValueError('neither load_pkcs12() nor load_key_and_certificates() present in the current cryptography version') + raise ValueError( + "neither load_pkcs12() nor load_key_and_certificates() present in the current cryptography version" + ) if passphrase is not None: passphrase = to_bytes(passphrase) @@ -718,7 +847,7 @@ def parse_pkcs12(pkcs12_bytes, passphrase=None): if _load_pkcs12 is not None: return _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase) - if LooseVersion(cryptography.__version__) >= LooseVersion('35.0'): + if LooseVersion(cryptography.__version__) >= LooseVersion("35.0"): return _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase) return _parse_pkcs12_legacy(pkcs12_bytes, passphrase) @@ -739,7 +868,9 @@ def _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase=None): def _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase=None): # Backwards compatibility code for cryptography 35.x - private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase) + private_key, certificate, additional_certificates = _load_key_and_certificates( + pkcs12_bytes, passphrase + ) friendly_name = None if certificate: @@ -749,18 +880,26 @@ def _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase=None): # This code basically does what load_key_and_certificates() does, but without error-checking. # Since load_key_and_certificates succeeded, it should not fail. pkcs12 = backend._ffi.gc( - backend._lib.d2i_PKCS12_bio(backend._bytes_to_bio(pkcs12_bytes).bio, backend._ffi.NULL), - backend._lib.PKCS12_free) + backend._lib.d2i_PKCS12_bio( + backend._bytes_to_bio(pkcs12_bytes).bio, backend._ffi.NULL + ), + backend._lib.PKCS12_free, + ) certificate_x509_ptr = backend._ffi.new("X509 **") - with backend._zeroed_null_terminated_buf(to_bytes(passphrase) if passphrase is not None else None) as passphrase_buffer: + with backend._zeroed_null_terminated_buf( + to_bytes(passphrase) if passphrase is not None else None + ) as passphrase_buffer: backend._lib.PKCS12_parse( pkcs12, passphrase_buffer, backend._ffi.new("EVP_PKEY **"), certificate_x509_ptr, - backend._ffi.new("Cryptography_STACK_OF_X509 **")) + backend._ffi.new("Cryptography_STACK_OF_X509 **"), + ) if certificate_x509_ptr[0] != backend._ffi.NULL: - maybe_name = backend._lib.X509_alias_get0(certificate_x509_ptr[0], backend._ffi.NULL) + maybe_name = backend._lib.X509_alias_get0( + certificate_x509_ptr[0], backend._ffi.NULL + ) if maybe_name != backend._ffi.NULL: friendly_name = backend._ffi.string(maybe_name) @@ -769,7 +908,9 @@ def _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase=None): def _parse_pkcs12_legacy(pkcs12_bytes, passphrase=None): # Backwards compatibility code for cryptography < 35.0.0 - private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase) + private_key, certificate, additional_certificates = _load_key_and_certificates( + pkcs12_bytes, passphrase + ) friendly_name = None if certificate: @@ -782,39 +923,62 @@ def _parse_pkcs12_legacy(pkcs12_bytes, passphrase=None): def cryptography_verify_signature(signature, data, hash_algorithm, signer_public_key): - ''' + """ Check whether the given signature of the given data was signed by the given public key object. - ''' + """ try: - if CRYPTOGRAPHY_HAS_RSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): - signer_public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) + if CRYPTOGRAPHY_HAS_RSA_SIGN and isinstance( + signer_public_key, + cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey, + ): + signer_public_key.verify( + signature, data, padding.PKCS1v15(), hash_algorithm + ) return True - if CRYPTOGRAPHY_HAS_EC_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): - signer_public_key.verify(signature, data, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hash_algorithm)) + if CRYPTOGRAPHY_HAS_EC_SIGN and isinstance( + signer_public_key, + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, + ): + signer_public_key.verify( + signature, + data, + cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hash_algorithm), + ) return True - if CRYPTOGRAPHY_HAS_DSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey): + if CRYPTOGRAPHY_HAS_DSA_SIGN and isinstance( + signer_public_key, + cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey, + ): signer_public_key.verify(signature, data, hash_algorithm) return True - if CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey): + if CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance( + signer_public_key, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey, + ): signer_public_key.verify(signature, data) return True - if CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey): + if CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance( + signer_public_key, + cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey, + ): signer_public_key.verify(signature, data) return True - raise OpenSSLObjectError(u'Unsupported public key type {0}'.format(type(signer_public_key))) + raise OpenSSLObjectError( + u"Unsupported public key type {0}".format(type(signer_public_key)) + ) except InvalidSignature: return False def cryptography_verify_certificate_signature(certificate, signer_public_key): - ''' + """ Check whether the given X509 certificate object was signed by the given public key object. - ''' + """ return cryptography_verify_signature( certificate.signature, certificate.tbs_certificate_bytes, certificate.signature_hash_algorithm, - signer_public_key + signer_public_key, ) diff --git a/plugins/module_utils/crypto/math.py b/plugins/module_utils/crypto/math.py index 9c2f2d2c..010aa6d3 100644 --- a/plugins/module_utils/crypto/math.py +++ b/plugins/module_utils/crypto/math.py @@ -14,7 +14,7 @@ import sys def binary_exp_mod(f, e, m): - '''Computes f^e mod m in O(log e) multiplications modulo m.''' + """Computes f^e mod m in O(log e) multiplications modulo m.""" # Compute len_e = floor(log_2(e)) len_e = -1 x = e @@ -31,18 +31,18 @@ def binary_exp_mod(f, e, m): def simple_gcd(a, b): - '''Compute GCD of its two inputs.''' + """Compute GCD of its two inputs.""" while b != 0: a, b = b, a % b return a def quick_is_not_prime(n): - '''Does some quick checks to see if we can poke a hole into the primality of n. + """Does some quick checks to see if we can poke a hole into the primality of n. A result of `False` does **not** mean that the number is prime; it just means that we could not detect quickly whether it is not prime. - ''' + """ if n <= 2: return n < 2 # The constant in the next line is the product of all primes < 200 @@ -52,9 +52,52 @@ def quick_is_not_prime(n): if n < 200 and gcd == n: # Explicitly check for all primes < 200 return n not in ( - 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, - 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, - 181, 191, 193, 197, 199, + 2, + 3, + 5, + 7, + 11, + 13, + 17, + 19, + 23, + 29, + 31, + 37, + 41, + 43, + 47, + 53, + 59, + 61, + 67, + 71, + 73, + 79, + 83, + 89, + 97, + 101, + 103, + 107, + 109, + 113, + 127, + 131, + 137, + 139, + 149, + 151, + 157, + 163, + 167, + 173, + 179, + 181, + 191, + 193, + 197, + 199, ) return True # TODO: maybe do some iterations of Miller-Rabin to increase confidence @@ -83,6 +126,7 @@ if python_version >= (2, 7) or python_version >= (3, 1): if no == 0: return 0 return no.bit_length() + else: # Slow, but works def count_bytes(no): @@ -107,25 +151,27 @@ else: count += 1 return count + if sys.version_info[0] >= 3: # Python 3 (and newer) def _convert_int_to_bytes(count, no): - return no.to_bytes(count, byteorder='big') + return no.to_bytes(count, byteorder="big") def _convert_bytes_to_int(data): - return int.from_bytes(data, byteorder='big', signed=False) + return int.from_bytes(data, byteorder="big", signed=False) def _to_hex(no): return hex(no)[2:] + else: # Python 2 def _convert_int_to_bytes(count, n): if n == 0 and count == 0: - return '' - h = '%x' % n + return "" + h = "%x" % n if len(h) > 2 * count: - raise Exception('Number {1} needs more than {0} bytes!'.format(count, n)) - return ('0' * (2 * count - len(h)) + h).decode('hex') + raise Exception("Number {1} needs more than {0} bytes!".format(count, n)) + return ("0" * (2 * count - len(h)) + h).decode("hex") def _convert_bytes_to_int(data): v = 0 @@ -134,7 +180,7 @@ else: return v def _to_hex(no): - return '%x' % no + return "%x" % no def convert_int_to_bytes(no, count=None): @@ -164,7 +210,7 @@ def convert_int_to_hex(no, digits=None): no = abs(no) value = _to_hex(no) if digits is not None and len(value) < digits: - value = '0' * (digits - len(value)) + value + value = "0" * (digits - len(value)) + value return value diff --git a/plugins/module_utils/crypto/module_backends/certificate.py b/plugins/module_utils/crypto/module_backends/certificate.py index 964ee93d..d7920ccd 100644 --- a/plugins/module_utils/crypto/module_backends/certificate.py +++ b/plugins/module_utils/crypto/module_backends/certificate.py @@ -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"], ], ) diff --git a/plugins/module_utils/crypto/module_backends/certificate_acme.py b/plugins/module_utils/crypto/module_backends/certificate_acme.py index 60cc7b8e..a1d6bf02 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_acme.py +++ b/plugins/module_utils/crypto/module_backends/certificate_acme.py @@ -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" + ), + ) + ) diff --git a/plugins/module_utils/crypto/module_backends/certificate_entrust.py b/plugins/module_utils/crypto/module_backends/certificate_entrust.py index 0f1fe296..737c5b3f 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_entrust.py +++ b/plugins/module_utils/crypto/module_backends/certificate_entrust.py @@ -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", + ], + ] ) diff --git a/plugins/module_utils/crypto/module_backends/certificate_info.py b/plugins/module_utils/crypto/module_backends/certificate_info.py index c82ace4e..73709848 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_info.py +++ b/plugins/module_utils/crypto/module_backends/certificate_info.py @@ -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)) diff --git a/plugins/module_utils/crypto/module_backends/certificate_ownca.py b/plugins/module_utils/crypto/module_backends/certificate_ownca.py index e35afd42..91f8b7a4 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_ownca.py +++ b/plugins/module_utils/crypto/module_backends/certificate_ownca.py @@ -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"], + ] + ) diff --git a/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py b/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py index 94f80cae..beeb08b4 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py +++ b/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py @@ -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"], + ), + ) + ) diff --git a/plugins/module_utils/crypto/module_backends/common.py b/plugins/module_utils/crypto/module_backends/common.py index 8466bc11..808f2642 100644 --- a/plugins/module_utils/crypto/module_backends/common.py +++ b/plugins/module_utils/crypto/module_backends/common.py @@ -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") diff --git a/plugins/module_utils/crypto/module_backends/crl_info.py b/plugins/module_utils/crypto/module_backends/crl_info.py index 56d23f24..37dcd6dd 100644 --- a/plugins/module_utils/crypto/module_backends/crl_info.py +++ b/plugins/module_utils/crypto/module_backends/crl_info.py @@ -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() diff --git a/plugins/module_utils/crypto/module_backends/csr.py b/plugins/module_utils/crypto/module_backends/csr.py index 2d425423..5273e042 100644 --- a/plugins/module_utils/crypto/module_backends/csr.py +++ b/plugins/module_utils/crypto/module_backends/csr.py @@ -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"], ], ) diff --git a/plugins/module_utils/crypto/module_backends/csr_info.py b/plugins/module_utils/crypto/module_backends/csr_info.py index 967199a3..f665aa32 100644 --- a/plugins/module_utils/crypto/module_backends/csr_info.py +++ b/plugins/module_utils/crypto/module_backends/csr_info.py @@ -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)) diff --git a/plugins/module_utils/crypto/module_backends/privatekey.py b/plugins/module_utils/crypto/module_backends/privatekey.py index 7d84ce57..2e473f14 100644 --- a/plugins/module_utils/crypto/module_backends/privatekey.py +++ b/plugins/module_utils/crypto/module_backends/privatekey.py @@ -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"]], ], ) diff --git a/plugins/module_utils/crypto/module_backends/privatekey_convert.py b/plugins/module_utils/crypto/module_backends/privatekey_convert.py index 9ab870e8..db71d5ef 100644 --- a/plugins/module_utils/crypto/module_backends/privatekey_convert.py +++ b/plugins/module_utils/crypto/module_backends/privatekey_convert.py @@ -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"], ], ) diff --git a/plugins/module_utils/crypto/module_backends/privatekey_info.py b/plugins/module_utils/crypto/module_backends/privatekey_info.py index 47d73ca1..a0912102 100644 --- a/plugins/module_utils/crypto/module_backends/privatekey_info.py +++ b/plugins/module_utils/crypto/module_backends/privatekey_info.py @@ -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)) diff --git a/plugins/module_utils/crypto/module_backends/publickey_info.py b/plugins/module_utils/crypto/module_backends/publickey_info.py index 04bc0b6c..59a31ba2 100644 --- a/plugins/module_utils/crypto/module_backends/publickey_info.py +++ b/plugins/module_utils/crypto/module_backends/publickey_info.py @@ -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)) diff --git a/plugins/module_utils/crypto/pem.py b/plugins/module_utils/crypto/pem.py index 7ba634fc..e61dbbbc 100644 --- a/plugins/module_utils/crypto/pem.py +++ b/plugins/module_utils/crypto/pem.py @@ -10,29 +10,33 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -PEM_START = '-----BEGIN ' -PEM_END_START = '-----END ' -PEM_END = '-----' -PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY') -PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY' +PEM_START = "-----BEGIN " +PEM_END_START = "-----END " +PEM_END = "-----" +PKCS8_PRIVATEKEY_NAMES = ("PRIVATE KEY", "ENCRYPTED PRIVATE KEY") +PKCS1_PRIVATEKEY_SUFFIX = " PRIVATE KEY" -def identify_pem_format(content, encoding='utf-8'): - '''Given the contents of a binary file, tests whether this could be a PEM file.''' +def identify_pem_format(content, encoding="utf-8"): + """Given the contents of a binary file, tests whether this could be a PEM file.""" try: first_pem = extract_first_pem(content.decode(encoding)) if first_pem is None: return False lines = first_pem.splitlines(False) - if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END): + if ( + lines[0].startswith(PEM_START) + and lines[0].endswith(PEM_END) + and len(lines[0]) > len(PEM_START) + len(PEM_END) + ): return True except UnicodeDecodeError: pass return False -def identify_private_key_format(content, encoding='utf-8'): - '''Given the contents of a private key file, identifies its format.''' +def identify_private_key_format(content, encoding="utf-8"): + """Given the contents of a private key file, identifies its format.""" # See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85 # (PEM_read_bio_PrivateKey) # and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47 @@ -40,42 +44,48 @@ def identify_private_key_format(content, encoding='utf-8'): try: first_pem = extract_first_pem(content.decode(encoding)) if first_pem is None: - return 'raw' + return "raw" lines = first_pem.splitlines(False) - if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END): - name = lines[0][len(PEM_START):-len(PEM_END)] + if ( + lines[0].startswith(PEM_START) + and lines[0].endswith(PEM_END) + and len(lines[0]) > len(PEM_START) + len(PEM_END) + ): + name = lines[0][len(PEM_START) : -len(PEM_END)] if name in PKCS8_PRIVATEKEY_NAMES: - return 'pkcs8' - if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(PKCS1_PRIVATEKEY_SUFFIX): - return 'pkcs1' - return 'unknown-pem' + return "pkcs8" + if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith( + PKCS1_PRIVATEKEY_SUFFIX + ): + return "pkcs1" + return "unknown-pem" except UnicodeDecodeError: pass - return 'raw' + return "raw" def split_pem_list(text, keep_inbetween=False): - ''' + """ Split concatenated PEM objects into a list of strings, where each is one PEM object. - ''' + """ result = [] current = [] if keep_inbetween else None for line in text.splitlines(True): if line.strip(): - if not keep_inbetween and line.startswith('-----BEGIN '): + if not keep_inbetween and line.startswith("-----BEGIN "): current = [] if current is not None: current.append(line) - if line.startswith('-----END '): - result.append(''.join(current)) + if line.startswith("-----END "): + result.append("".join(current)) current = [] if keep_inbetween else None return result def extract_first_pem(text): - ''' + """ Given one PEM or multiple concatenated PEM objects, return only the first one, or None if there is none. - ''' + """ all_pems = split_pem_list(text) if not all_pems: return None @@ -87,24 +97,42 @@ def _extract_type(line, start=PEM_START): return None if not line.endswith(PEM_END): return None - return line[len(start):-len(PEM_END)] + return line[len(start) : -len(PEM_END)] def extract_pem(content, strict=False): lines = content.splitlines() if len(lines) < 3: - raise ValueError('PEM must have at least 3 lines, have only {count}'.format(count=len(lines))) + raise ValueError( + "PEM must have at least 3 lines, have only {count}".format(count=len(lines)) + ) header_type = _extract_type(lines[0]) if header_type is None: - raise ValueError('First line is not of format {start}...{end}: {line!r}'.format(start=PEM_START, end=PEM_END, line=lines[0])) + raise ValueError( + "First line is not of format {start}...{end}: {line!r}".format( + start=PEM_START, end=PEM_END, line=lines[0] + ) + ) footer_type = _extract_type(lines[-1], start=PEM_END_START) if strict: if header_type != footer_type: - raise ValueError('Header type ({header}) is different from footer type ({footer})'.format(header=header_type, footer=footer_type)) + raise ValueError( + "Header type ({header}) is different from footer type ({footer})".format( + header=header_type, footer=footer_type + ) + ) for idx, line in enumerate(lines[1:-2]): if len(line) != 64: - raise ValueError('Line {idx} has length {len} instead of 64'.format(idx=idx, len=len(line))) + raise ValueError( + "Line {idx} has length {len} instead of 64".format( + idx=idx, len=len(line) + ) + ) if not (0 < len(lines[-2]) <= 64): - raise ValueError('Last line has length {len}, should be in (0, 64]'.format(len=len(lines[-2]))) + raise ValueError( + "Last line has length {len}, should be in (0, 64]".format( + len=len(lines[-2]) + ) + ) content = lines[1:-1] - return header_type, ''.join(content) + return header_type, "".join(content) diff --git a/plugins/module_utils/crypto/support.py b/plugins/module_utils/crypto/support.py index 4f6ee622..931113c3 100644 --- a/plugins/module_utils/crypto/support.py +++ b/plugins/module_utils/crypto/support.py @@ -32,6 +32,7 @@ from ansible_collections.community.crypto.plugins.module_utils.time import ( # try: from OpenSSL import crypto + HAS_PYOPENSSL = True except (ImportError, AttributeError): # Error handled in the calling module. @@ -52,7 +53,14 @@ from .basic import OpenSSLBadPassphraseError, OpenSSLObjectError # This list of preferred fingerprints is used when prefer_one=True is supplied to the # fingerprinting methods. PREFERRED_FINGERPRINTS = ( - 'sha256', 'sha3_256', 'sha512', 'sha3_512', 'sha384', 'sha3_384', 'sha1', 'md5' + "sha256", + "sha3_256", + "sha512", + "sha3_512", + "sha384", + "sha3_384", + "sha1", + "md5", ) @@ -71,8 +79,16 @@ def get_fingerprint_of_bytes(source, prefer_one=False): if prefer_one: # Sort algorithms to have the ones in PREFERRED_FINGERPRINTS at the beginning - prefered_algorithms = [algorithm for algorithm in PREFERRED_FINGERPRINTS if algorithm in algorithms] - prefered_algorithms += sorted([algorithm for algorithm in algorithms if algorithm not in PREFERRED_FINGERPRINTS]) + prefered_algorithms = [ + algorithm for algorithm in PREFERRED_FINGERPRINTS if algorithm in algorithms + ] + prefered_algorithms += sorted( + [ + algorithm + for algorithm in algorithms + if algorithm not in PREFERRED_FINGERPRINTS + ] + ) algorithms = prefered_algorithms for algo in algorithms: @@ -88,34 +104,47 @@ def get_fingerprint_of_bytes(source, prefer_one=False): pubkey_digest = h.hexdigest() except TypeError: pubkey_digest = h.hexdigest(32) - fingerprint[algo] = ':'.join(pubkey_digest[i:i + 2] for i in range(0, len(pubkey_digest), 2)) + fingerprint[algo] = ":".join( + pubkey_digest[i : i + 2] for i in range(0, len(pubkey_digest), 2) + ) if prefer_one: break return fingerprint -def get_fingerprint_of_privatekey(privatekey, backend='cryptography', prefer_one=False): - """Generate the fingerprint of the public key. """ +def get_fingerprint_of_privatekey(privatekey, backend="cryptography", prefer_one=False): + """Generate the fingerprint of the public key.""" - if backend == 'cryptography': + if backend == "cryptography": publickey = privatekey.public_key().public_bytes( - serialization.Encoding.DER, - serialization.PublicFormat.SubjectPublicKeyInfo + serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo ) return get_fingerprint_of_bytes(publickey, prefer_one=prefer_one) -def get_fingerprint(path, passphrase=None, content=None, backend='cryptography', prefer_one=False): - """Generate the fingerprint of the public key. """ +def get_fingerprint( + path, passphrase=None, content=None, backend="cryptography", prefer_one=False +): + """Generate the fingerprint of the public key.""" - privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend) + privatekey = load_privatekey( + path, + passphrase=passphrase, + content=content, + check_passphrase=False, + backend=backend, + ) - return get_fingerprint_of_privatekey(privatekey, backend=backend, prefer_one=prefer_one) + return get_fingerprint_of_privatekey( + privatekey, backend=backend, prefer_one=prefer_one + ) -def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='cryptography'): +def load_privatekey( + path, passphrase=None, check_passphrase=True, content=None, backend="cryptography" +): """Load the specified OpenSSL private key. The content can also be specified via content; in that case, @@ -124,58 +153,72 @@ def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, try: if content is None: - with open(path, 'rb') as b_priv_key_fh: + with open(path, "rb") as b_priv_key_fh: priv_key_detail = b_priv_key_fh.read() else: priv_key_detail = content except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) - if backend == 'pyopenssl': + if backend == "pyopenssl": # First try: try to load with real passphrase (resp. empty string) # Will work if this is the correct passphrase, or the key is not # password-protected. try: - result = crypto.load_privatekey(crypto.FILETYPE_PEM, - priv_key_detail, - to_bytes(passphrase or '')) + result = crypto.load_privatekey( + crypto.FILETYPE_PEM, priv_key_detail, to_bytes(passphrase or "") + ) except crypto.Error as e: if len(e.args) > 0 and len(e.args[0]) > 0: - if e.args[0][0][2] in ('bad decrypt', 'bad password read'): + if e.args[0][0][2] in ("bad decrypt", "bad password read"): # This happens in case we have the wrong passphrase. if passphrase is not None: - raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!') + raise OpenSSLBadPassphraseError( + "Wrong passphrase provided for private key!" + ) else: - raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') - raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e)) + raise OpenSSLBadPassphraseError( + "No passphrase provided, but private key is password-protected!" + ) + raise OpenSSLObjectError("Error while deserializing key: {0}".format(e)) if check_passphrase: # Next we want to make sure that the key is actually protected by # a passphrase (in case we did try the empty string before, make # sure that the key is not protected by the empty string) try: - crypto.load_privatekey(crypto.FILETYPE_PEM, - priv_key_detail, - to_bytes('y' if passphrase == 'x' else 'x')) + crypto.load_privatekey( + crypto.FILETYPE_PEM, + priv_key_detail, + to_bytes("y" if passphrase == "x" else "x"), + ) if passphrase is not None: # Since we can load the key without an exception, the # key is not password-protected - raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!') + raise OpenSSLBadPassphraseError( + "Passphrase provided, but private key is not password-protected!" + ) except crypto.Error as e: if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0: - if e.args[0][0][2] in ('bad decrypt', 'bad password read'): + if e.args[0][0][2] in ("bad decrypt", "bad password read"): # The key is obviously protected by the empty string. # Do not do this at home (if it is possible at all)... - raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') - elif backend == 'cryptography': + raise OpenSSLBadPassphraseError( + "No passphrase provided, but private key is password-protected!" + ) + elif backend == "cryptography": try: - result = load_pem_private_key(priv_key_detail, - None if passphrase is None else to_bytes(passphrase), - cryptography_backend()) + result = load_pem_private_key( + priv_key_detail, + None if passphrase is None else to_bytes(passphrase), + cryptography_backend(), + ) except TypeError: - raise OpenSSLBadPassphraseError('Wrong or empty passphrase provided for private key') + raise OpenSSLBadPassphraseError( + "Wrong or empty passphrase provided for private key" + ) except ValueError: - raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key') + raise OpenSSLBadPassphraseError("Wrong passphrase provided for private key") return result @@ -183,60 +226,72 @@ def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, def load_publickey(path=None, content=None, backend=None): if content is None: if path is None: - raise OpenSSLObjectError('Must provide either path or content') + raise OpenSSLObjectError("Must provide either path or content") try: - with open(path, 'rb') as b_priv_key_fh: + with open(path, "rb") as b_priv_key_fh: content = b_priv_key_fh.read() except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) - if backend == 'cryptography': + if backend == "cryptography": try: - return serialization.load_pem_public_key(content, backend=cryptography_backend()) + return serialization.load_pem_public_key( + content, backend=cryptography_backend() + ) except Exception as e: - raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e)) + raise OpenSSLObjectError("Error while deserializing key: {0}".format(e)) -def load_certificate(path, content=None, backend='cryptography', der_support_enabled=False): +def load_certificate( + path, content=None, backend="cryptography", der_support_enabled=False +): """Load the specified certificate.""" try: if content is None: - with open(path, 'rb') as cert_fh: + with open(path, "rb") as cert_fh: cert_content = cert_fh.read() else: cert_content = content except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) - if backend == 'pyopenssl': + if backend == "pyopenssl": if der_support_enabled is False or identify_pem_format(cert_content): return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content) elif der_support_enabled: - raise OpenSSLObjectError('Certificate in DER format is not supported by the pyopenssl backend.') - elif backend == 'cryptography': + raise OpenSSLObjectError( + "Certificate in DER format is not supported by the pyopenssl backend." + ) + elif backend == "cryptography": if der_support_enabled is False or identify_pem_format(cert_content): try: - return x509.load_pem_x509_certificate(cert_content, cryptography_backend()) + return x509.load_pem_x509_certificate( + cert_content, cryptography_backend() + ) except ValueError as exc: raise OpenSSLObjectError(exc) elif der_support_enabled: try: - return x509.load_der_x509_certificate(cert_content, cryptography_backend()) + return x509.load_der_x509_certificate( + cert_content, cryptography_backend() + ) except ValueError as exc: - raise OpenSSLObjectError('Cannot parse DER certificate: {0}'.format(exc)) + raise OpenSSLObjectError( + "Cannot parse DER certificate: {0}".format(exc) + ) -def load_certificate_request(path, content=None, backend='cryptography'): +def load_certificate_request(path, content=None, backend="cryptography"): """Load the specified certificate signing request.""" try: if content is None: - with open(path, 'rb') as csr_fh: + with open(path, "rb") as csr_fh: csr_content = csr_fh.read() else: csr_content = content except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) - if backend == 'cryptography': + if backend == "cryptography": try: return x509.load_pem_x509_csr(csr_content, cryptography_backend()) except ValueError as exc: @@ -245,23 +300,40 @@ def load_certificate_request(path, content=None, backend='cryptography'): def parse_name_field(input_dict, name_field_name=None): """Take a dict with key: value or key: list_of_values mappings and return a list of tuples""" - error_str = '{key}' if name_field_name is None else '{key} in {name}' + error_str = "{key}" if name_field_name is None else "{key} in {name}" result = [] for key, value in input_dict.items(): if isinstance(value, list): for entry in value: if not isinstance(entry, six.string_types): - raise TypeError(('Values %s must be strings' % error_str).format(key=key, name=name_field_name)) + raise TypeError( + ("Values %s must be strings" % error_str).format( + key=key, name=name_field_name + ) + ) if not entry: - raise ValueError(('Values for %s must not be empty strings' % error_str).format(key=key)) + raise ValueError( + ("Values for %s must not be empty strings" % error_str).format( + key=key + ) + ) result.append((key, entry)) elif isinstance(value, six.string_types): if not value: - raise ValueError(('Value for %s must not be an empty string' % error_str).format(key=key)) + raise ValueError( + ("Value for %s must not be an empty string" % error_str).format( + key=key + ) + ) result.append((key, value)) else: - raise TypeError(('Value for %s must be either a string or a list of strings' % error_str).format(key=key)) + raise TypeError( + ( + "Value for %s must be either a string or a list of strings" + % error_str + ).format(key=key) + ) return result @@ -272,28 +344,32 @@ def parse_ordered_name_field(input_list, name_field_name): for index, entry in enumerate(input_list): if len(entry) != 1: raise ValueError( - 'Entry #{index} in {name} must be a dictionary with exactly one key-value pair'.format( - name=name_field_name, index=index + 1)) + "Entry #{index} in {name} must be a dictionary with exactly one key-value pair".format( + name=name_field_name, index=index + 1 + ) + ) try: result.extend(parse_name_field(entry, name_field_name=name_field_name)) except (TypeError, ValueError) as exc: raise ValueError( - 'Error while processing entry #{index} in {name}: {error}'.format( - name=name_field_name, index=index + 1, error=exc)) + "Error while processing entry #{index} in {name}: {error}".format( + name=name_field_name, index=index + 1, error=exc + ) + ) return result def select_message_digest(digest_string): digest = None - if digest_string == 'sha256': + if digest_string == "sha256": digest = hashes.SHA256() - elif digest_string == 'sha384': + elif digest_string == "sha384": digest = hashes.SHA384() - elif digest_string == 'sha512': + elif digest_string == "sha512": digest = hashes.SHA512() - elif digest_string == 'sha1': + elif digest_string == "sha1": digest = hashes.SHA1() - elif digest_string == 'md5': + elif digest_string == "md5": digest = hashes.MD5() return digest @@ -317,7 +393,7 @@ class OpenSSLObject(object): def _check_perms(module): file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): return False return not module.set_fs_attributes_if_different(file_args, False) diff --git a/plugins/module_utils/ecs/api.py b/plugins/module_utils/ecs/api.py index fa5c0b11..b0153dc3 100644 --- a/plugins/module_utils/ecs/api.py +++ b/plugins/module_utils/ecs/api.py @@ -41,22 +41,25 @@ valid_file_format = re.compile(r".*(\.)(yml|yaml|json)$") def ecs_client_argument_spec(): return dict( - entrust_api_user=dict(type='str', required=True), - entrust_api_key=dict(type='str', required=True, no_log=True), - entrust_api_client_cert_path=dict(type='path', required=True), - entrust_api_client_cert_key_path=dict(type='path', required=True, no_log=True), - entrust_api_specification_path=dict(type='path', default='https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml'), + entrust_api_user=dict(type="str", required=True), + entrust_api_key=dict(type="str", required=True, no_log=True), + entrust_api_client_cert_path=dict(type="path", required=True), + entrust_api_client_cert_key_path=dict(type="path", required=True, no_log=True), + entrust_api_specification_path=dict( + type="path", + default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml", + ), ) class SessionConfigurationException(Exception): - """ Raised if we cannot configure a session with the API """ + """Raised if we cannot configure a session with the API""" pass class RestOperationException(Exception): - """ Encapsulate a REST API error """ + """Encapsulate a REST API error""" def __init__(self, error): self.status = to_native(error.get("status", None)) @@ -106,7 +109,12 @@ class RestOperation(object): self.parameters = {} else: self.parameters = parameters - self.url = "{scheme}://{host}{base_path}{uri}".format(scheme="https", host=session._spec.get("host"), base_path=session._spec.get("basePath"), uri=uri) + self.url = "{scheme}://{host}{base_path}{uri}".format( + scheme="https", + host=session._spec.get("host"), + base_path=session._spec.get("basePath"), + uri=uri, + ) def restmethod(self, *args, **kwargs): """Do the hard work of making the request here""" @@ -145,7 +153,9 @@ class RestOperation(object): try: if body_parameters: body_parameters_json = json.dumps(body_parameters) - response = self.session.request.open(method=self.method, url=url, data=body_parameters_json) + response = self.session.request.open( + method=self.method, url=url, data=body_parameters_json + ) else: response = self.session.request.open(method=self.method, url=url) except HTTPError as e: @@ -167,11 +177,13 @@ class RestOperation(object): raise RestOperationException(result) # Raise a generic RestOperationException if this fails - raise RestOperationException({"status": result_code, "errors": [{"message": "REST Operation Failed"}]}) + raise RestOperationException( + {"status": result_code, "errors": [{"message": "REST Operation Failed"}]} + ) class Resource(object): - """ Implement basic CRUD operations against a path. """ + """Implement basic CRUD operations against a path.""" def __init__(self, session): self.session = session @@ -196,13 +208,20 @@ class Resource(object): elif method.lower() == "patch": operation_name = "Patch" else: - raise SessionConfigurationException(to_native("Invalid REST method type {0}".format(method))) + raise SessionConfigurationException( + to_native("Invalid REST method type {0}".format(method)) + ) # Get the non-parameter parts of the URL and append to the operation name # e.g /application/version -> GetApplicationVersion # e.g. /application/{id} -> GetApplication # This may lead to duplicates, which we must prevent. - operation_name += re.sub(r"{(.*)}", "", url).replace("/", " ").title().replace(" ", "") + operation_name += ( + re.sub(r"{(.*)}", "", url) + .replace("/", " ") + .title() + .replace(" ", "") + ) operation_spec["operationId"] = operation_name op = RestOperation(session, url, method, parameters) @@ -244,7 +263,9 @@ class ECSSession(object): self.request.url_username = entrust_api_user self.request.url_password = entrust_api_key else: - raise SessionConfigurationException(to_native("User and key must be provided.")) + raise SessionConfigurationException( + to_native("User and key must be provided.") + ) # set up client certificate if passed (support all-in one or cert + key) entrust_api_cert = self.get_config("entrust_api_cert") @@ -254,45 +275,78 @@ class ECSSession(object): if entrust_api_cert_key: self.request.client_key = entrust_api_cert_key else: - raise SessionConfigurationException(to_native("Client certificate for authentication to the API must be provided.")) + raise SessionConfigurationException( + to_native( + "Client certificate for authentication to the API must be provided." + ) + ) # set up the spec - entrust_api_specification_path = self.get_config("entrust_api_specification_path") + entrust_api_specification_path = self.get_config( + "entrust_api_specification_path" + ) - if not entrust_api_specification_path.startswith("http") and not os.path.isfile(entrust_api_specification_path): - raise SessionConfigurationException(to_native("OpenAPI specification was not found at location {0}.".format(entrust_api_specification_path))) + if not entrust_api_specification_path.startswith("http") and not os.path.isfile( + entrust_api_specification_path + ): + raise SessionConfigurationException( + to_native( + "OpenAPI specification was not found at location {0}.".format( + entrust_api_specification_path + ) + ) + ) if not valid_file_format.match(entrust_api_specification_path): - raise SessionConfigurationException(to_native("OpenAPI specification filename must end in .json, .yml or .yaml")) + raise SessionConfigurationException( + to_native( + "OpenAPI specification filename must end in .json, .yml or .yaml" + ) + ) self.verify = True if entrust_api_specification_path.startswith("http"): try: - http_response = Request().open(method="GET", url=entrust_api_specification_path) + http_response = Request().open( + method="GET", url=entrust_api_specification_path + ) http_response_contents = http_response.read() if entrust_api_specification_path.endswith(".json"): self._spec = json.load(http_response_contents) - elif entrust_api_specification_path.endswith(".yml") or entrust_api_specification_path.endswith(".yaml"): + elif entrust_api_specification_path.endswith( + ".yml" + ) or entrust_api_specification_path.endswith(".yaml"): self._spec = yaml.safe_load(http_response_contents) except HTTPError as e: - raise SessionConfigurationException(to_native("Error downloading specification from address '{0}', received error code '{1}'".format( - entrust_api_specification_path, e.getcode()))) + raise SessionConfigurationException( + to_native( + "Error downloading specification from address '{0}', received error code '{1}'".format( + entrust_api_specification_path, e.getcode() + ) + ) + ) else: with open(entrust_api_specification_path) as f: if ".json" in entrust_api_specification_path: self._spec = json.load(f) - elif ".yml" in entrust_api_specification_path or ".yaml" in entrust_api_specification_path: + elif ( + ".yml" in entrust_api_specification_path + or ".yaml" in entrust_api_specification_path + ): self._spec = yaml.safe_load(f) def get_config(self, item): return self._config.get(item, None) def _read_config_vars(self, name, **kwargs): - """ Read configuration from variables passed to the module. """ + """Read configuration from variables passed to the module.""" config = {} entrust_api_specification_path = kwargs.get("entrust_api_specification_path") - if not entrust_api_specification_path or (not entrust_api_specification_path.startswith("http") and not os.path.isfile(entrust_api_specification_path)): + if not entrust_api_specification_path or ( + not entrust_api_specification_path.startswith("http") + and not os.path.isfile(entrust_api_specification_path) + ): raise SessionConfigurationException( to_native( "Parameter provided for entrust_api_specification_path of value '{0}' was not a valid file path or HTTPS address.".format( @@ -305,30 +359,50 @@ class ECSSession(object): file_path = kwargs.get(required_file) if not file_path or not os.path.isfile(file_path): raise SessionConfigurationException( - to_native("Parameter provided for {0} of value '{1}' was not a valid file path.".format(required_file, file_path)) + to_native( + "Parameter provided for {0} of value '{1}' was not a valid file path.".format( + required_file, file_path + ) + ) ) for required_var in ["entrust_api_user", "entrust_api_key"]: if not kwargs.get(required_var): - raise SessionConfigurationException(to_native("Parameter provided for {0} was missing.".format(required_var))) + raise SessionConfigurationException( + to_native( + "Parameter provided for {0} was missing.".format(required_var) + ) + ) config["entrust_api_cert"] = kwargs.get("entrust_api_cert") config["entrust_api_cert_key"] = kwargs.get("entrust_api_cert_key") - config["entrust_api_specification_path"] = kwargs.get("entrust_api_specification_path") + config["entrust_api_specification_path"] = kwargs.get( + "entrust_api_specification_path" + ) config["entrust_api_user"] = kwargs.get("entrust_api_user") config["entrust_api_key"] = kwargs.get("entrust_api_key") return config -def ECSClient(entrust_api_user=None, entrust_api_key=None, entrust_api_cert=None, entrust_api_cert_key=None, entrust_api_specification_path=None): +def ECSClient( + entrust_api_user=None, + entrust_api_key=None, + entrust_api_cert=None, + entrust_api_cert_key=None, + entrust_api_specification_path=None, +): """Create an ECS client""" if not YAML_FOUND: - raise SessionConfigurationException(missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) + raise SessionConfigurationException( + missing_required_lib("PyYAML"), exception=YAML_IMP_ERR + ) if entrust_api_specification_path is None: - entrust_api_specification_path = "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml" + entrust_api_specification_path = ( + "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml" + ) # Not functionally necessary with current uses of this module_util, but better to be explicit for future use cases entrust_api_user = to_text(entrust_api_user) diff --git a/plugins/module_utils/gnupg/cli.py b/plugins/module_utils/gnupg/cli.py index b0b19679..ca290fcb 100644 --- a/plugins/module_utils/gnupg/cli.py +++ b/plugins/module_utils/gnupg/cli.py @@ -39,19 +39,32 @@ class GPGRunner(object): def get_fingerprint_from_stdout(stdout): lines = stdout.splitlines(False) for line in lines: - if line.startswith('fpr:'): - parts = line.split(':') + if line.startswith("fpr:"): + parts = line.split(":") if len(parts) <= 9 or not parts[9]: - raise GPGError('Result line "{line}" does not have fingerprint as 10th component'.format(line=line)) + raise GPGError( + 'Result line "{line}" does not have fingerprint as 10th component'.format( + line=line + ) + ) return parts[9] - raise GPGError('Cannot extract fingerprint from stdout "{stdout}"'.format(stdout=stdout)) + raise GPGError( + 'Cannot extract fingerprint from stdout "{stdout}"'.format(stdout=stdout) + ) def get_fingerprint_from_file(gpg_runner, path): if not os.path.exists(path): - raise GPGError('{path} does not exist'.format(path=path)) + raise GPGError("{path} does not exist".format(path=path)) stdout = gpg_runner.run_command( - ['--no-keyring', '--with-colons', '--import-options', 'show-only', '--import', path], + [ + "--no-keyring", + "--with-colons", + "--import-options", + "show-only", + "--import", + path, + ], check_rc=True, )[1] return get_fingerprint_from_stdout(stdout) @@ -59,7 +72,14 @@ def get_fingerprint_from_file(gpg_runner, path): def get_fingerprint_from_bytes(gpg_runner, content): stdout = gpg_runner.run_command( - ['--no-keyring', '--with-colons', '--import-options', 'show-only', '--import', '/dev/stdin'], + [ + "--no-keyring", + "--with-colons", + "--import-options", + "show-only", + "--import", + "/dev/stdin", + ], data=content, check_rc=True, )[1] diff --git a/plugins/module_utils/io.py b/plugins/module_utils/io.py index ccbe4c62..44699f0a 100644 --- a/plugins/module_utils/io.py +++ b/plugins/module_utils/io.py @@ -16,28 +16,28 @@ import tempfile def load_file(path, module=None): - ''' + """ Load the file as a bytes string. - ''' + """ try: - with open(path, 'rb') as f: + with open(path, "rb") as f: return f.read() except Exception as exc: if module is None: raise - module.fail_json('Error while loading {0} - {1}'.format(path, str(exc))) + module.fail_json("Error while loading {0} - {1}".format(path, str(exc))) def load_file_if_exists(path, module=None, ignore_errors=False): - ''' + """ Load the file as a bytes string. If the file does not exist, ``None`` is returned. If ``ignore_errors`` is ``True``, will ignore errors. Otherwise, errors are raised as exceptions if ``module`` is not specified, and result in ``module.fail_json`` being called when ``module`` is specified. - ''' + """ try: - with open(path, 'rb') as f: + with open(path, "rb") as f: return f.read() except EnvironmentError as exc: if exc.errno == errno.ENOENT: @@ -46,20 +46,20 @@ def load_file_if_exists(path, module=None, ignore_errors=False): return None if module is None: raise - module.fail_json('Error while loading {0} - {1}'.format(path, str(exc))) + module.fail_json("Error while loading {0} - {1}".format(path, str(exc))) except Exception as exc: if ignore_errors: return None if module is None: raise - module.fail_json('Error while loading {0} - {1}'.format(path, str(exc))) + module.fail_json("Error while loading {0} - {1}".format(path, str(exc))) def write_file(module, content, default_mode=None, path=None): - ''' + """ Writes content into destination file as securely as possible. Uses file arguments from module. - ''' + """ # Find out parameters for file try: file_args = module.load_file_common_arguments(module.params, path=path) @@ -68,11 +68,11 @@ def write_file(module, content, default_mode=None, path=None): # pre-2.10 behavior of module_utils/crypto.py for older Ansible versions. file_args = module.load_file_common_arguments(module.params) if path is not None: - file_args['path'] = path - if file_args['mode'] is None: - file_args['mode'] = default_mode + file_args["path"] = path + if file_args["mode"] is None: + file_args["mode"] = default_mode # Create tempfile name - tmp_fd, tmp_name = tempfile.mkstemp(prefix=b'.ansible_tmp') + tmp_fd, tmp_name = tempfile.mkstemp(prefix=b".ansible_tmp") try: os.close(tmp_fd) except Exception: @@ -89,18 +89,22 @@ def write_file(module, content, default_mode=None, path=None): os.remove(tmp_name) except Exception: pass - module.fail_json(msg='Error while writing result into temporary file: {0}'.format(e)) + module.fail_json( + msg="Error while writing result into temporary file: {0}".format(e) + ) # Update destination to wanted permissions - if os.path.exists(file_args['path']): + if os.path.exists(file_args["path"]): module.set_fs_attributes_if_different(file_args, False) # Move tempfile to final destination - module.atomic_move(os.path.abspath(tmp_name), os.path.abspath(file_args['path'])) + module.atomic_move( + os.path.abspath(tmp_name), os.path.abspath(file_args["path"]) + ) # Try to update permissions again - if not module.check_file_absent_if_check_mode(file_args['path']): + if not module.check_file_absent_if_check_mode(file_args["path"]): module.set_fs_attributes_if_different(file_args, False) except Exception as e: try: os.remove(tmp_name) except Exception: pass - module.fail_json(msg='Error while writing result: {0}'.format(e)) + module.fail_json(msg="Error while writing result: {0}".format(e)) diff --git a/plugins/module_utils/openssh/backends/common.py b/plugins/module_utils/openssh/backends/common.py index 99f0a5e1..5968d9b5 100644 --- a/plugins/module_utils/openssh/backends/common.py +++ b/plugins/module_utils/openssh/backends/common.py @@ -44,17 +44,24 @@ def safe_atomic_move(module, path, destination): def _restore_all_on_failure(f): def backup_and_restore(self, sources_and_destinations, *args, **kwargs): - backups = [(d, self.module.backup_local(d)) for s, d in sources_and_destinations if os.path.exists(d)] + backups = [ + (d, self.module.backup_local(d)) + for s, d in sources_and_destinations + if os.path.exists(d) + ] try: f(self, sources_and_destinations, *args, **kwargs) except Exception: for destination, backup in backups: - self.module.atomic_move(os.path.abspath(backup), os.path.abspath(destination)) + self.module.atomic_move( + os.path.abspath(backup), os.path.abspath(destination) + ) raise else: for destination, backup in backups: self.module.add_cleanup_file(backup) + return backup_and_restore @@ -85,10 +92,10 @@ class OpensshModule(object): def result(self): result = self._result - result['changed'] = self.changed + result["changed"] = self.changed if self.module._diff: - result['diff'] = self.diff + result["diff"] = self.diff return result @@ -107,6 +114,7 @@ class OpensshModule(object): def wrapper(self, *args, **kwargs): if not self.check_mode: f(self, *args, **kwargs) + return wrapper @staticmethod @@ -114,72 +122,92 @@ class OpensshModule(object): def wrapper(self, *args, **kwargs): f(self, *args, **kwargs) self.changed = True + return wrapper def _check_if_base_dir(self, path): - base_dir = os.path.dirname(path) or '.' + base_dir = os.path.dirname(path) or "." if not os.path.isdir(base_dir): self.module.fail_json( name=base_dir, - msg='The directory %s does not exist or the file is not a directory' % base_dir + msg="The directory %s does not exist or the file is not a directory" + % base_dir, ) def _get_ssh_version(self): - ssh_bin = self.module.get_bin_path('ssh') + ssh_bin = self.module.get_bin_path("ssh") if not ssh_bin: return "" - return parse_openssh_version(self.module.run_command([ssh_bin, '-V', '-q'], check_rc=True)[2].strip()) + return parse_openssh_version( + self.module.run_command([ssh_bin, "-V", "-q"], check_rc=True)[2].strip() + ) @_restore_all_on_failure def _safe_secure_move(self, sources_and_destinations): """Moves a list of files from 'source' to 'destination' and restores 'destination' from backup upon failure. - If 'destination' does not already exist, then 'source' permissions are preserved to prevent - exposing protected data ('atomic_move' uses the 'destination' base directory mask for - permissions if 'destination' does not already exists). + If 'destination' does not already exist, then 'source' permissions are preserved to prevent + exposing protected data ('atomic_move' uses the 'destination' base directory mask for + permissions if 'destination' does not already exists). """ for source, destination in sources_and_destinations: if os.path.exists(destination): - self.module.atomic_move(os.path.abspath(source), os.path.abspath(destination)) + self.module.atomic_move( + os.path.abspath(source), os.path.abspath(destination) + ) else: self.module.preserved_copy(source, destination) def _update_permissions(self, path): file_args = self.module.load_file_common_arguments(self.module.params) - file_args['path'] = path + file_args["path"] = path if not self.module.check_file_absent_if_check_mode(path): - self.changed = self.module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = self.module.set_fs_attributes_if_different( + file_args, self.changed + ) else: self.changed = True class KeygenCommand(object): def __init__(self, module): - self._bin_path = module.get_bin_path('ssh-keygen', True) + self._bin_path = module.get_bin_path("ssh-keygen", True) self._run_command = module.run_command - def generate_certificate(self, certificate_path, identifier, options, pkcs11_provider, principals, - serial_number, signature_algorithm, signing_key_path, type, - time_parameters, use_agent, **kwargs): - args = [self._bin_path, '-s', signing_key_path, '-P', '', '-I', identifier] + def generate_certificate( + self, + certificate_path, + identifier, + options, + pkcs11_provider, + principals, + serial_number, + signature_algorithm, + signing_key_path, + type, + time_parameters, + use_agent, + **kwargs + ): + args = [self._bin_path, "-s", signing_key_path, "-P", "", "-I", identifier] if options: for option in options: - args.extend(['-O', option]) + args.extend(["-O", option]) if pkcs11_provider: - args.extend(['-D', pkcs11_provider]) + args.extend(["-D", pkcs11_provider]) if principals: - args.extend(['-n', ','.join(principals)]) + args.extend(["-n", ",".join(principals)]) if serial_number is not None: - args.extend(['-z', str(serial_number)]) - if type == 'host': - args.extend(['-h']) + args.extend(["-z", str(serial_number)]) + if type == "host": + args.extend(["-h"]) if use_agent: - args.extend(['-U']) + args.extend(["-U"]) if time_parameters.validity_string: - args.extend(['-V', time_parameters.validity_string]) + args.extend(["-V", time_parameters.validity_string]) if signature_algorithm: - args.extend(['-t', signature_algorithm]) + args.extend(["-t", signature_algorithm]) args.append(certificate_path) return self._run_command(args, **kwargs) @@ -187,44 +215,62 @@ class KeygenCommand(object): def generate_keypair(self, private_key_path, size, type, comment, **kwargs): args = [ self._bin_path, - '-q', - '-N', '', - '-b', str(size), - '-t', type, - '-f', private_key_path, - '-C', comment or '' + "-q", + "-N", + "", + "-b", + str(size), + "-t", + type, + "-f", + private_key_path, + "-C", + comment or "", ] # "y" must be entered in response to the "overwrite" prompt - data = 'y' if os.path.exists(private_key_path) else None + data = "y" if os.path.exists(private_key_path) else None return self._run_command(args, data=data, **kwargs) def get_certificate_info(self, certificate_path, **kwargs): - return self._run_command([self._bin_path, '-L', '-f', certificate_path], **kwargs) + return self._run_command( + [self._bin_path, "-L", "-f", certificate_path], **kwargs + ) def get_matching_public_key(self, private_key_path, **kwargs): - return self._run_command([self._bin_path, '-P', '', '-y', '-f', private_key_path], **kwargs) + return self._run_command( + [self._bin_path, "-P", "", "-y", "-f", private_key_path], **kwargs + ) def get_private_key(self, private_key_path, **kwargs): - return self._run_command([self._bin_path, '-l', '-f', private_key_path], **kwargs) + return self._run_command( + [self._bin_path, "-l", "-f", private_key_path], **kwargs + ) - def update_comment(self, private_key_path, comment, force_new_format=True, **kwargs): - if os.path.exists(private_key_path) and not os.access(private_key_path, os.W_OK): + def update_comment( + self, private_key_path, comment, force_new_format=True, **kwargs + ): + if os.path.exists(private_key_path) and not os.access( + private_key_path, os.W_OK + ): try: os.chmod(private_key_path, stat.S_IWUSR + stat.S_IRUSR) except (IOError, OSError) as e: - raise e("The private key at %s is not writeable preventing a comment update" % private_key_path) + raise e( + "The private key at %s is not writeable preventing a comment update" + % private_key_path + ) - command = [self._bin_path, '-q'] + command = [self._bin_path, "-q"] if force_new_format: - command.append('-o') - command.extend(['-c', '-C', comment, '-f', private_key_path]) + command.append("-o") + command.extend(["-c", "-C", comment, "-f", private_key_path]) return self._run_command(command, **kwargs) class PrivateKey(object): - def __init__(self, size, key_type, fingerprint, format=''): + def __init__(self, size, key_type, fingerprint, format=""): self._size = size self._type = key_type self._fingerprint = fingerprint @@ -258,10 +304,10 @@ class PrivateKey(object): def to_dict(self): return { - 'size': self._size, - 'type': self._type, - 'fingerprint': self._fingerprint, - 'format': self._format, + "size": self._size, + "type": self._type, + "fingerprint": self._fingerprint, + "format": self._format, } @@ -275,11 +321,17 @@ class PublicKey(object): if not isinstance(other, type(self)): return NotImplemented - return all([ - self._type_string == other._type_string, - self._data == other._data, - (self._comment == other._comment) if self._comment is not None and other._comment is not None else True - ]) + return all( + [ + self._type_string == other._type_string, + self._data == other._data, + ( + (self._comment == other._comment) + if self._comment is not None and other._comment is not None + else True + ), + ] + ) def __ne__(self, other): return not self == other @@ -305,19 +357,19 @@ class PublicKey(object): @classmethod def from_string(cls, string): - properties = string.strip('\n').split(' ', 2) + properties = string.strip("\n").split(" ", 2) return cls( type_string=properties[0], data=properties[1], - comment=properties[2] if len(properties) > 2 else "" + comment=properties[2] if len(properties) > 2 else "", ) @classmethod def load(cls, path): try: - with open(path, 'r') as f: - properties = f.read().strip(' \n').split(' ', 2) + with open(path, "r") as f: + properties = f.read().strip(" \n").split(" ", 2) except (IOError, OSError): raise @@ -327,25 +379,25 @@ class PublicKey(object): return cls( type_string=properties[0], data=properties[1], - comment='' if len(properties) <= 2 else properties[2], + comment="" if len(properties) <= 2 else properties[2], ) def to_dict(self): return { - 'comment': self._comment, - 'public_key': self._data, + "comment": self._comment, + "public_key": self._data, } def parse_private_key_format(path): - with open(path, 'r') as file: + with open(path, "r") as file: header = file.readline().strip() - if header == '-----BEGIN OPENSSH PRIVATE KEY-----': - return 'SSH' - elif header == '-----BEGIN PRIVATE KEY-----': - return 'PKCS8' - elif header == '-----BEGIN RSA PRIVATE KEY-----': - return 'PKCS1' + if header == "-----BEGIN OPENSSH PRIVATE KEY-----": + return "SSH" + elif header == "-----BEGIN PRIVATE KEY-----": + return "PKCS8" + elif header == "-----BEGIN RSA PRIVATE KEY-----": + return "PKCS1" - return '' + return "" diff --git a/plugins/module_utils/openssh/backends/keypair_backend.py b/plugins/module_utils/openssh/backends/keypair_backend.py index cbefd469..15df0310 100644 --- a/plugins/module_utils/openssh/backends/keypair_backend.py +++ b/plugins/module_utils/openssh/backends/keypair_backend.py @@ -48,14 +48,18 @@ class KeypairBackend(OpensshModule): def __init__(self, module): super(KeypairBackend, self).__init__(module) - self.comment = self.module.params['comment'] - self.private_key_path = self.module.params['path'] - self.public_key_path = self.private_key_path + '.pub' - self.regenerate = self.module.params['regenerate'] if not self.module.params['force'] else 'always' - self.state = self.module.params['state'] - self.type = self.module.params['type'] + self.comment = self.module.params["comment"] + self.private_key_path = self.module.params["path"] + self.public_key_path = self.private_key_path + ".pub" + self.regenerate = ( + self.module.params["regenerate"] + if not self.module.params["force"] + else "always" + ) + self.state = self.module.params["state"] + self.type = self.module.params["type"] - self.size = self._get_size(self.module.params['size']) + self.size = self._get_size(self.module.params["size"]) self._validate_path() self.original_private_key = None @@ -64,31 +68,35 @@ class KeypairBackend(OpensshModule): self.public_key = None def _get_size(self, size): - if self.type in ('rsa', 'rsa1'): + if self.type in ("rsa", "rsa1"): result = 4096 if size is None else size if result < 1024: return self.module.fail_json( - msg="For RSA keys, the minimum size is 1024 bits and the default is 4096 bits. " + - "Attempting to use bit lengths under 1024 will cause the module to fail." + msg="For RSA keys, the minimum size is 1024 bits and the default is 4096 bits. " + + "Attempting to use bit lengths under 1024 will cause the module to fail." ) - elif self.type == 'dsa': + elif self.type == "dsa": result = 1024 if size is None else size if result != 1024: - return self.module.fail_json(msg="DSA keys must be exactly 1024 bits as specified by FIPS 186-2.") - elif self.type == 'ecdsa': + return self.module.fail_json( + msg="DSA keys must be exactly 1024 bits as specified by FIPS 186-2." + ) + elif self.type == "ecdsa": result = 256 if size is None else size if result not in (256, 384, 521): return self.module.fail_json( - msg="For ECDSA keys, size determines the key length by selecting from one of " + - "three elliptic curve sizes: 256, 384 or 521 bits. " + - "Attempting to use bit lengths other than these three values for ECDSA keys will " + - "cause this module to fail." + msg="For ECDSA keys, size determines the key length by selecting from one of " + + "three elliptic curve sizes: 256, 384 or 521 bits. " + + "Attempting to use bit lengths other than these three values for ECDSA keys will " + + "cause this module to fail." ) - elif self.type == 'ed25519': + elif self.type == "ed25519": # User input is ignored for `key size` when `key type` is ed25519 result = 256 else: - return self.module.fail_json(msg="%s is not a valid value for key type" % self.type) + return self.module.fail_json( + msg="%s is not a valid value for key type" % self.type + ) return result @@ -96,13 +104,16 @@ class KeypairBackend(OpensshModule): self._check_if_base_dir(self.private_key_path) if os.path.isdir(self.private_key_path): - self.module.fail_json(msg='%s is a directory. Please specify a path to a file.' % self.private_key_path) + self.module.fail_json( + msg="%s is a directory. Please specify a path to a file." + % self.private_key_path + ) def _execute(self): self.original_private_key = self._load_private_key() self.original_public_key = self._load_public_key() - if self.state == 'present': + if self.state == "present": self._validate_key_load() if self._should_generate(): @@ -149,13 +160,15 @@ class KeypairBackend(OpensshModule): return os.path.exists(self.public_key_path) def _validate_key_load(self): - if (self._private_key_exists() - and self.regenerate in ('never', 'fail', 'partial_idempotence') - and (self.original_private_key is None or not self._private_key_readable())): + if ( + self._private_key_exists() + and self.regenerate in ("never", "fail", "partial_idempotence") + and (self.original_private_key is None or not self._private_key_readable()) + ): self.module.fail_json( - msg="Unable to read the key. The key is protected with a passphrase or broken. " + - "Will not proceed. To force regeneration, call the module with `generate` " + - "set to `full_idempotence` or `always`, or with `force=true`." + msg="Unable to read the key. The key is protected with a passphrase or broken. " + + "Will not proceed. To force regeneration, call the module with `generate` " + + "set to `full_idempotence` or `always`, or with `force=true`." ) @abc.abstractmethod @@ -165,17 +178,17 @@ class KeypairBackend(OpensshModule): def _should_generate(self): if self.original_private_key is None: return True - elif self.regenerate == 'never': + elif self.regenerate == "never": return False - elif self.regenerate == 'fail': + elif self.regenerate == "fail": if not self._private_key_valid(): 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`." + 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`." ) return False - elif self.regenerate in ('partial_idempotence', 'full_idempotence'): + elif self.regenerate in ("partial_idempotence", "full_idempotence"): return not self._private_key_valid() else: return True @@ -184,11 +197,13 @@ class KeypairBackend(OpensshModule): if self.original_private_key is None: return False - return all([ - self.size == self.original_private_key.size, - self.type == self.original_private_key.type, - self._private_key_valid_backend(), - ]) + return all( + [ + self.size == self.original_private_key.size, + self.type == self.original_private_key.type, + self._private_key_valid_backend(), + ] + ) @abc.abstractmethod def _private_key_valid_backend(self): @@ -200,13 +215,20 @@ class KeypairBackend(OpensshModule): temp_private_key, temp_public_key = self._generate_temp_keypair() try: - self._safe_secure_move([(temp_private_key, self.private_key_path), (temp_public_key, self.public_key_path)]) + self._safe_secure_move( + [ + (temp_private_key, self.private_key_path), + (temp_public_key, self.public_key_path), + ] + ) except OSError as e: self.module.fail_json(msg=to_native(e)) def _generate_temp_keypair(self): - temp_private_key = os.path.join(self.module.tmpdir, os.path.basename(self.private_key_path)) - temp_public_key = temp_private_key + '.pub' + temp_private_key = os.path.join( + self.module.tmpdir, os.path.basename(self.private_key_path) + ) + temp_public_key = temp_private_key + ".pub" try: self._generate_keypair(temp_private_key) @@ -239,27 +261,33 @@ class KeypairBackend(OpensshModule): @OpensshModule.skip_if_check_mode def _restore_public_key(self): try: - temp_public_key = self._create_temp_public_key(str(self._get_public_key()) + '\n') - self._safe_secure_move([ - (temp_public_key, self.public_key_path) - ]) + temp_public_key = self._create_temp_public_key( + str(self._get_public_key()) + "\n" + ) + self._safe_secure_move([(temp_public_key, self.public_key_path)]) except (IOError, OSError): self.module.fail_json( - msg="The public key is missing or does not match the private key. " + - "Unable to regenerate the public key." + msg="The public key is missing or does not match the private key. " + + "Unable to regenerate the public key." ) if self.comment: self._update_comment() def _create_temp_public_key(self, content): - temp_public_key = os.path.join(self.module.tmpdir, os.path.basename(self.public_key_path)) + temp_public_key = os.path.join( + self.module.tmpdir, os.path.basename(self.public_key_path) + ) default_permissions = 0o644 existing_permissions = file_mode(self.public_key_path) try: - secure_write(temp_public_key, existing_permissions or default_permissions, to_bytes(content)) + secure_write( + temp_public_key, + existing_permissions or default_permissions, + to_bytes(content), + ) except (IOError, OSError) as e: self.module.fail_json(msg=to_native(e)) self.module.add_cleanup_file(temp_public_key) @@ -290,25 +318,29 @@ class KeypairBackend(OpensshModule): public_key = self.public_key or self.original_public_key return { - 'size': self.size, - 'type': self.type, - 'filename': self.private_key_path, - 'fingerprint': private_key.fingerprint if private_key else '', - 'public_key': str(public_key) if public_key else '', - 'comment': public_key.comment if public_key else '', + "size": self.size, + "type": self.type, + "filename": self.private_key_path, + "fingerprint": private_key.fingerprint if private_key else "", + "public_key": str(public_key) if public_key else "", + "comment": public_key.comment if public_key else "", } @property def diff(self): - before = self.original_private_key.to_dict() if self.original_private_key else {} - before.update(self.original_public_key.to_dict() if self.original_public_key else {}) + before = ( + self.original_private_key.to_dict() if self.original_private_key else {} + ) + before.update( + self.original_public_key.to_dict() if self.original_public_key else {} + ) after = self.private_key.to_dict() if self.private_key else {} after.update(self.public_key.to_dict() if self.public_key else {}) return { - 'before': before, - 'after': after, + "before": before, + "after": after, } @@ -316,36 +348,59 @@ class KeypairBackendOpensshBin(KeypairBackend): def __init__(self, module): super(KeypairBackendOpensshBin, self).__init__(module) - if self.module.params['private_key_format'] != 'auto': + if self.module.params["private_key_format"] != "auto": self.module.fail_json( - msg="'auto' is the only valid option for " + - "'private_key_format' when 'backend' is not 'cryptography'" + msg="'auto' is the only valid option for " + + "'private_key_format' when 'backend' is not 'cryptography'" ) self.ssh_keygen = KeygenCommand(self.module) def _generate_keypair(self, private_key_path): - self.ssh_keygen.generate_keypair(private_key_path, self.size, self.type, self.comment, check_rc=True) + self.ssh_keygen.generate_keypair( + private_key_path, self.size, self.type, self.comment, check_rc=True + ) def _get_private_key(self): - rc, private_key_content, err = self.ssh_keygen.get_private_key(self.private_key_path, check_rc=False) + rc, private_key_content, err = self.ssh_keygen.get_private_key( + self.private_key_path, check_rc=False + ) if rc != 0: raise ValueError(err) return PrivateKey.from_string(private_key_content) def _get_public_key(self): - public_key_content = self.ssh_keygen.get_matching_public_key(self.private_key_path, check_rc=True)[1] + public_key_content = self.ssh_keygen.get_matching_public_key( + self.private_key_path, check_rc=True + )[1] return PublicKey.from_string(public_key_content) def _private_key_readable(self): - rc, stdout, stderr = self.ssh_keygen.get_matching_public_key(self.private_key_path, check_rc=False) - return not (rc == 255 or any_in(stderr, 'is not a public key file', 'incorrect passphrase', 'load failed')) + rc, stdout, stderr = self.ssh_keygen.get_matching_public_key( + self.private_key_path, check_rc=False + ) + return not ( + rc == 255 + or any_in( + stderr, + "is not a public key file", + "incorrect passphrase", + "load failed", + ) + ) def _update_comment(self): try: ssh_version = self._get_ssh_version() or "7.8" - force_new_format = LooseVersion('6.5') <= LooseVersion(ssh_version) < LooseVersion('7.8') - self.ssh_keygen.update_comment(self.private_key_path, self.comment, force_new_format=force_new_format, check_rc=True) + force_new_format = ( + LooseVersion("6.5") <= LooseVersion(ssh_version) < LooseVersion("7.8") + ) + self.ssh_keygen.update_comment( + self.private_key_path, + self.comment, + force_new_format=force_new_format, + check_rc=True, + ) except (IOError, OSError) as e: self.module.fail_json(msg=to_native(e)) @@ -357,30 +412,41 @@ class KeypairBackendCryptography(KeypairBackend): def __init__(self, module): super(KeypairBackendCryptography, self).__init__(module) - if self.type == 'rsa1': - self.module.fail_json(msg="RSA1 keys are not supported by the cryptography backend") + if self.type == "rsa1": + self.module.fail_json( + msg="RSA1 keys are not supported by the cryptography backend" + ) - self.passphrase = to_bytes(module.params['passphrase']) if module.params['passphrase'] else None - self.private_key_format = self._get_key_format(module.params['private_key_format']) + self.passphrase = ( + to_bytes(module.params["passphrase"]) + if module.params["passphrase"] + else None + ) + self.private_key_format = self._get_key_format( + module.params["private_key_format"] + ) def _get_key_format(self, key_format): - result = 'SSH' + result = "SSH" - if key_format == 'auto': + if key_format == "auto": # Default to OpenSSH 7.8 compatibility when OpenSSH is not installed ssh_version = self._get_ssh_version() or "7.8" - if LooseVersion(ssh_version) < LooseVersion("7.8") and self.type != 'ed25519': + if ( + LooseVersion(ssh_version) < LooseVersion("7.8") + and self.type != "ed25519" + ): # OpenSSH made SSH formatted private keys available in version 6.5, # but still defaulted to PKCS1 format with the exception of ed25519 keys - result = 'PKCS1' + result = "PKCS1" - if result == 'SSH' and not HAS_OPENSSH_PRIVATE_FORMAT: + if result == "SSH" and not HAS_OPENSSH_PRIVATE_FORMAT: self.module.fail_json( msg=missing_required_lib( - 'cryptography >= 3.0', - reason="to load/dump private keys in the default OpenSSH format for OpenSSH >= 7.8 " + - "or for ed25519 keys" + "cryptography >= 3.0", + reason="to load/dump private keys in the default OpenSSH format for OpenSSH >= 7.8 " + + "or for ed25519 keys", ) ) else: @@ -393,7 +459,7 @@ class KeypairBackendCryptography(KeypairBackend): keytype=self.type, size=self.size, passphrase=self.passphrase, - comment=self.comment or '', + comment=self.comment or "", ) encoded_private_key = OpensshKeypair.encode_openssh_privatekey( @@ -401,22 +467,28 @@ class KeypairBackendCryptography(KeypairBackend): ) secure_write(private_key_path, 0o600, encoded_private_key) - public_key_path = private_key_path + '.pub' + public_key_path = private_key_path + ".pub" secure_write(public_key_path, 0o644, keypair.public_key) def _get_private_key(self): - keypair = OpensshKeypair.load(path=self.private_key_path, passphrase=self.passphrase, no_public_key=True) + keypair = OpensshKeypair.load( + path=self.private_key_path, passphrase=self.passphrase, no_public_key=True + ) return PrivateKey( size=keypair.size, key_type=keypair.key_type, fingerprint=keypair.fingerprint, - format=parse_private_key_format(self.private_key_path) + format=parse_private_key_format(self.private_key_path), ) def _get_public_key(self): try: - keypair = OpensshKeypair.load(path=self.private_key_path, passphrase=self.passphrase, no_public_key=True) + keypair = OpensshKeypair.load( + path=self.private_key_path, + passphrase=self.passphrase, + no_public_key=True, + ) except OpenSSHError: # Simulates the null output of ssh-keygen return "" @@ -425,7 +497,11 @@ class KeypairBackendCryptography(KeypairBackend): def _private_key_readable(self): try: - OpensshKeypair.load(path=self.private_key_path, passphrase=self.passphrase, no_public_key=True) + OpensshKeypair.load( + path=self.private_key_path, + passphrase=self.passphrase, + no_public_key=True, + ) except (InvalidPrivateKeyFileError, InvalidPassphraseError): return False @@ -433,7 +509,9 @@ class KeypairBackendCryptography(KeypairBackend): # when loading an unencrypted key if self.passphrase: try: - OpensshKeypair.load(path=self.private_key_path, passphrase=None, no_public_key=True) + OpensshKeypair.load( + path=self.private_key_path, passphrase=None, no_public_key=True + ) except (InvalidPrivateKeyFileError, InvalidPassphraseError): return True else: @@ -442,14 +520,16 @@ class KeypairBackendCryptography(KeypairBackend): return True def _update_comment(self): - keypair = OpensshKeypair.load(path=self.private_key_path, passphrase=self.passphrase, no_public_key=True) + keypair = OpensshKeypair.load( + path=self.private_key_path, passphrase=self.passphrase, no_public_key=True + ) try: keypair.comment = self.comment except InvalidCommentError as e: self.module.fail_json(msg=to_native(e)) try: - temp_public_key = self._create_temp_public_key(keypair.public_key + b'\n') + temp_public_key = self._create_temp_public_key(keypair.public_key + b"\n") self._safe_secure_move([(temp_public_key, self.public_key_path)]) except (IOError, OSError) as e: self.module.fail_json(msg=to_native(e)) @@ -457,7 +537,7 @@ class KeypairBackendCryptography(KeypairBackend): def _private_key_valid_backend(self): # avoids breaking behavior and prevents # automatic conversions with OpenSSH upgrades - if self.module.params['private_key_format'] == 'auto': + if self.module.params["private_key_format"] == "auto": return True return self.private_key_format == self.original_private_key.format @@ -465,24 +545,26 @@ class KeypairBackendCryptography(KeypairBackend): def select_backend(module, backend): can_use_cryptography = HAS_OPENSSH_SUPPORT - can_use_opensshbin = bool(module.get_bin_path('ssh-keygen')) + can_use_opensshbin = bool(module.get_bin_path("ssh-keygen")) - if backend == 'auto': - if can_use_opensshbin and not module.params['passphrase']: - backend = 'opensshbin' + if backend == "auto": + if can_use_opensshbin and not module.params["passphrase"]: + backend = "opensshbin" elif can_use_cryptography: - backend = 'cryptography' + backend = "cryptography" else: - module.fail_json(msg="Cannot find either the OpenSSH binary in the PATH " + - "or cryptography >= 2.6 installed on this system") + module.fail_json( + msg="Cannot find either the OpenSSH binary in the PATH " + + "or cryptography >= 2.6 installed on this system" + ) - if backend == 'opensshbin': + if backend == "opensshbin": if not can_use_opensshbin: module.fail_json(msg="Cannot find the OpenSSH binary in the PATH") return backend, KeypairBackendOpensshBin(module) - elif backend == 'cryptography': + elif backend == "cryptography": if not can_use_cryptography: module.fail_json(msg=missing_required_lib("cryptography >= 2.6")) return backend, KeypairBackendCryptography(module) else: - raise ValueError('Unsupported value for backend: {0}'.format(backend)) + raise ValueError("Unsupported value for backend: {0}".format(backend)) diff --git a/plugins/module_utils/openssh/certificate.py b/plugins/module_utils/openssh/certificate.py index 4feb4eb3..ea64e143 100644 --- a/plugins/module_utils/openssh/certificate.py +++ b/plugins/module_utils/openssh/certificate.py @@ -51,54 +51,56 @@ _USER_TYPE = 1 _HOST_TYPE = 2 _SSH_TYPE_STRINGS = { - 'rsa': b"ssh-rsa", - 'dsa': b"ssh-dss", - 'ecdsa-nistp256': b"ecdsa-sha2-nistp256", - 'ecdsa-nistp384': b"ecdsa-sha2-nistp384", - 'ecdsa-nistp521': b"ecdsa-sha2-nistp521", - 'ed25519': b"ssh-ed25519", + "rsa": b"ssh-rsa", + "dsa": b"ssh-dss", + "ecdsa-nistp256": b"ecdsa-sha2-nistp256", + "ecdsa-nistp384": b"ecdsa-sha2-nistp384", + "ecdsa-nistp521": b"ecdsa-sha2-nistp521", + "ed25519": b"ssh-ed25519", } _CERT_SUFFIX_V01 = b"-cert-v01@openssh.com" # See https://datatracker.ietf.org/doc/html/rfc5656#section-6.1 _ECDSA_CURVE_IDENTIFIERS = { - 'ecdsa-nistp256': b'nistp256', - 'ecdsa-nistp384': b'nistp384', - 'ecdsa-nistp521': b'nistp521', + "ecdsa-nistp256": b"nistp256", + "ecdsa-nistp384": b"nistp384", + "ecdsa-nistp521": b"nistp521", } _ECDSA_CURVE_IDENTIFIERS_LOOKUP = { - b'nistp256': 'ecdsa-nistp256', - b'nistp384': 'ecdsa-nistp384', - b'nistp521': 'ecdsa-nistp521', + b"nistp256": "ecdsa-nistp256", + b"nistp384": "ecdsa-nistp384", + b"nistp521": "ecdsa-nistp521", } _USE_TIMEZONE = sys.version_info >= (3, 6) _ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=_USE_TIMEZONE) -_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max +_FOREVER = ( + datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max +) _CRITICAL_OPTIONS = ( - 'force-command', - 'source-address', - 'verify-required', + "force-command", + "source-address", + "verify-required", ) _DIRECTIVES = ( - 'clear', - 'no-x11-forwarding', - 'no-agent-forwarding', - 'no-port-forwarding', - 'no-pty', - 'no-user-rc', + "clear", + "no-x11-forwarding", + "no-agent-forwarding", + "no-port-forwarding", + "no-pty", + "no-user-rc", ) _EXTENSIONS = ( - 'permit-x11-forwarding', - 'permit-agent-forwarding', - 'permit-port-forwarding', - 'permit-pty', - 'permit-user-rc' + "permit-x11-forwarding", + "permit-agent-forwarding", + "permit-port-forwarding", + "permit-pty", + "permit-user-rc", ) if six.PY3: @@ -111,13 +113,19 @@ class OpensshCertificateTimeParameters(object): self._valid_to = self.to_datetime(valid_to) if self._valid_from > self._valid_to: - raise ValueError("Valid from: %s must not be greater than Valid to: %s" % (valid_from, valid_to)) + raise ValueError( + "Valid from: %s must not be greater than Valid to: %s" + % (valid_from, valid_to) + ) def __eq__(self, other): if not isinstance(other, type(self)): return NotImplemented else: - return self._valid_from == other._valid_from and self._valid_to == other._valid_to + return ( + self._valid_from == other._valid_from + and self._valid_to == other._valid_to + ) def __ne__(self, other): return not self == other @@ -126,7 +134,8 @@ class OpensshCertificateTimeParameters(object): def validity_string(self): if not (self._valid_from == _ALWAYS and self._valid_to == _FOREVER): return "%s:%s" % ( - self.valid_from(date_format='openssh'), self.valid_to(date_format='openssh') + self.valid_from(date_format="openssh"), + self.valid_to(date_format="openssh"), ) return "" @@ -144,16 +153,22 @@ class OpensshCertificateTimeParameters(object): @staticmethod def format_datetime(dt, date_format): - if date_format in ('human_readable', 'openssh'): + if date_format in ("human_readable", "openssh"): if dt == _ALWAYS: - result = 'always' + result = "always" elif dt == _FOREVER: - result = 'forever' + result = "forever" else: - result = dt.isoformat().replace('+00:00', '') if date_format == 'human_readable' else dt.strftime("%Y%m%d%H%M%S") - elif date_format == 'timestamp': + result = ( + dt.isoformat().replace("+00:00", "") + if date_format == "human_readable" + else dt.strftime("%Y%m%d%H%M%S") + ) + elif date_format == "timestamp": td = dt - _ALWAYS - result = int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6) + result = int( + (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + ) else: raise ValueError("%s is not a valid format" % date_format) return result @@ -162,12 +177,17 @@ class OpensshCertificateTimeParameters(object): def to_datetime(time_string_or_timestamp): try: if isinstance(time_string_or_timestamp, six.string_types): - result = OpensshCertificateTimeParameters._time_string_to_datetime(time_string_or_timestamp.strip()) + result = OpensshCertificateTimeParameters._time_string_to_datetime( + time_string_or_timestamp.strip() + ) elif isinstance(time_string_or_timestamp, (long, int)): - result = OpensshCertificateTimeParameters._timestamp_to_datetime(time_string_or_timestamp) + result = OpensshCertificateTimeParameters._timestamp_to_datetime( + time_string_or_timestamp + ) else: raise ValueError( - "Value must be of type (str, unicode, int, long) not %s" % type(time_string_or_timestamp) + "Value must be of type (str, unicode, int, long) not %s" + % type(time_string_or_timestamp) ) except ValueError: raise @@ -182,7 +202,9 @@ class OpensshCertificateTimeParameters(object): else: try: if _USE_TIMEZONE: - result = datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc) + result = datetime.fromtimestamp( + timestamp, tz=_datetime.timezone.utc + ) else: result = datetime.utcfromtimestamp(timestamp) except OverflowError: @@ -192,16 +214,21 @@ class OpensshCertificateTimeParameters(object): @staticmethod def _time_string_to_datetime(time_string): result = None - if time_string == 'always': + if time_string == "always": result = _ALWAYS - elif time_string == 'forever': + elif time_string == "forever": result = _FOREVER elif is_relative_time_string(time_string): - result = convert_relative_to_datetime(time_string, with_timezone=_USE_TIMEZONE) + result = convert_relative_to_datetime( + time_string, with_timezone=_USE_TIMEZONE + ) else: for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"): try: - result = _add_or_remove_timezone(datetime.strptime(time_string, time_format), with_timezone=_USE_TIMEZONE) + result = _add_or_remove_timezone( + datetime.strptime(time_string, time_format), + with_timezone=_USE_TIMEZONE, + ) except ValueError: pass if result is None: @@ -211,7 +238,7 @@ class OpensshCertificateTimeParameters(object): class OpensshCertificateOption(object): def __init__(self, option_type, name, data): - if option_type not in ('critical', 'extension'): + if option_type not in ("critical", "extension"): raise ValueError("type must be either 'critical' or 'extension'") if not isinstance(name, six.string_types): @@ -228,11 +255,13 @@ class OpensshCertificateOption(object): if not isinstance(other, type(self)): return NotImplemented - return all([ - self._option_type == other._option_type, - self._name == other._name, - self._data == other._data, - ]) + return all( + [ + self._option_type == other._option_type, + self._name == other._name, + self._data == other._data, + ] + ) def __hash__(self): return hash((self._option_type, self._name, self._data)) @@ -260,42 +289,47 @@ class OpensshCertificateOption(object): @classmethod def from_string(cls, option_string): if not isinstance(option_string, six.string_types): - raise ValueError("option_string must be a string not %s" % type(option_string)) + raise ValueError( + "option_string must be a string not %s" % type(option_string) + ) option_type = None - if ':' in option_string: - option_type, value = option_string.strip().split(':', 1) - if '=' in value: - name, data = value.split('=', 1) + if ":" in option_string: + option_type, value = option_string.strip().split(":", 1) + if "=" in value: + name, data = value.split("=", 1) else: - name, data = value, '' - elif '=' in option_string: - name, data = option_string.strip().split('=', 1) + name, data = value, "" + elif "=" in option_string: + name, data = option_string.strip().split("=", 1) else: - name, data = option_string.strip(), '' + name, data = option_string.strip(), "" return cls( option_type=option_type or get_option_type(name.lower()), name=name, - data=data + data=data, ) @six.add_metaclass(abc.ABCMeta) class OpensshCertificateInfo: """Encapsulates all certificate information which is signed by a CA key""" - def __init__(self, - nonce=None, - serial=None, - cert_type=None, - key_id=None, - principals=None, - valid_after=None, - valid_before=None, - critical_options=None, - extensions=None, - reserved=None, - signing_key=None): + + def __init__( + self, + nonce=None, + serial=None, + cert_type=None, + key_id=None, + principals=None, + valid_after=None, + valid_before=None, + critical_options=None, + extensions=None, + reserved=None, + signing_key=None, + ): self.nonce = nonce self.serial = serial self._cert_type = cert_type @@ -313,17 +347,17 @@ class OpensshCertificateInfo: @property def cert_type(self): if self._cert_type == _USER_TYPE: - return 'user' + return "user" elif self._cert_type == _HOST_TYPE: - return 'host' + return "host" else: - return '' + return "" @cert_type.setter def cert_type(self, cert_type): - if cert_type == 'user' or cert_type == _USER_TYPE: + if cert_type == "user" or cert_type == _USER_TYPE: self._cert_type = _USER_TYPE - elif cert_type == 'host' or cert_type == _HOST_TYPE: + elif cert_type == "host" or cert_type == _HOST_TYPE: self._cert_type = _HOST_TYPE else: raise ValueError("%s is not a valid certificate type" % cert_type) @@ -343,17 +377,17 @@ class OpensshCertificateInfo: class OpensshRSACertificateInfo(OpensshCertificateInfo): def __init__(self, e=None, n=None, **kwargs): super(OpensshRSACertificateInfo, self).__init__(**kwargs) - self.type_string = _SSH_TYPE_STRINGS['rsa'] + _CERT_SUFFIX_V01 + self.type_string = _SSH_TYPE_STRINGS["rsa"] + _CERT_SUFFIX_V01 self.e = e self.n = n # See https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 def public_key_fingerprint(self): if any([self.e is None, self.n is None]): - return b'' + return b"" writer = _OpensshWriter() - writer.string(_SSH_TYPE_STRINGS['rsa']) + writer.string(_SSH_TYPE_STRINGS["rsa"]) writer.mpint(self.e) writer.mpint(self.n) @@ -367,7 +401,7 @@ class OpensshRSACertificateInfo(OpensshCertificateInfo): class OpensshDSACertificateInfo(OpensshCertificateInfo): def __init__(self, p=None, q=None, g=None, y=None, **kwargs): super(OpensshDSACertificateInfo, self).__init__(**kwargs) - self.type_string = _SSH_TYPE_STRINGS['dsa'] + _CERT_SUFFIX_V01 + self.type_string = _SSH_TYPE_STRINGS["dsa"] + _CERT_SUFFIX_V01 self.p = p self.q = q self.g = g @@ -376,10 +410,10 @@ class OpensshDSACertificateInfo(OpensshCertificateInfo): # See https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 def public_key_fingerprint(self): if any([self.p is None, self.q is None, self.g is None, self.y is None]): - return b'' + return b"" writer = _OpensshWriter() - writer.string(_SSH_TYPE_STRINGS['dsa']) + writer.string(_SSH_TYPE_STRINGS["dsa"]) writer.mpint(self.p) writer.mpint(self.q) writer.mpint(self.g) @@ -411,16 +445,20 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo): def curve(self, curve): if curve in _ECDSA_CURVE_IDENTIFIERS.values(): self._curve = curve - self.type_string = _SSH_TYPE_STRINGS[_ECDSA_CURVE_IDENTIFIERS_LOOKUP[curve]] + _CERT_SUFFIX_V01 + self.type_string = ( + _SSH_TYPE_STRINGS[_ECDSA_CURVE_IDENTIFIERS_LOOKUP[curve]] + + _CERT_SUFFIX_V01 + ) else: raise ValueError( - "Curve must be one of %s" % (b','.join(list(_ECDSA_CURVE_IDENTIFIERS.values()))).decode('UTF-8') + "Curve must be one of %s" + % (b",".join(list(_ECDSA_CURVE_IDENTIFIERS.values()))).decode("UTF-8") ) # See https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 def public_key_fingerprint(self): if any([self.curve is None, self.public_key is None]): - return b'' + return b"" writer = _OpensshWriter() writer.string(_SSH_TYPE_STRINGS[_ECDSA_CURVE_IDENTIFIERS_LOOKUP[self.curve]]) @@ -437,15 +475,15 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo): class OpensshED25519CertificateInfo(OpensshCertificateInfo): def __init__(self, pk=None, **kwargs): super(OpensshED25519CertificateInfo, self).__init__(**kwargs) - self.type_string = _SSH_TYPE_STRINGS['ed25519'] + _CERT_SUFFIX_V01 + self.type_string = _SSH_TYPE_STRINGS["ed25519"] + _CERT_SUFFIX_V01 self.pk = pk def public_key_fingerprint(self): if self.pk is None: - return b'' + return b"" writer = _OpensshWriter() - writer.string(_SSH_TYPE_STRINGS['ed25519']) + writer.string(_SSH_TYPE_STRINGS["ed25519"]) writer.string(self.pk) return fingerprint(writer.bytes()) @@ -457,6 +495,7 @@ class OpensshED25519CertificateInfo(OpensshCertificateInfo): # See https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD class OpensshCertificate(object): """Encapsulates a formatted OpenSSH certificate including signature and signing key""" + def __init__(self, cert_info, signature): self._cert_info = cert_info @@ -468,13 +507,13 @@ class OpensshCertificate(object): raise ValueError("%s is not a valid path." % path) try: - with open(path, 'rb') as cert_file: + with open(path, "rb") as cert_file: data = cert_file.read() except (IOError, OSError) as e: raise ValueError("%s cannot be opened for reading: %s" % (path, e)) try: - format_identifier, b64_cert = data.split(b' ')[:2] + format_identifier, b64_cert = data.split(b" ")[:2] cert = binascii.a2b_base64(b64_cert) except (binascii.Error, ValueError): raise ValueError("Certificate not in OpenSSH format") @@ -484,7 +523,9 @@ class OpensshCertificate(object): pub_key_type = key_type break else: - raise ValueError("Invalid certificate format identifier: %s" % format_identifier) + raise ValueError( + "Invalid certificate format identifier: %s" % format_identifier + ) parser = OpensshParser(cert) @@ -499,7 +540,8 @@ class OpensshCertificate(object): if parser.remaining_bytes(): raise ValueError( - "%s bytes of additional data was not parsed while loading %s" % (parser.remaining_bytes(), path) + "%s bytes of additional data was not parsed while loading %s" + % (parser.remaining_bytes(), path) ) return cls( @@ -546,12 +588,16 @@ class OpensshCertificate(object): @property def critical_options(self): return [ - OpensshCertificateOption('critical', to_text(n), to_text(d)) for n, d in self._cert_info.critical_options + OpensshCertificateOption("critical", to_text(n), to_text(d)) + for n, d in self._cert_info.critical_options ] @property def extensions(self): - return [OpensshCertificateOption('extension', to_text(n), to_text(d)) for n, d in self._cert_info.extensions] + return [ + OpensshCertificateOption("extension", to_text(n), to_text(d)) + for n, d in self._cert_info.extensions + ] @property def reserved(self): @@ -564,7 +610,7 @@ class OpensshCertificate(object): @property def signature_type(self): signature_data = OpensshParser.signature_data(self.signature) - return to_text(signature_data['signature_type']) + return to_text(signature_data["signature_type"]) @staticmethod def _parse_cert_info(pub_key_type, parser): @@ -586,23 +632,24 @@ class OpensshCertificate(object): def to_dict(self): time_parameters = OpensshCertificateTimeParameters( - valid_from=self.valid_after, - valid_to=self.valid_before + valid_from=self.valid_after, valid_to=self.valid_before ) return { - 'type_string': self.type_string, - 'nonce': self.nonce, - 'serial': self.serial, - 'cert_type': self.type, - 'identifier': self.key_id, - 'principals': self.principals, - 'valid_after': time_parameters.valid_from(date_format='human_readable'), - 'valid_before': time_parameters.valid_to(date_format='human_readable'), - 'critical_options': [str(critical_option) for critical_option in self.critical_options], - 'extensions': [str(extension) for extension in self.extensions], - 'reserved': self.reserved, - 'public_key': self.public_key, - 'signing_key': self.signing_key, + "type_string": self.type_string, + "nonce": self.nonce, + "serial": self.serial, + "cert_type": self.type, + "identifier": self.key_id, + "principals": self.principals, + "valid_after": time_parameters.valid_from(date_format="human_readable"), + "valid_before": time_parameters.valid_to(date_format="human_readable"), + "critical_options": [ + str(critical_option) for critical_option in self.critical_options + ], + "extensions": [str(extension) for extension in self.extensions], + "reserved": self.reserved, + "public_key": self.public_key, + "signing_key": self.signing_key, } @@ -611,38 +658,46 @@ def apply_directives(directives): raise ValueError("directives must be one of %s" % ", ".join(_DIRECTIVES)) directive_to_option = { - 'no-x11-forwarding': OpensshCertificateOption('extension', 'permit-x11-forwarding', ''), - 'no-agent-forwarding': OpensshCertificateOption('extension', 'permit-agent-forwarding', ''), - 'no-port-forwarding': OpensshCertificateOption('extension', 'permit-port-forwarding', ''), - 'no-pty': OpensshCertificateOption('extension', 'permit-pty', ''), - 'no-user-rc': OpensshCertificateOption('extension', 'permit-user-rc', ''), + "no-x11-forwarding": OpensshCertificateOption( + "extension", "permit-x11-forwarding", "" + ), + "no-agent-forwarding": OpensshCertificateOption( + "extension", "permit-agent-forwarding", "" + ), + "no-port-forwarding": OpensshCertificateOption( + "extension", "permit-port-forwarding", "" + ), + "no-pty": OpensshCertificateOption("extension", "permit-pty", ""), + "no-user-rc": OpensshCertificateOption("extension", "permit-user-rc", ""), } - if 'clear' in directives: + if "clear" in directives: return [] else: - return list(set(default_options()) - set(directive_to_option[d] for d in directives)) + return list( + set(default_options()) - set(directive_to_option[d] for d in directives) + ) def default_options(): - return [OpensshCertificateOption('extension', name, '') for name in _EXTENSIONS] + return [OpensshCertificateOption("extension", name, "") for name in _EXTENSIONS] def fingerprint(public_key): """Generates a SHA256 hash and formats output to resemble ``ssh-keygen``""" h = sha256() h.update(public_key) - return b'SHA256:' + b64encode(h.digest()).rstrip(b'=') + return b"SHA256:" + b64encode(h.digest()).rstrip(b"=") def get_cert_info_object(key_type): - if key_type == 'rsa': + if key_type == "rsa": cert_info = OpensshRSACertificateInfo() - elif key_type == 'dsa': + elif key_type == "dsa": cert_info = OpensshDSACertificateInfo() - elif key_type in ('ecdsa-nistp256', 'ecdsa-nistp384', 'ecdsa-nistp521'): + elif key_type in ("ecdsa-nistp256", "ecdsa-nistp384", "ecdsa-nistp521"): cert_info = OpensshECDSACertificateInfo() - elif key_type == 'ed25519': + elif key_type == "ed25519": cert_info = OpensshED25519CertificateInfo() else: raise ValueError("%s is not a valid key type" % key_type) @@ -652,12 +707,14 @@ def get_cert_info_object(key_type): def get_option_type(name): if name in _CRITICAL_OPTIONS: - result = 'critical' + result = "critical" elif name in _EXTENSIONS: - result = 'extension' + result = "extension" else: - raise ValueError("%s is not a valid option. " % name + - "Custom options must start with 'critical:' or 'extension:' to indicate type") + raise ValueError( + "%s is not a valid option. " % name + + "Custom options must start with 'critical:' or 'extension:' to indicate type" + ) return result @@ -675,7 +732,7 @@ def parse_option_list(option_list): directives.append(option.lower()) else: option_object = OpensshCertificateOption.from_string(option) - if option_object.type == 'critical': + if option_object.type == "critical": critical_options.append(option_object) else: extensions.append(option_object) diff --git a/plugins/module_utils/openssh/cryptography.py b/plugins/module_utils/openssh/cryptography.py index 31a519ac..ee66dee8 100644 --- a/plugins/module_utils/openssh/cryptography.py +++ b/plugins/module_utils/openssh/cryptography.py @@ -38,41 +38,41 @@ try: HAS_OPENSSH_SUPPORT = True _ALGORITHM_PARAMETERS = { - 'rsa': { - 'default_size': 2048, - 'valid_sizes': range(1024, 16384), - 'signer_params': { - 'padding': padding.PSS( + "rsa": { + "default_size": 2048, + "valid_sizes": range(1024, 16384), + "signer_params": { + "padding": padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), - 'algorithm': hashes.SHA256(), + "algorithm": hashes.SHA256(), }, }, - 'dsa': { - 'default_size': 1024, - 'valid_sizes': [1024], - 'signer_params': { - 'algorithm': hashes.SHA256(), + "dsa": { + "default_size": 1024, + "valid_sizes": [1024], + "signer_params": { + "algorithm": hashes.SHA256(), }, }, - 'ed25519': { - 'default_size': 256, - 'valid_sizes': [256], - 'signer_params': {}, + "ed25519": { + "default_size": 256, + "valid_sizes": [256], + "signer_params": {}, }, - 'ecdsa': { - 'default_size': 256, - 'valid_sizes': [256, 384, 521], - 'signer_params': { - 'signature_algorithm': ec.ECDSA(hashes.SHA256()), + "ecdsa": { + "default_size": 256, + "valid_sizes": [256, 384, 521], + "signer_params": { + "signature_algorithm": ec.ECDSA(hashes.SHA256()), }, - 'curves': { + "curves": { 256: ec.SECP256R1(), 384: ec.SECP384R1(), 521: ec.SECP521R1(), - } - } + }, + }, } except ImportError: HAS_OPENSSH_PRIVATE_FORMAT = False @@ -80,7 +80,7 @@ except ImportError: CRYPTOGRAPHY_VERSION = "0.0" _ALGORITHM_PARAMETERS = {} -_TEXT_ENCODING = 'UTF-8' +_TEXT_ENCODING = "UTF-8" class OpenSSHError(Exception): @@ -131,26 +131,25 @@ class AsymmetricKeypair(object): """Container for newly generated asymmetric key pairs or those loaded from existing files""" @classmethod - def generate(cls, keytype='rsa', size=None, passphrase=None): + def generate(cls, keytype="rsa", size=None, passphrase=None): """Returns an Asymmetric_Keypair object generated with the supplied parameters - or defaults to an unencrypted RSA-2048 key + or defaults to an unencrypted RSA-2048 key - :keytype: One of rsa, dsa, ecdsa, ed25519 - :size: The key length for newly generated keys - :passphrase: Secret of type Bytes used to encrypt the private key being generated + :keytype: One of rsa, dsa, ecdsa, ed25519 + :size: The key length for newly generated keys + :passphrase: Secret of type Bytes used to encrypt the private key being generated """ if keytype not in _ALGORITHM_PARAMETERS.keys(): raise InvalidKeyTypeError( - "%s is not a valid keytype. Valid keytypes are %s" % ( - keytype, ", ".join(_ALGORITHM_PARAMETERS.keys()) - ) + "%s is not a valid keytype. Valid keytypes are %s" + % (keytype, ", ".join(_ALGORITHM_PARAMETERS.keys())) ) if not size: - size = _ALGORITHM_PARAMETERS[keytype]['default_size'] + size = _ALGORITHM_PARAMETERS[keytype]["default_size"] else: - if size not in _ALGORITHM_PARAMETERS[keytype]['valid_sizes']: + if size not in _ALGORITHM_PARAMETERS[keytype]["valid_sizes"]: raise InvalidKeySizeError( "%s is not a valid key size for %s keys" % (size, keytype) ) @@ -160,7 +159,7 @@ class AsymmetricKeypair(object): else: encryption_algorithm = serialization.NoEncryption() - if keytype == 'rsa': + if keytype == "rsa": privatekey = rsa.generate_private_key( # Public exponent should always be 65537 to prevent issues # if improper padding is used during signing @@ -168,16 +167,16 @@ class AsymmetricKeypair(object): key_size=size, backend=backend, ) - elif keytype == 'dsa': + elif keytype == "dsa": privatekey = dsa.generate_private_key( key_size=size, backend=backend, ) - elif keytype == 'ed25519': + elif keytype == "ed25519": privatekey = Ed25519PrivateKey.generate() - elif keytype == 'ecdsa': + elif keytype == "ecdsa": privatekey = ec.generate_private_key( - _ALGORITHM_PARAMETERS['ecdsa']['curves'][size], + _ALGORITHM_PARAMETERS["ecdsa"]["curves"][size], backend=backend, ) @@ -188,18 +187,25 @@ class AsymmetricKeypair(object): size=size, privatekey=privatekey, publickey=publickey, - encryption_algorithm=encryption_algorithm + encryption_algorithm=encryption_algorithm, ) @classmethod - def load(cls, path, passphrase=None, private_key_format='PEM', public_key_format='PEM', no_public_key=False): + def load( + cls, + path, + passphrase=None, + private_key_format="PEM", + public_key_format="PEM", + no_public_key=False, + ): """Returns an Asymmetric_Keypair object loaded from the supplied file path - :path: A path to an existing private key to be loaded - :passphrase: Secret of type bytes used to decrypt the private key being loaded - :private_key_format: Format of private key to be loaded - :public_key_format: Format of public key to be loaded - :no_public_key: Set 'True' to only load a private key and automatically populate the matching public key + :path: A path to an existing private key to be loaded + :passphrase: Secret of type bytes used to decrypt the private key being loaded + :private_key_format: Format of private key to be loaded + :public_key_format: Format of public key to be loaded + :no_public_key: Set 'True' to only load a private key and automatically populate the matching public key """ if passphrase: @@ -211,40 +217,42 @@ class AsymmetricKeypair(object): if no_public_key: publickey = privatekey.public_key() else: - publickey = load_publickey(path + '.pub', public_key_format) + publickey = load_publickey(path + ".pub", public_key_format) # Ed25519 keys are always of size 256 and do not have a key_size attribute if isinstance(privatekey, Ed25519PrivateKey): - size = _ALGORITHM_PARAMETERS['ed25519']['default_size'] + size = _ALGORITHM_PARAMETERS["ed25519"]["default_size"] else: size = privatekey.key_size if isinstance(privatekey, rsa.RSAPrivateKey): - keytype = 'rsa' + keytype = "rsa" elif isinstance(privatekey, dsa.DSAPrivateKey): - keytype = 'dsa' + keytype = "dsa" elif isinstance(privatekey, ec.EllipticCurvePrivateKey): - keytype = 'ecdsa' + keytype = "ecdsa" elif isinstance(privatekey, Ed25519PrivateKey): - keytype = 'ed25519' + keytype = "ed25519" else: - raise InvalidKeyTypeError("Key type '%s' is not supported" % type(privatekey)) + raise InvalidKeyTypeError( + "Key type '%s' is not supported" % type(privatekey) + ) return cls( keytype=keytype, size=size, privatekey=privatekey, publickey=publickey, - encryption_algorithm=encryption_algorithm + encryption_algorithm=encryption_algorithm, ) def __init__(self, keytype, size, privatekey, publickey, encryption_algorithm): """ - :keytype: One of rsa, dsa, ecdsa, ed25519 - :size: The key length for the private key of this key pair - :privatekey: Private key object of this key pair - :publickey: Public key object of this key pair - :encryption_algorithm: Hashed secret used to encrypt the private key of this key pair + :keytype: One of rsa, dsa, ecdsa, ed25519 + :size: The key length for the private key of this key pair + :privatekey: Private key object of this key pair + :publickey: Public key object of this key pair + :encryption_algorithm: Hashed secret used to encrypt the private key of this key pair """ self.__size = size @@ -254,7 +262,7 @@ class AsymmetricKeypair(object): self.__encryption_algorithm = encryption_algorithm try: - self.verify(self.sign(b'message'), b'message') + self.verify(self.sign(b"message"), b"message") except InvalidSignatureError: raise InvalidPublicKeyFileError( "The private key and public key of this keypair do not match" @@ -264,8 +272,11 @@ class AsymmetricKeypair(object): if not isinstance(other, AsymmetricKeypair): return NotImplemented - return (compare_publickeys(self.public_key, other.public_key) and - compare_encryption_algorithms(self.encryption_algorithm, other.encryption_algorithm)) + return compare_publickeys( + self.public_key, other.public_key + ) and compare_encryption_algorithms( + self.encryption_algorithm, other.encryption_algorithm + ) def __ne__(self, other): return not self == other @@ -303,13 +314,12 @@ class AsymmetricKeypair(object): def sign(self, data): """Returns signature of data signed with the private key of this key pair - :data: byteslike data to sign + :data: byteslike data to sign """ try: signature = self.__privatekey.sign( - data, - **_ALGORITHM_PARAMETERS[self.__keytype]['signer_params'] + data, **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] ) except TypeError as e: raise InvalidDataError(e) @@ -318,16 +328,16 @@ class AsymmetricKeypair(object): def verify(self, signature, data): """Verifies that the signature associated with the provided data was signed - by the private key of this key pair. + by the private key of this key pair. - :signature: signature to verify - :data: byteslike data signed by the provided signature + :signature: signature to verify + :data: byteslike data signed by the provided signature """ try: return self.__publickey.verify( signature, data, - **_ALGORITHM_PARAMETERS[self.__keytype]['signer_params'] + **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] ) except InvalidSignature: raise InvalidSignatureError @@ -335,7 +345,7 @@ class AsymmetricKeypair(object): def update_passphrase(self, passphrase=None): """Updates the encryption algorithm of this key pair - :passphrase: Byte secret used to encrypt this key pair + :passphrase: Byte secret used to encrypt this key pair """ if passphrase: @@ -348,20 +358,20 @@ class OpensshKeypair(object): """Container for OpenSSH encoded asymmetric key pairs""" @classmethod - def generate(cls, keytype='rsa', size=None, passphrase=None, comment=None): + def generate(cls, keytype="rsa", size=None, passphrase=None, comment=None): """Returns an Openssh_Keypair object generated using the supplied parameters or defaults to a RSA-2048 key - :keytype: One of rsa, dsa, ecdsa, ed25519 - :size: The key length for newly generated keys - :passphrase: Secret of type Bytes used to encrypt the newly generated private key - :comment: Comment for a newly generated OpenSSH public key + :keytype: One of rsa, dsa, ecdsa, ed25519 + :size: The key length for newly generated keys + :passphrase: Secret of type Bytes used to encrypt the newly generated private key + :comment: Comment for a newly generated OpenSSH public key """ if comment is None: comment = "%s@%s" % (getuser(), gethostname()) asym_keypair = AsymmetricKeypair.generate(keytype, size, passphrase) - openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, 'SSH') + openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, "SSH") openssh_publickey = cls.encode_openssh_publickey(asym_keypair, comment) fingerprint = calculate_fingerprint(openssh_publickey) @@ -377,18 +387,20 @@ class OpensshKeypair(object): def load(cls, path, passphrase=None, no_public_key=False): """Returns an Openssh_Keypair object loaded from the supplied file path - :path: A path to an existing private key to be loaded - :passphrase: Secret used to decrypt the private key being loaded - :no_public_key: Set 'True' to only load a private key and automatically populate the matching public key + :path: A path to an existing private key to be loaded + :passphrase: Secret used to decrypt the private key being loaded + :no_public_key: Set 'True' to only load a private key and automatically populate the matching public key """ if no_public_key: comment = "" else: - comment = extract_comment(path + '.pub') + comment = extract_comment(path + ".pub") - asym_keypair = AsymmetricKeypair.load(path, passphrase, 'SSH', 'SSH', no_public_key) - openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, 'SSH') + asym_keypair = AsymmetricKeypair.load( + path, passphrase, "SSH", "SSH", no_public_key + ) + openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, "SSH") openssh_publickey = cls.encode_openssh_publickey(asym_keypair, comment) fingerprint = calculate_fingerprint(openssh_publickey) @@ -404,29 +416,33 @@ class OpensshKeypair(object): def encode_openssh_privatekey(asym_keypair, key_format): """Returns an OpenSSH encoded private key for a given keypair - :asym_keypair: Asymmetric_Keypair from the private key is extracted - :key_format: Format of the encoded private key. + :asym_keypair: Asymmetric_Keypair from the private key is extracted + :key_format: Format of the encoded private key. """ - if key_format == 'SSH': + if key_format == "SSH": # Default to PEM format if SSH not available if not HAS_OPENSSH_PRIVATE_FORMAT: privatekey_format = serialization.PrivateFormat.PKCS8 else: privatekey_format = serialization.PrivateFormat.OpenSSH - elif key_format == 'PKCS8': + elif key_format == "PKCS8": privatekey_format = serialization.PrivateFormat.PKCS8 - elif key_format == 'PKCS1': - if asym_keypair.key_type == 'ed25519': - raise InvalidKeyFormatError("ed25519 keys cannot be represented in PKCS1 format") + elif key_format == "PKCS1": + if asym_keypair.key_type == "ed25519": + raise InvalidKeyFormatError( + "ed25519 keys cannot be represented in PKCS1 format" + ) privatekey_format = serialization.PrivateFormat.TraditionalOpenSSL else: - raise InvalidKeyFormatError("The accepted private key formats are SSH, PKCS8, and PKCS1") + raise InvalidKeyFormatError( + "The accepted private key formats are SSH, PKCS8, and PKCS1" + ) encoded_privatekey = asym_keypair.private_key.private_bytes( encoding=serialization.Encoding.PEM, format=privatekey_format, - encryption_algorithm=asym_keypair.encryption_algorithm + encryption_algorithm=asym_keypair.encryption_algorithm, ) return encoded_privatekey @@ -435,8 +451,8 @@ class OpensshKeypair(object): def encode_openssh_publickey(asym_keypair, comment): """Returns an OpenSSH encoded public key for a given keypair - :asym_keypair: Asymmetric_Keypair from the public key is extracted - :comment: Comment to apply to the end of the returned OpenSSH encoded public key + :asym_keypair: Asymmetric_Keypair from the public key is extracted + :comment: Comment to apply to the end of the returned OpenSSH encoded public key """ encoded_publickey = asym_keypair.public_key.public_bytes( encoding=serialization.Encoding.OpenSSH, @@ -445,17 +461,21 @@ class OpensshKeypair(object): validate_comment(comment) - encoded_publickey += (" %s" % comment).encode(encoding=_TEXT_ENCODING) if comment else b'' + encoded_publickey += ( + (" %s" % comment).encode(encoding=_TEXT_ENCODING) if comment else b"" + ) return encoded_publickey - def __init__(self, asym_keypair, openssh_privatekey, openssh_publickey, fingerprint, comment): + def __init__( + self, asym_keypair, openssh_privatekey, openssh_publickey, fingerprint, comment + ): """ - :asym_keypair: An Asymmetric_Keypair object from which the OpenSSH encoded keypair is derived - :openssh_privatekey: An OpenSSH encoded private key - :openssh_privatekey: An OpenSSH encoded public key - :fingerprint: The fingerprint of the OpenSSH encoded public key of this keypair - :comment: Comment applied to the OpenSSH public key of this keypair + :asym_keypair: An Asymmetric_Keypair object from which the OpenSSH encoded keypair is derived + :openssh_privatekey: An OpenSSH encoded private key + :openssh_privatekey: An OpenSSH encoded public key + :fingerprint: The fingerprint of the OpenSSH encoded public key of this keypair + :comment: Comment applied to the OpenSSH public key of this keypair """ self.__asym_keypair = asym_keypair @@ -468,7 +488,10 @@ class OpensshKeypair(object): if not isinstance(other, OpensshKeypair): return NotImplemented - return self.asymmetric_keypair == other.asymmetric_keypair and self.comment == other.comment + return ( + self.asymmetric_keypair == other.asymmetric_keypair + and self.comment == other.comment + ) @property def asymmetric_keypair(self): @@ -516,53 +539,59 @@ class OpensshKeypair(object): def comment(self, comment): """Updates the comment applied to the OpenSSH formatted public key of this key pair - :comment: Text to update the OpenSSH public key comment + :comment: Text to update the OpenSSH public key comment """ validate_comment(comment) self.__comment = comment - encoded_comment = (" %s" % self.__comment).encode(encoding=_TEXT_ENCODING) if self.__comment else b'' - self.__openssh_publickey = b' '.join(self.__openssh_publickey.split(b' ', 2)[:2]) + encoded_comment + encoded_comment = ( + (" %s" % self.__comment).encode(encoding=_TEXT_ENCODING) + if self.__comment + else b"" + ) + self.__openssh_publickey = ( + b" ".join(self.__openssh_publickey.split(b" ", 2)[:2]) + encoded_comment + ) return self.__openssh_publickey def update_passphrase(self, passphrase): """Updates the passphrase used to encrypt the private key of this keypair - :passphrase: Text secret used for encryption + :passphrase: Text secret used for encryption """ self.__asym_keypair.update_passphrase(passphrase) - self.__openssh_privatekey = OpensshKeypair.encode_openssh_privatekey(self.__asym_keypair, 'SSH') + self.__openssh_privatekey = OpensshKeypair.encode_openssh_privatekey( + self.__asym_keypair, "SSH" + ) def load_privatekey(path, passphrase, key_format): privatekey_loaders = { - 'PEM': serialization.load_pem_private_key, - 'DER': serialization.load_der_private_key, + "PEM": serialization.load_pem_private_key, + "DER": serialization.load_der_private_key, } # OpenSSH formatted private keys are not available in Cryptography <3.0 - if hasattr(serialization, 'load_ssh_private_key'): - privatekey_loaders['SSH'] = serialization.load_ssh_private_key + if hasattr(serialization, "load_ssh_private_key"): + privatekey_loaders["SSH"] = serialization.load_ssh_private_key else: - privatekey_loaders['SSH'] = serialization.load_pem_private_key + privatekey_loaders["SSH"] = serialization.load_pem_private_key try: privatekey_loader = privatekey_loaders[key_format] except KeyError: raise InvalidKeyFormatError( - "%s is not a valid key format (%s)" % ( - key_format, - ','.join(privatekey_loaders.keys()) - ) + "%s is not a valid key format (%s)" + % (key_format, ",".join(privatekey_loaders.keys())) ) if not os.path.exists(path): raise InvalidPrivateKeyFileError("No file was found at %s" % path) try: - with open(path, 'rb') as f: + with open(path, "rb") as f: content = f.read() privatekey = privatekey_loader( @@ -573,9 +602,9 @@ def load_privatekey(path, passphrase, key_format): except ValueError as e: # Revert to PEM if key could not be loaded in SSH format - if key_format == 'SSH': + if key_format == "SSH": try: - privatekey = privatekey_loaders['PEM']( + privatekey = privatekey_loaders["PEM"]( data=content, password=passphrase, backend=backend, @@ -598,26 +627,24 @@ def load_privatekey(path, passphrase, key_format): def load_publickey(path, key_format): publickey_loaders = { - 'PEM': serialization.load_pem_public_key, - 'DER': serialization.load_der_public_key, - 'SSH': serialization.load_ssh_public_key, + "PEM": serialization.load_pem_public_key, + "DER": serialization.load_der_public_key, + "SSH": serialization.load_ssh_public_key, } try: publickey_loader = publickey_loaders[key_format] except KeyError: raise InvalidKeyFormatError( - "%s is not a valid key format (%s)" % ( - key_format, - ','.join(publickey_loaders.keys()) - ) + "%s is not a valid key format (%s)" + % (key_format, ",".join(publickey_loaders.keys())) ) if not os.path.exists(path): raise InvalidPublicKeyFileError("No file was found at %s" % path) try: - with open(path, 'rb') as f: + with open(path, "rb") as f: content = f.read() publickey = publickey_loader( @@ -646,10 +673,13 @@ def compare_publickeys(pk1, pk2): def compare_encryption_algorithms(ea1, ea2): - if isinstance(ea1, serialization.NoEncryption) and isinstance(ea2, serialization.NoEncryption): + if isinstance(ea1, serialization.NoEncryption) and isinstance( + ea2, serialization.NoEncryption + ): return True - elif (isinstance(ea1, serialization.BestAvailableEncryption) and - isinstance(ea2, serialization.BestAvailableEncryption)): + elif isinstance(ea1, serialization.BestAvailableEncryption) and isinstance( + ea2, serialization.BestAvailableEncryption + ): return ea1.password == ea2.password else: return False @@ -663,7 +693,7 @@ def get_encryption_algorithm(passphrase): def validate_comment(comment): - if not hasattr(comment, 'encode'): + if not hasattr(comment, "encode"): raise InvalidCommentError("%s cannot be encoded to text" % comment) @@ -673,8 +703,8 @@ def extract_comment(path): raise InvalidPublicKeyFileError("No file was found at %s" % path) try: - with open(path, 'rb') as f: - fields = f.read().split(b' ', 2) + with open(path, "rb") as f: + fields = f.read().split(b" ", 2) if len(fields) == 3: comment = fields[2].decode(_TEXT_ENCODING) else: @@ -687,7 +717,9 @@ def extract_comment(path): def calculate_fingerprint(openssh_publickey): digest = hashes.Hash(hashes.SHA256(), backend=backend) - decoded_pubkey = b64decode(openssh_publickey.split(b' ')[1]) + decoded_pubkey = b64decode(openssh_publickey.split(b" ")[1]) digest.update(decoded_pubkey) - return 'SHA256:%s' % b64encode(digest.finalize()).decode(encoding=_TEXT_ENCODING).rstrip('=') + return "SHA256:%s" % b64encode(digest.finalize()).decode( + encoding=_TEXT_ENCODING + ).rstrip("=") diff --git a/plugins/module_utils/openssh/utils.py b/plugins/module_utils/openssh/utils.py index f3d72759..0e56672f 100644 --- a/plugins/module_utils/openssh/utils.py +++ b/plugins/module_utils/openssh/utils.py @@ -34,17 +34,17 @@ if PY3: long = int # 0 (False) or 1 (True) encoded as a single byte -_BOOLEAN = Struct(b'?') +_BOOLEAN = Struct(b"?") # Unsigned 8-bit integer in network-byte-order -_UBYTE = Struct(b'!B') +_UBYTE = Struct(b"!B") _UBYTE_MAX = 0xFF # Unsigned 32-bit integer in network-byte-order -_UINT32 = Struct(b'!I') +_UINT32 = Struct(b"!I") # Unsigned 32-bit little endian integer -_UINT32_LE = Struct(b' _UINT32_MAX: - raise ValueError("Value must be a positive integer less than %s" % _UINT32_MAX) + raise ValueError( + "Value must be a positive integer less than %s" % _UINT32_MAX + ) self._buff.extend(_UINT32.pack(value)) @@ -293,7 +315,9 @@ class _OpensshWriter(object): if not isinstance(value, (long, int)): raise TypeError("Value must be of type (long, int) not %s" % type(value)) if value < 0 or value > _UINT64_MAX: - raise ValueError("Value must be a positive integer less than %s" % _UINT64_MAX) + raise ValueError( + "Value must be a positive integer less than %s" % _UINT64_MAX + ) self._buff.extend(_UINT64.pack(value)) @@ -320,7 +344,7 @@ class _OpensshWriter(object): raise TypeError("Value must be a list of byte strings not %s" % type(value)) try: - self.string(','.join(value).encode('ASCII')) + self.string(",".join(value).encode("ASCII")) except UnicodeEncodeError as e: raise ValueError("Name-list's must consist of US-ASCII characters: %s" % e) @@ -365,9 +389,9 @@ class _OpensshWriter(object): result = bytes() # 0 and -1 are treated as special cases since they are used as sentinels for all other values if num == 0: - result += b'\x00' + result += b"\x00" elif num == -1: - result += b'\xFF' + result += b"\xff" elif num > 0: while num >> 32: result = _UINT32.pack(num & _UINT32_MAX) + result @@ -378,7 +402,7 @@ class _OpensshWriter(object): num = num >> 8 # Zero pad final byte if most-significant bit is 1 as per mpint definition if ord(result[0]) & 0x80: - result = b'\x00' + result + result = b"\x00" + result else: while (num >> 32) < -1: result = _UINT32.pack(num & _UINT32_MAX) + result @@ -387,7 +411,7 @@ class _OpensshWriter(object): result = _UBYTE.pack(num & _UBYTE_MAX) + result num = num >> 8 if not ord(result[0]) & 0x80: - result = b'\xFF' + result + result = b"\xff" + result return result diff --git a/plugins/module_utils/serial.py b/plugins/module_utils/serial.py index f967c167..7dce6b12 100644 --- a/plugins/module_utils/serial.py +++ b/plugins/module_utils/serial.py @@ -21,12 +21,12 @@ def th(number): mod_100 = abs_number % 100 if mod_100 not in (11, 12, 13): if mod_10 == 1: - return 'st' + return "st" if mod_10 == 2: - return 'nd' + return "nd" if mod_10 == 3: - return 'rd' - return 'th' + return "rd" + return "th" def parse_serial(value): @@ -35,14 +35,17 @@ def parse_serial(value): """ value = to_native(value) result = 0 - for i, part in enumerate(value.split(':')): + for i, part in enumerate(value.split(":")): try: part_value = int(part, 16) if part_value < 0 or part_value > 255: - raise ValueError('the value is not in range [0, 255]') + raise ValueError("the value is not in range [0, 255]") except ValueError as exc: - raise ValueError("The {idx}{th} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}".format( - idx=i + 1, th=th(i + 1), part=part, exc=exc)) + raise ValueError( + "The {idx}{th} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}".format( + idx=i + 1, th=th(i + 1), part=part, exc=exc + ) + ) result = (result << 8) | part_value return result @@ -53,5 +56,5 @@ def to_serial(value): """ value = convert_int_to_hex(value).upper() if len(value) % 2 != 0: - value = '0' + value - return ':'.join(value[i:i + 2] for i in range(0, len(value), 2)) + value = "0" + value + return ":".join(value[i : i + 2] for i in range(0, len(value), 2)) diff --git a/plugins/module_utils/time.py b/plugins/module_utils/time.py index e4e2fdb4..bc9e8e63 100644 --- a/plugins/module_utils/time.py +++ b/plugins/module_utils/time.py @@ -33,13 +33,13 @@ except AttributeError: return _DURATION_ZERO def tzname(self, dt): - return 'UTC' + return "UTC" def fromutc(self, dt): return dt def __repr__(self): - return 'UTC' + return "UTC" UTC = _UTCClass() @@ -69,20 +69,29 @@ def remove_timezone(timestamp): def add_or_remove_timezone(timestamp, with_timezone): - return ensure_utc_timezone(timestamp) if with_timezone else remove_timezone(timestamp) + return ( + ensure_utc_timezone(timestamp) if with_timezone else remove_timezone(timestamp) + ) if sys.version_info < (3, 3): + def get_epoch_seconds(timestamp): - epoch = datetime.datetime(1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None) + epoch = datetime.datetime( + 1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None + ) delta = timestamp - epoch try: return delta.total_seconds() except AttributeError: # Python 2.6 and earlier: total_seconds() does not yet exist, so we use the formula from # https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds - return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 + return ( + delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6 + ) / 10**6 + else: + def get_epoch_seconds(timestamp): if timestamp.tzinfo is None: # timestamp.timestamp() is offset by the local timezone if timestamp has no timezone @@ -101,7 +110,8 @@ def convert_relative_to_datetime(relative_time_string, with_timezone=False, now= parsed_result = re.match( r"^(?P[+-])((?P\d+)[wW])?((?P\d+)[dD])?((?P\d+)[hH])?((?P\d+)[mM])?((?P\d+)[sS]?)?$", - relative_time_string) + relative_time_string, + ) if parsed_result is None or len(relative_time_string) == 1: # not matched or only a single "+" or "-" @@ -115,11 +125,9 @@ def convert_relative_to_datetime(relative_time_string, with_timezone=False, now= if parsed_result.group("hours") is not None: offset += datetime.timedelta(hours=int(parsed_result.group("hours"))) if parsed_result.group("minutes") is not None: - offset += datetime.timedelta( - minutes=int(parsed_result.group("minutes"))) + offset += datetime.timedelta(minutes=int(parsed_result.group("minutes"))) if parsed_result.group("seconds") is not None: - offset += datetime.timedelta( - seconds=int(parsed_result.group("seconds"))) + offset += datetime.timedelta(seconds=int(parsed_result.group("seconds"))) if now is None: now = get_now_datetime(with_timezone=with_timezone) @@ -132,33 +140,43 @@ def convert_relative_to_datetime(relative_time_string, with_timezone=False, now= return now - offset -def get_relative_time_option(input_string, input_name, backend='cryptography', with_timezone=False, now=None): +def get_relative_time_option( + input_string, input_name, backend="cryptography", with_timezone=False, now=None +): """Return an absolute timespec if a relative timespec or an ASN1 formatted - string is provided. + string is provided. - The return value will be a datetime object for the cryptography backend, - and a ASN1 formatted string for the pyopenssl backend.""" + The return value will be a datetime object for the cryptography backend, + and a ASN1 formatted string for the pyopenssl backend.""" result = to_native(input_string) if result is None: raise OpenSSLObjectError( - 'The timespec "%s" for %s is not valid' % - input_string, input_name) + 'The timespec "%s" for %s is not valid' % input_string, input_name + ) # Relative time if result.startswith("+") or result.startswith("-"): - result_datetime = convert_relative_to_datetime(result, with_timezone=with_timezone, now=now) - if backend == 'pyopenssl': + result_datetime = convert_relative_to_datetime( + result, with_timezone=with_timezone, now=now + ) + if backend == "pyopenssl": return result_datetime.strftime("%Y%m%d%H%M%SZ") - elif backend == 'cryptography': + elif backend == "cryptography": return result_datetime # Absolute time - if backend == 'pyopenssl': + if backend == "pyopenssl": return input_string - elif backend == 'cryptography': + elif backend == "cryptography": for date_fmt, length in [ - ('%Y%m%d%H%M%SZ', 15), # this also parses '202401020304Z', but as datetime(2024, 1, 2, 3, 0, 4) - ('%Y%m%d%H%MZ', 13), - ('%Y%m%d%H%M%S%z', 14 + 5), # this also parses '202401020304+0000', but as datetime(2024, 1, 2, 3, 0, 4, tzinfo=...) - ('%Y%m%d%H%M%z', 12 + 5), + ( + "%Y%m%d%H%M%SZ", + 15, + ), # this also parses '202401020304Z', but as datetime(2024, 1, 2, 3, 0, 4) + ("%Y%m%d%H%MZ", 13), + ( + "%Y%m%d%H%M%S%z", + 14 + 5, + ), # this also parses '202401020304+0000', but as datetime(2024, 1, 2, 3, 0, 4, tzinfo=...) + ("%Y%m%d%H%M%z", 12 + 5), ]: if len(result) != length: continue @@ -170,6 +188,5 @@ def get_relative_time_option(input_string, input_name, backend='cryptography', w return add_or_remove_timezone(res, with_timezone=with_timezone) raise OpenSSLObjectError( - 'The time spec "%s" for %s is invalid' % - (input_string, input_name) + 'The time spec "%s" for %s is invalid' % (input_string, input_name) ) diff --git a/plugins/modules/acme_account.py b/plugins/modules/acme_account.py index c8c8e076..400cc519 100644 --- a/plugins/modules/acme_account.py +++ b/plugins/modules/acme_account.py @@ -188,73 +188,88 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec() argument_spec.update_argspec( - terms_agreed=dict(type='bool', default=False), - state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']), - allow_creation=dict(type='bool', default=True), - contact=dict(type='list', elements='str', default=[]), - new_account_key_src=dict(type='path'), - new_account_key_content=dict(type='str', no_log=True), - new_account_key_passphrase=dict(type='str', no_log=True), - external_account_binding=dict(type='dict', options=dict( - kid=dict(type='str', required=True), - alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']), - key=dict(type='str', required=True, no_log=True), - )) + terms_agreed=dict(type="bool", default=False), + state=dict( + type="str", required=True, choices=["absent", "present", "changed_key"] + ), + allow_creation=dict(type="bool", default=True), + contact=dict(type="list", elements="str", default=[]), + new_account_key_src=dict(type="path"), + new_account_key_content=dict(type="str", no_log=True), + new_account_key_passphrase=dict(type="str", no_log=True), + external_account_binding=dict( + type="dict", + options=dict( + kid=dict(type="str", required=True), + alg=dict( + type="str", required=True, choices=["HS256", "HS384", "HS512"] + ), + key=dict(type="str", required=True, no_log=True), + ), + ), ) argument_spec.update( - mutually_exclusive=( - ['new_account_key_src', 'new_account_key_content'], - ), + mutually_exclusive=(["new_account_key_src", "new_account_key_content"],), required_if=( # Make sure that for state == changed_key, one of # new_account_key_src and new_account_key_content are specified - ['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True], + [ + "state", + "changed_key", + ["new_account_key_src", "new_account_key_content"], + True, + ], ), ) module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) - if module.params['external_account_binding']: + if module.params["external_account_binding"]: # Make sure padding is there - key = module.params['external_account_binding']['key'] + key = module.params["external_account_binding"]["key"] if len(key) % 4 != 0: - key = key + ('=' * (4 - (len(key) % 4))) + key = key + ("=" * (4 - (len(key) % 4))) # Make sure key is Base64 encoded try: base64.urlsafe_b64decode(key) except Exception as e: - module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e) - module.params['external_account_binding']['key'] = key + module.fail_json( + msg="Key for external_account_binding must be Base64 URL encoded (%s)" + % e + ) + module.params["external_account_binding"]["key"] = key try: client = ACMEClient(module, backend) account = ACMEAccount(client) changed = False - state = module.params.get('state') + state = module.params.get("state") diff_before = {} diff_after = {} - if state == 'absent': + if state == "absent": created, account_data = account.setup_account(allow_creation=False) if account_data: diff_before = dict(account_data) - diff_before['public_account_key'] = client.account_key_data['jwk'] + diff_before["public_account_key"] = client.account_key_data["jwk"] if created: - raise AssertionError('Unwanted account creation') + raise AssertionError("Unwanted account creation") if account_data is not None: # Account is not yet deactivated if not module.check_mode: # Deactivate it - payload = { - 'status': 'deactivated' - } + payload = {"status": "deactivated"} result, info = client.send_signed_request( - client.account_uri, payload, error_msg='Failed to deactivate account', expected_status_codes=[200]) + client.account_uri, + payload, + error_msg="Failed to deactivate account", + expected_status_codes=[200], + ) changed = True - elif state == 'present': - allow_creation = module.params.get('allow_creation') - contact = [str(v) for v in module.params.get('contact')] - terms_agreed = module.params.get('terms_agreed') - external_account_binding = module.params.get('external_account_binding') + elif state == "present": + allow_creation = module.params.get("allow_creation") + contact = [str(v) for v in module.params.get("contact")] + terms_agreed = module.params.get("terms_agreed") + external_account_binding = module.params.get("external_account_binding") created, account_data = account.setup_account( contact, terms_agreed=terms_agreed, @@ -262,77 +277,87 @@ def main(): external_account_binding=external_account_binding, ) if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException( + msg="Account does not exist or is deactivated." + ) if created: diff_before = {} else: diff_before = dict(account_data) - diff_before['public_account_key'] = client.account_key_data['jwk'] + diff_before["public_account_key"] = client.account_key_data["jwk"] updated = False if not created: updated, account_data = account.update_account(account_data, contact) changed = created or updated diff_after = dict(account_data) - diff_after['public_account_key'] = client.account_key_data['jwk'] - elif state == 'changed_key': + diff_after["public_account_key"] = client.account_key_data["jwk"] + elif state == "changed_key": # Parse new account key try: new_key_data = client.parse_key( - module.params.get('new_account_key_src'), - module.params.get('new_account_key_content'), - passphrase=module.params.get('new_account_key_passphrase'), + module.params.get("new_account_key_src"), + module.params.get("new_account_key_content"), + passphrase=module.params.get("new_account_key_passphrase"), ) except KeyParsingError as e: - raise ModuleFailException("Error while parsing new account key: {msg}".format(msg=e.msg)) + raise ModuleFailException( + "Error while parsing new account key: {msg}".format(msg=e.msg) + ) # Verify that the account exists and has not been deactivated created, account_data = account.setup_account(allow_creation=False) if created: - raise AssertionError('Unwanted account creation') + raise AssertionError("Unwanted account creation") if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException( + msg="Account does not exist or is deactivated." + ) diff_before = dict(account_data) - diff_before['public_account_key'] = client.account_key_data['jwk'] + diff_before["public_account_key"] = client.account_key_data["jwk"] # Now we can start the account key rollover if not module.check_mode: # Compose inner signed message # https://tools.ietf.org/html/rfc8555#section-7.3.5 - url = client.directory['keyChange'] + url = client.directory["keyChange"] protected = { - "alg": new_key_data['alg'], - "jwk": new_key_data['jwk'], + "alg": new_key_data["alg"], + "jwk": new_key_data["jwk"], "url": url, } payload = { "account": client.account_uri, - "newKey": new_key_data['jwk'], # specified in draft 12 and older + "newKey": new_key_data["jwk"], # specified in draft 12 and older "oldKey": client.account_jwk, # specified in draft 13 and newer } data = client.sign_request(protected, payload, new_key_data) # Send request and verify result result, info = client.send_signed_request( - url, data, error_msg='Failed to rollover account key', expected_status_codes=[200]) + url, + data, + error_msg="Failed to rollover account key", + expected_status_codes=[200], + ) if module._diff: client.account_key_data = new_key_data - client.account_jws_header['alg'] = new_key_data['alg'] + client.account_jws_header["alg"] = new_key_data["alg"] diff_after = account.get_account_data() elif module._diff: # Kind of fake diff_after diff_after = dict(diff_before) - diff_after['public_account_key'] = new_key_data['jwk'] + diff_after["public_account_key"] = new_key_data["jwk"] changed = True result = { - 'changed': changed, - 'account_uri': client.account_uri, + "changed": changed, + "account_uri": client.account_uri, } if module._diff: - result['diff'] = { - 'before': diff_before, - 'after': diff_after, + result["diff"] = { + "before": diff_before, + "after": diff_after, } module.exit_json(**result) except ModuleFailException as e: e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_account_info.py b/plugins/modules/acme_account_info.py index 2e4cd7fd..694cca76 100644 --- a/plugins/modules/acme_account_info.py +++ b/plugins/modules/acme_account_info.py @@ -226,24 +226,30 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import def get_orders_list(module, client, orders_url): - ''' + """ Retrieves orders list (handles pagination). - ''' + """ orders = [] while orders_url: # Get part of orders list - res, info = client.get_request(orders_url, parse_json_result=True, fail_on_error=True) - if not res.get('orders'): + res, info = client.get_request( + orders_url, parse_json_result=True, fail_on_error=True + ) + if not res.get("orders"): if orders: - module.warn('When retrieving orders list part {0}, got empty result list'.format(orders_url)) + module.warn( + "When retrieving orders list part {0}, got empty result list".format( + orders_url + ) + ) break # Add order URLs to result list - orders.extend(res['orders']) + orders.extend(res["orders"]) # Extract URL of next part of results list new_orders_url = [] def f(link, relation): - if relation == 'next': + if relation == "next": new_orders_url.append(link) process_links(info, f) @@ -256,16 +262,18 @@ def get_orders_list(module, client, orders_url): def get_order(client, order_url): - ''' + """ Retrieve order data. - ''' + """ return client.get_request(order_url, parse_json_result=True, fail_on_error=True)[0] def main(): argument_spec = create_default_argspec() argument_spec.update_argspec( - retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']), + retrieve_orders=dict( + type="str", default="ignore", choices=["ignore", "url_list", "object_list"] + ), ) module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) @@ -280,28 +288,31 @@ def main(): remove_account_uri_if_not_exists=True, ) if created: - raise AssertionError('Unwanted account creation') + raise AssertionError("Unwanted account creation") result = { - 'changed': False, - 'exists': client.account_uri is not None, - 'account_uri': client.account_uri, + "changed": False, + "exists": client.account_uri is not None, + "account_uri": client.account_uri, } if client.account_uri is not None: # Make sure promised data is there - if 'contact' not in account_data: - account_data['contact'] = [] - account_data['public_account_key'] = client.account_key_data['jwk'] - result['account'] = account_data + if "contact" not in account_data: + account_data["contact"] = [] + account_data["public_account_key"] = client.account_key_data["jwk"] + result["account"] = account_data # Retrieve orders list - if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore': - orders = get_orders_list(module, client, account_data['orders']) - result['order_uris'] = orders - if module.params['retrieve_orders'] == 'object_list': - result['orders'] = [get_order(client, order) for order in orders] + if ( + account_data.get("orders") + and module.params["retrieve_orders"] != "ignore" + ): + orders = get_orders_list(module, client, account_data["orders"]) + result["order_uris"] = orders + if module.params["retrieve_orders"] == "object_list": + result["orders"] = [get_order(client, order) for order in orders] module.exit_json(**result) except ModuleFailException as e: e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_ari_info.py b/plugins/modules/acme_ari_info.py index fffed430..05f17ff2 100644 --- a/plugins/modules/acme_ari_info.py +++ b/plugins/modules/acme_ari_info.py @@ -112,16 +112,12 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(with_account=False) argument_spec.update_argspec( - certificate_path=dict(type='path'), - certificate_content=dict(type='str'), + certificate_path=dict(type="path"), + certificate_content=dict(type="str"), ) argument_spec.update( - required_one_of=( - ['certificate_path', 'certificate_content'], - ), - mutually_exclusive=( - ['certificate_path', 'certificate_content'], - ), + required_one_of=(["certificate_path", "certificate_content"],), + mutually_exclusive=(["certificate_path", "certificate_content"],), ) module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) @@ -129,10 +125,12 @@ def main(): try: client = ACMEClient(module, backend) if not client.directory.has_renewal_info_endpoint(): - module.fail_json(msg='The ACME endpoint does not support ACME Renewal Information retrieval') + module.fail_json( + msg="The ACME endpoint does not support ACME Renewal Information retrieval" + ) renewal_info = client.get_renewal_info( - cert_filename=module.params['certificate_path'], - cert_content=module.params['certificate_content'], + cert_filename=module.params["certificate_path"], + cert_content=module.params["certificate_content"], include_retry_after=True, ) module.exit_json(renewal_info=renewal_info) @@ -140,5 +138,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index b4e7d74f..47d0c2db 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -600,76 +600,94 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import ) -NO_CHALLENGE = 'no challenge' +NO_CHALLENGE = "no challenge" class ACMECertificateClient(object): - ''' + """ ACME client class. Uses an ACME account object and a CSR to start and validate ACME challenges and download the respective certificates. - ''' + """ def __init__(self, module, backend): self.module = module - self.version = module.params['acme_version'] - self.challenge = module.params['challenge'] + self.version = module.params["acme_version"] + self.challenge = module.params["challenge"] # We use None instead of a magic string for 'no challenge' if self.challenge == NO_CHALLENGE: self.challenge = None - self.csr = module.params['csr'] - self.csr_content = module.params['csr_content'] - self.dest = module.params.get('dest') - self.fullchain_dest = module.params.get('fullchain_dest') - self.chain_dest = module.params.get('chain_dest') + self.csr = module.params["csr"] + self.csr_content = module.params["csr_content"] + self.dest = module.params.get("dest") + self.fullchain_dest = module.params.get("fullchain_dest") + self.chain_dest = module.params.get("chain_dest") self.client = ACMEClient(module, backend) self.account = ACMEAccount(self.client) self.directory = self.client.directory - self.data = module.params['data'] + self.data = module.params["data"] self.authorizations = None self.cert_days = -1 self.order = None - self.order_uri = self.data.get('order_uri') if self.data else None + self.order_uri = self.data.get("order_uri") if self.data else None self.all_chains = None self.select_chain_matcher = [] - self.include_renewal_cert_id = module.params['include_renewal_cert_id'] - self.profile = module.params['profile'] - self.order_creation_error_strategy = module.params['order_creation_error_strategy'] - self.order_creation_max_retries = module.params['order_creation_max_retries'] + self.include_renewal_cert_id = module.params["include_renewal_cert_id"] + self.profile = module.params["profile"] + self.order_creation_error_strategy = module.params[ + "order_creation_error_strategy" + ] + self.order_creation_max_retries = module.params["order_creation_max_retries"] - if self.module.params['select_chain']: - for criterium_idx, criterium in enumerate(self.module.params['select_chain']): + if self.module.params["select_chain"]: + for criterium_idx, criterium in enumerate( + self.module.params["select_chain"] + ): try: self.select_chain_matcher.append( self.client.backend.create_chain_matcher( - Criterium(criterium, index=criterium_idx))) + Criterium(criterium, index=criterium_idx) + ) + ) except ValueError as exc: - self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc)) + self.module.warn( + "Error while parsing criterium: {error}. Ignoring criterium.".format( + error=exc + ) + ) if self.profile is not None: - meta_profiles = (self.directory.get('meta') or {}).get('profiles') or {} + meta_profiles = (self.directory.get("meta") or {}).get("profiles") or {} if not meta_profiles: - raise ModuleFailException(msg='The ACME CA does not support profiles.') + raise ModuleFailException(msg="The ACME CA does not support profiles.") if self.profile not in meta_profiles: - raise ModuleFailException(msg='The ACME CA does not support selected profile {0!r}.'.format(self.profile)) + raise ModuleFailException( + msg="The ACME CA does not support selected profile {0!r}.".format( + self.profile + ) + ) # Make sure account exists - modify_account = module.params['modify_account'] + modify_account = module.params["modify_account"] if modify_account or self.version > 1: contact = [] - if module.params['account_email']: - contact.append('mailto:' + module.params['account_email']) + if module.params["account_email"]: + contact.append("mailto:" + module.params["account_email"]) created, account_data = self.account.setup_account( contact, - agreement=module.params.get('agreement'), - terms_agreed=module.params.get('terms_agreed'), + agreement=module.params.get("agreement"), + terms_agreed=module.params.get("terms_agreed"), allow_creation=modify_account, ) if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException( + msg="Account does not exist or is deactivated." + ) updated = False if not created and account_data and modify_account: - updated, account_data = self.account.update_account(account_data, contact) + updated, account_data = self.account.update_account( + account_data, contact + ) self.changed = created or updated else: # This happens if modify_account is False and the ACME v1 @@ -683,13 +701,15 @@ class ACMECertificateClient(object): raise ModuleFailException("CSR %s not found" % (self.csr)) # Extract list of identifiers from CSR - self.identifiers = self.client.backend.get_ordered_csr_identifiers(csr_filename=self.csr, csr_content=self.csr_content) + self.identifiers = self.client.backend.get_ordered_csr_identifiers( + csr_filename=self.csr, csr_content=self.csr_content + ) def is_first_step(self): - ''' + """ Return True if this is the first execution of this module, i.e. if a sufficient data object from a first run has not been provided. - ''' + """ if self.data is None: return True if self.version == 1: @@ -701,32 +721,34 @@ class ACMECertificateClient(object): return self.order_uri is None def _get_cert_info_or_none(self): - if self.module.params.get('dest'): - filename = self.module.params['dest'] + if self.module.params.get("dest"): + filename = self.module.params["dest"] else: - filename = self.module.params['fullchain_dest'] + filename = self.module.params["fullchain_dest"] if not os.path.exists(filename): return None return self.client.backend.get_cert_information(cert_filename=filename) def start_challenges(self): - ''' + """ Create new authorizations for all identifiers of the CSR, respectively start a new order for ACME v2. - ''' + """ self.authorizations = {} if self.version == 1: for identifier_type, identifier in self.identifiers: - if identifier_type != 'dns': - raise ModuleFailException('ACME v1 only supports DNS identifiers!') + if identifier_type != "dns": + raise ModuleFailException("ACME v1 only supports DNS identifiers!") for identifier_type, identifier in self.identifiers: authz = Authorization.create(self.client, identifier_type, identifier) - self.authorizations[normalize_combined_identifier(authz.combined_identifier)] = authz + self.authorizations[ + normalize_combined_identifier(authz.combined_identifier) + ] = authz else: replaces_cert_id = None - if ( - self.include_renewal_cert_id == 'always' or - (self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint()) + if self.include_renewal_cert_id == "always" or ( + self.include_renewal_cert_id == "when_ari_supported" + and self.client.directory.has_renewal_info_endpoint() ): cert_info = self._get_cert_info_or_none() if cert_info is not None: @@ -750,39 +772,46 @@ class ACMECertificateClient(object): self.changed = True def get_challenges_data(self, first_step): - ''' + """ Get challenge details for the chosen challenge type. Return a tuple of generic challenge details, and specialized DNS challenge details. - ''' + """ # Get general challenge data data = {} for type_identifier, authz in self.authorizations.items(): identifier_type, identifier = split_identifier(type_identifier) # Skip valid authentications: their challenges are already valid # and do not need to be returned - if authz.status == 'valid': + if authz.status == "valid": continue # We drop the type from the key to preserve backwards compatibility data[authz.identifier] = authz.get_challenge_data(self.client) - if first_step and self.challenge is not None and self.challenge not in data[authz.identifier]: - raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format( - self.challenge, type_identifier)) + if ( + first_step + and self.challenge is not None + and self.challenge not in data[authz.identifier] + ): + raise ModuleFailException( + "Found no challenge of type '{0}' for identifier {1}!".format( + self.challenge, type_identifier + ) + ) # Get DNS challenge data data_dns = {} - if self.challenge == 'dns-01': + if self.challenge == "dns-01": for identifier, challenges in data.items(): if self.challenge in challenges: - values = data_dns.get(challenges[self.challenge]['record']) + values = data_dns.get(challenges[self.challenge]["record"]) if values is None: values = [] - data_dns[challenges[self.challenge]['record']] = values - values.append(challenges[self.challenge]['resource_value']) + data_dns[challenges[self.challenge]["record"]] = values + values.append(challenges[self.challenge]["resource_value"]) return data, data_dns def finish_challenges(self): - ''' + """ Verify challenges for all identifiers of the CSR. - ''' + """ self.authorizations = {} # Step 1: obtain challenge information @@ -791,7 +820,9 @@ class ACMECertificateClient(object): # will be returned instead. for identifier_type, identifier in self.identifiers: authz = Authorization.create(self.client, identifier_type, identifier) - self.authorizations[combine_identifier(identifier_type, identifier)] = authz + self.authorizations[combine_identifier(identifier_type, identifier)] = ( + authz + ) else: # For ACME v2, we obtain the order object by fetching the # order URI, and extract the information from there. @@ -802,12 +833,12 @@ class ACMECertificateClient(object): # Step 2: validate pending challenges authzs_to_wait_for = [] for type_identifier, authz in self.authorizations.items(): - if authz.status == 'pending': + if authz.status == "pending": if self.challenge is not None: authz.call_validate(self.client, self.challenge, wait=False) authzs_to_wait_for.append(authz) # If there is no challenge, we must check whether the authz is valid - elif authz.status != 'valid': + elif authz.status != "valid": authz.raise_error( 'Status is not "valid", even though no challenge should be necessary', module=self.client.module, @@ -823,7 +854,11 @@ class ACMECertificateClient(object): try: alt_cert = CertificateChain.download(self.client, alternate) except ModuleFailException as e: - self.module.warn('Error while downloading alternative certificate {0}: {1}'.format(alternate, e)) + self.module.warn( + "Error while downloading alternative certificate {0}: {1}".format( + alternate, e + ) + ) continue alternate_chains.append(alt_cert) return alternate_chains @@ -832,35 +867,52 @@ class ACMECertificateClient(object): for criterium_idx, matcher in enumerate(self.select_chain_matcher): for chain in chains: if matcher.match(chain): - self.module.debug('Found matching chain for criterium {0}'.format(criterium_idx)) + self.module.debug( + "Found matching chain for criterium {0}".format(criterium_idx) + ) return chain return None def get_certificate(self): - ''' + """ Request a new certificate and write it to the destination file. First verifies whether all authorizations are valid; if not, aborts with an error. - ''' + """ for identifier_type, identifier in self.identifiers: - authz = self.authorizations.get(normalize_combined_identifier(combine_identifier(identifier_type, identifier))) + authz = self.authorizations.get( + normalize_combined_identifier( + combine_identifier(identifier_type, identifier) + ) + ) if authz is None: - raise ModuleFailException('Found no authorization information for "{identifier}"!'.format( - identifier=combine_identifier(identifier_type, identifier))) - if authz.status != 'valid': - authz.raise_error('Status is "{status}" and not "valid"'.format(status=authz.status), module=self.module) + raise ModuleFailException( + 'Found no authorization information for "{identifier}"!'.format( + identifier=combine_identifier(identifier_type, identifier) + ) + ) + if authz.status != "valid": + authz.raise_error( + 'Status is "{status}" and not "valid"'.format(status=authz.status), + module=self.module, + ) if self.version == 1: - cert = retrieve_acme_v1_certificate(self.client, pem_to_der(self.csr, self.csr_content)) + cert = retrieve_acme_v1_certificate( + self.client, pem_to_der(self.csr, self.csr_content) + ) else: self.order.finalize(self.client, pem_to_der(self.csr, self.csr_content)) cert = CertificateChain.download(self.client, self.order.certificate_uri) - if self.module.params['retrieve_all_alternates'] or self.select_chain_matcher: + if ( + self.module.params["retrieve_all_alternates"] + or self.select_chain_matcher + ): # Retrieve alternate chains alternate_chains = self.download_alternate_chains(cert) # Prepare return value for all alternate chains - if self.module.params['retrieve_all_alternates']: + if self.module.params["retrieve_all_alternates"]: self.all_chains = [cert.to_json()] for alt_chain in alternate_chains: self.all_chains.append(alt_chain.to_json()) @@ -871,89 +923,122 @@ class ACMECertificateClient(object): if matching_chain: cert = matching_chain else: - self.module.debug('Found no matching alternative chain') + self.module.debug("Found no matching alternative chain") if cert.cert is not None: pem_cert = cert.cert chain = cert.chain - if self.dest and write_file(self.module, self.dest, pem_cert.encode('utf8')): + if self.dest and write_file( + self.module, self.dest, pem_cert.encode("utf8") + ): self.cert_days = self.client.backend.get_cert_days(self.dest) self.changed = True - if self.fullchain_dest and write_file(self.module, self.fullchain_dest, (pem_cert + "\n".join(chain)).encode('utf8')): + if self.fullchain_dest and write_file( + self.module, + self.fullchain_dest, + (pem_cert + "\n".join(chain)).encode("utf8"), + ): self.cert_days = self.client.backend.get_cert_days(self.fullchain_dest) self.changed = True - if self.chain_dest and write_file(self.module, self.chain_dest, ("\n".join(chain)).encode('utf8')): + if self.chain_dest and write_file( + self.module, self.chain_dest, ("\n".join(chain)).encode("utf8") + ): self.changed = True def deactivate_authzs(self): - ''' + """ Deactivates all valid authz's. Does not raise exceptions. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://tools.ietf.org/html/rfc8555#section-7.5.2 - ''' + """ for authz in self.authorizations.values(): try: authz.deactivate(self.client) except Exception: # ignore errors pass - if authz.status != 'deactivated': - self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url)) + if authz.status != "deactivated": + self.module.warn( + warning="Could not deactivate authz object {0}.".format(authz.url) + ) def main(): argument_spec = create_default_argspec(with_certificate=True) - argument_spec.argument_spec['csr']['aliases'] = ['src'] + argument_spec.argument_spec["csr"]["aliases"] = ["src"] argument_spec.update_argspec( - modify_account=dict(type='bool', default=True), - account_email=dict(type='str'), - agreement=dict(type='str'), - terms_agreed=dict(type='bool', default=False), - challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]), - data=dict(type='dict'), - dest=dict(type='path', aliases=['cert']), - fullchain_dest=dict(type='path', aliases=['fullchain']), - chain_dest=dict(type='path', aliases=['chain']), - remaining_days=dict(type='int', default=10), - deactivate_authzs=dict(type='bool', default=False), - force=dict(type='bool', default=False), - retrieve_all_alternates=dict(type='bool', default=False), - select_chain=dict(type='list', elements='dict', options=dict( - test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']), - issuer=dict(type='dict'), - subject=dict(type='dict'), - subject_key_identifier=dict(type='str'), - authority_key_identifier=dict(type='str'), - )), - include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'), - profile=dict(type='str'), - order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']), - order_creation_max_retries=dict(type='int', default=3), + modify_account=dict(type="bool", default=True), + account_email=dict(type="str"), + agreement=dict(type="str"), + terms_agreed=dict(type="bool", default=False), + challenge=dict( + type="str", + default="http-01", + choices=["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE], + ), + data=dict(type="dict"), + dest=dict(type="path", aliases=["cert"]), + fullchain_dest=dict(type="path", aliases=["fullchain"]), + chain_dest=dict(type="path", aliases=["chain"]), + remaining_days=dict(type="int", default=10), + deactivate_authzs=dict(type="bool", default=False), + force=dict(type="bool", default=False), + retrieve_all_alternates=dict(type="bool", default=False), + select_chain=dict( + type="list", + elements="dict", + options=dict( + test_certificates=dict( + type="str", default="all", choices=["first", "last", "all"] + ), + issuer=dict(type="dict"), + subject=dict(type="dict"), + subject_key_identifier=dict(type="str"), + authority_key_identifier=dict(type="str"), + ), + ), + include_renewal_cert_id=dict( + type="str", + choices=["never", "when_ari_supported", "always"], + default="never", + ), + profile=dict(type="str"), + order_creation_error_strategy=dict( + type="str", + default="auto", + choices=["auto", "always", "fail", "retry_without_replaces_cert_id"], + ), + order_creation_max_retries=dict(type="int", default=3), ) argument_spec.update( required_one_of=[ - ['dest', 'fullchain_dest'], + ["dest", "fullchain_dest"], ], ) module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, False) try: - if module.params.get('dest'): - cert_days = backend.get_cert_days(module.params['dest']) + if module.params.get("dest"): + cert_days = backend.get_cert_days(module.params["dest"]) else: - cert_days = backend.get_cert_days(module.params['fullchain_dest']) + cert_days = backend.get_cert_days(module.params["fullchain_dest"]) - if module.params['force'] or cert_days < module.params['remaining_days']: + if module.params["force"] or cert_days < module.params["remaining_days"]: # If checkmode is active, base the changed state solely on the status # of the certificate file as all other actions (accessing an account, checking # the authorization status...) would lead to potential changes of the current # state if module.check_mode: - module.exit_json(changed=True, authorizations={}, challenge_data={}, cert_days=cert_days) + module.exit_json( + changed=True, + authorizations={}, + challenge_data={}, + cert_days=cert_days, + ) else: client = ACMECertificateClient(module, backend) client.cert_days = cert_days @@ -968,9 +1053,9 @@ def main(): client.finish_challenges() client.get_certificate() if client.all_chains is not None: - other['all_chains'] = client.all_chains + other["all_chains"] = client.all_chains finally: - if module.params['deactivate_authzs']: + if module.params["deactivate_authzs"]: client.deactivate_authzs() data, data_dns = client.get_challenges_data(first_step=is_first_step) auths = dict() @@ -994,5 +1079,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_deactivate_authz.py b/plugins/modules/acme_certificate_deactivate_authz.py index f570a986..189e710a 100644 --- a/plugins/modules/acme_certificate_deactivate_authz.py +++ b/plugins/modules/acme_certificate_deactivate_authz.py @@ -73,11 +73,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor def main(): argument_spec = create_default_argspec() argument_spec.update_argspec( - order_uri=dict(type='str', required=True), + order_uri=dict(type="str", required=True), ) module = argument_spec.create_ansible_module(supports_check_mode=True) - if module.params['acme_version'] == 1: - module.fail_json('The module does not support acme_version=1') + if module.params["acme_version"] == 1: + module.fail_json("The module does not support acme_version=1") backend = create_backend(module, False) @@ -87,9 +87,9 @@ def main(): dummy, account_data = account.setup_account(allow_creation=False) if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException(msg="Account does not exist or is deactivated.") - order = Order.from_url(client, module.params['order_uri']) + order = Order.from_url(client, module.params["order_uri"]) order.load_authorizations(client) changed = False @@ -104,13 +104,15 @@ def main(): except Exception: # ignore errors pass - if authz.status != 'deactivated': - module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url)) + if authz.status != "deactivated": + module.warn( + warning="Could not deactivate authz object {0}.".format(authz.url) + ) module.exit_json(changed=changed) except ModuleFailException as e: e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_order_create.py b/plugins/modules/acme_certificate_order_create.py index e1341319..971f602b 100644 --- a/plugins/modules/acme_certificate_order_create.py +++ b/plugins/modules/acme_certificate_order_create.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = """ module: acme_certificate_order_create author: Felix Fontein (@felixfontein) version_added: 2.24.0 @@ -158,9 +158,9 @@ options: for at most the specified amount of times. type: int default: 3 -''' +""" -EXAMPLES = r''' +EXAMPLES = r""" --- ### Example with HTTP-01 challenge ### @@ -253,9 +253,9 @@ EXAMPLES = r''' cert_dest: /etc/httpd/ssl/sample.com.crt fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt -''' +""" -RETURN = ''' +RETURN = """ challenge_data: description: - For every identifier, provides the challenge information. @@ -377,7 +377,7 @@ account_uri: description: ACME account URI. returned: success type: str -''' +""" from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, @@ -394,37 +394,51 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(with_certificate=True) argument_spec.update_argspec( - deactivate_authzs=dict(type='bool', default=True), - replaces_cert_id=dict(type='str'), - profile=dict(type='str'), - order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']), - order_creation_max_retries=dict(type='int', default=3), + deactivate_authzs=dict(type="bool", default=True), + replaces_cert_id=dict(type="str"), + profile=dict(type="str"), + order_creation_error_strategy=dict( + type="str", + default="auto", + choices=["auto", "always", "fail", "retry_without_replaces_cert_id"], + ), + order_creation_max_retries=dict(type="int", default=3), ) module = argument_spec.create_ansible_module() - if module.params['acme_version'] == 1: - module.fail_json('The module does not support acme_version=1') + if module.params["acme_version"] == 1: + module.fail_json("The module does not support acme_version=1") backend = create_backend(module, False) try: client = ACMECertificateClient(module, backend) - profile = module.params['profile'] + profile = module.params["profile"] if profile is not None: - meta_profiles = (client.client.directory.get('meta') or {}).get('profiles') or {} + meta_profiles = (client.client.directory.get("meta") or {}).get( + "profiles" + ) or {} if not meta_profiles: - raise ModuleFailException(msg='The ACME CA does not support profiles. Please omit the "profile" option.') + raise ModuleFailException( + msg='The ACME CA does not support profiles. Please omit the "profile" option.' + ) if profile not in meta_profiles: - raise ModuleFailException(msg='The ACME CA does not support selected profile {0!r}.'.format(profile)) + raise ModuleFailException( + msg="The ACME CA does not support selected profile {0!r}.".format( + profile + ) + ) order = None done = False try: - order = client.create_order(replaces_cert_id=module.params['replaces_cert_id'], profile=profile) + order = client.create_order( + replaces_cert_id=module.params["replaces_cert_id"], profile=profile + ) client.check_that_authorizations_can_be_used(order) done = True finally: - if module.params['deactivate_authzs'] and order and not done: + if module.params["deactivate_authzs"] and order and not done: client.deactivate_authzs(order) data, data_dns = client.get_challenges_data(order) module.exit_json( @@ -438,5 +452,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_order_finalize.py b/plugins/modules/acme_certificate_order_finalize.py index e8664f33..0ee42944 100644 --- a/plugins/modules/acme_certificate_order_finalize.py +++ b/plugins/modules/acme_certificate_order_finalize.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = """ module: acme_certificate_order_finalize author: Felix Fontein (@felixfontein) version_added: 2.24.0 @@ -170,9 +170,9 @@ options: - "The identifier must be of the form V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)." type: str -''' +""" -EXAMPLES = r''' +EXAMPLES = r""" --- ### Example with HTTP-01 challenge ### @@ -265,9 +265,9 @@ EXAMPLES = r''' cert_dest: /etc/httpd/ssl/sample.com.crt fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt -''' +""" -RETURN = ''' +RETURN = """ account_uri: description: ACME account URI. returned: success @@ -323,7 +323,7 @@ selected_chain: as concatenated PEM certificates. type: str returned: always -''' +""" from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, @@ -340,29 +340,39 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(with_certificate=True) argument_spec.update_argspec( - order_uri=dict(type='str', required=True), - cert_dest=dict(type='path'), - fullchain_dest=dict(type='path'), - chain_dest=dict(type='path'), - deactivate_authzs=dict(type='str', default='always', choices=['never', 'always', 'on_error', 'on_success']), - retrieve_all_alternates=dict(type='bool', default=False), - select_chain=dict(type='list', elements='dict', options=dict( - test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']), - issuer=dict(type='dict'), - subject=dict(type='dict'), - subject_key_identifier=dict(type='str'), - authority_key_identifier=dict(type='str'), - )), + order_uri=dict(type="str", required=True), + cert_dest=dict(type="path"), + fullchain_dest=dict(type="path"), + chain_dest=dict(type="path"), + deactivate_authzs=dict( + type="str", + default="always", + choices=["never", "always", "on_error", "on_success"], + ), + retrieve_all_alternates=dict(type="bool", default=False), + select_chain=dict( + type="list", + elements="dict", + options=dict( + test_certificates=dict( + type="str", default="all", choices=["first", "last", "all"] + ), + issuer=dict(type="dict"), + subject=dict(type="dict"), + subject_key_identifier=dict(type="str"), + authority_key_identifier=dict(type="str"), + ), + ), ) module = argument_spec.create_ansible_module() - if module.params['acme_version'] == 1: - module.fail_json('The module does not support acme_version=1') + if module.params["acme_version"] == 1: + module.fail_json("The module does not support acme_version=1") backend = create_backend(module, False) try: client = ACMECertificateClient(module, backend) - select_chain_matcher = client.parse_select_chain(module.params['select_chain']) + select_chain_matcher = client.parse_select_chain(module.params["select_chain"]) other = dict() done = False order = None @@ -370,9 +380,12 @@ def main(): # Step 1: load order order = client.load_order() - download_all_chains = len(select_chain_matcher) > 0 or module.params['retrieve_all_alternates'] + download_all_chains = ( + len(select_chain_matcher) > 0 + or module.params["retrieve_all_alternates"] + ) changed = False - if order.status == 'valid': + if order.status == "valid": # Step 2 and 3: download certificate(s) and chain(s) cert, alternate_chains = client.download_certificate( order, @@ -395,34 +408,36 @@ def main(): # Step 4: pick chain, write certificates, and provide return values if alternate_chains is not None: # Prepare return value for all alternate chains - if module.params['retrieve_all_alternates']: + if module.params["retrieve_all_alternates"]: all_chains = [cert.to_json()] for alt_chain in alternate_chains: all_chains.append(alt_chain.to_json()) - other['all_chains'] = all_chains + other["all_chains"] = all_chains # Try to select alternate chain depending on criteria if select_chain_matcher: - matching_chain = client.find_matching_chain([cert] + alternate_chains, select_chain_matcher) + matching_chain = client.find_matching_chain( + [cert] + alternate_chains, select_chain_matcher + ) if matching_chain: cert = matching_chain else: - module.debug('Found no matching alternative chain') + module.debug("Found no matching alternative chain") if client.write_cert_chain( cert, - cert_dest=module.params['cert_dest'], - fullchain_dest=module.params['fullchain_dest'], - chain_dest=module.params['chain_dest'], + cert_dest=module.params["cert_dest"], + fullchain_dest=module.params["fullchain_dest"], + chain_dest=module.params["chain_dest"], ): changed = True done = True finally: if ( - module.params['deactivate_authzs'] == 'always' or - (module.params['deactivate_authzs'] == 'on_success' and done) or - (module.params['deactivate_authzs'] == 'on_error' and not done) + module.params["deactivate_authzs"] == "always" + or (module.params["deactivate_authzs"] == "on_success" and done) + or (module.params["deactivate_authzs"] == "on_error" and not done) ): if order: client.deactivate_authzs(order) @@ -436,5 +451,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_order_info.py b/plugins/modules/acme_certificate_order_info.py index 9dfda958..8786b0ba 100644 --- a/plugins/modules/acme_certificate_order_info.py +++ b/plugins/modules/acme_certificate_order_info.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = """ module: acme_certificate_order_info author: Felix Fontein (@felixfontein) version_added: 2.24.0 @@ -57,9 +57,9 @@ options: - The order URI provided by RV(community.crypto.acme_certificate_order_create#module:order_uri). type: str required: true -''' +""" -EXAMPLES = r''' +EXAMPLES = r""" --- - name: Create a challenge for sample.com using a account key from a variable community.crypto.acme_certificate_order_create: @@ -76,9 +76,9 @@ EXAMPLES = r''' - name: Show information ansible.builtin.debug: var: order_info -''' +""" -RETURN = ''' +RETURN = """ account_uri: description: ACME account URI. returned: success @@ -360,7 +360,7 @@ authorizations_by_status: type: list elements: str returned: always -''' +""" from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, @@ -377,11 +377,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(with_certificate=False) argument_spec.update_argspec( - order_uri=dict(type='str', required=True), + order_uri=dict(type="str", required=True), ) module = argument_spec.create_ansible_module(supports_check_mode=True) - if module.params['acme_version'] == 1: - module.fail_json('The module does not support acme_version=1') + if module.params["acme_version"] == 1: + module.fail_json("The module does not support acme_version=1") backend = create_backend(module, False) @@ -390,12 +390,12 @@ def main(): order = client.load_order() authorizations_by_identifier = dict() authorizations_by_status = { - 'pending': [], - 'invalid': [], - 'valid': [], - 'revoked': [], - 'deactivated': [], - 'expired': [], + "pending": [], + "invalid": [], + "valid": [], + "revoked": [], + "deactivated": [], + "expired": [], } for identifier, authz in order.authorizations.items(): authorizations_by_identifier[identifier] = authz.to_json() @@ -412,5 +412,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_order_validate.py b/plugins/modules/acme_certificate_order_validate.py index 13c2dc7e..9ac0c184 100644 --- a/plugins/modules/acme_certificate_order_validate.py +++ b/plugins/modules/acme_certificate_order_validate.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = """ module: acme_certificate_order_validate author: Felix Fontein (@felixfontein) version_added: 2.24.0 @@ -95,9 +95,9 @@ options: concern." type: bool default: true -''' +""" -EXAMPLES = r''' +EXAMPLES = r""" --- ### Example with HTTP-01 challenge ### @@ -190,9 +190,9 @@ EXAMPLES = r''' cert_dest: /etc/httpd/ssl/sample.com.crt fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt -''' +""" -RETURN = ''' +RETURN = """ account_uri: description: ACME account URI. returned: success @@ -235,7 +235,7 @@ validating_challenges: - The URL of the challenge object. type: str returned: always -''' +""" from ansible_collections.community.crypto.plugins.module_utils.acme.acme import ( create_backend, @@ -252,13 +252,13 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(with_certificate=False) argument_spec.update_argspec( - order_uri=dict(type='str', required=True), - challenge=dict(type='str', choices=['http-01', 'dns-01', 'tls-alpn-01']), - deactivate_authzs=dict(type='bool', default=True), + order_uri=dict(type="str", required=True), + challenge=dict(type="str", choices=["http-01", "dns-01", "tls-alpn-01"]), + deactivate_authzs=dict(type="bool", default=True), ) module = argument_spec.create_ansible_module() - if module.params['acme_version'] == 1: - module.fail_json('The module does not support acme_version=1') + if module.params["acme_version"] == 1: + module.fail_json("The module does not support acme_version=1") backend = create_backend(module, False) @@ -277,34 +277,43 @@ def main(): # Step 3: figure out challenges to use challenges = {} for authz in pending_authzs: - challenges[authz.combined_identifier] = module.params['challenge'] + challenges[authz.combined_identifier] = module.params["challenge"] missing_challenge_authzs = [k for k, v in challenges.items() if v is None] if missing_challenge_authzs: raise ModuleFailException( - 'The challenge parameter must be supplied if there are pending authorizations.' - ' The following authorizations are pending: {missing_challenge_authzs}'.format( - missing_challenge_authzs=', '.join(sorted(missing_challenge_authzs)), + "The challenge parameter must be supplied if there are pending authorizations." + " The following authorizations are pending: {missing_challenge_authzs}".format( + missing_challenge_authzs=", ".join( + sorted(missing_challenge_authzs) + ), ) ) bad_challenge_authzs = [ - authz.combined_identifier for authz in pending_authzs + authz.combined_identifier + for authz in pending_authzs if authz.find_challenge(challenges[authz.combined_identifier]) is None ] if bad_challenge_authzs: raise ModuleFailException( - 'The following authorizations do not support the selected challenges: {authz_challenges_pairs}'.format( - authz_challenges_pairs=', '.join(sorted( - '{authz} with {challenge}'.format(authz=authz, challenge=challenges[authz]) - for authz in bad_challenge_authzs - )), + "The following authorizations do not support the selected challenges: {authz_challenges_pairs}".format( + authz_challenges_pairs=", ".join( + sorted( + "{authz} with {challenge}".format( + authz=authz, challenge=challenges[authz] + ) + for authz in bad_challenge_authzs + ) + ), ) ) really_pending_authzs = [ - authz for authz in pending_authzs - if authz.find_challenge(challenges[authz.combined_identifier]).status == 'pending' + authz + for authz in pending_authzs + if authz.find_challenge(challenges[authz.combined_identifier]).status + == "pending" ] # Step 4: validate pending authorizations @@ -316,7 +325,7 @@ def main(): done = True finally: - if order and module.params['deactivate_authzs'] and not done: + if order and module.params["deactivate_authzs"] and not done: client.deactivate_authzs(order) module.exit_json( changed=len(authzs_with_challenges_to_wait_for) > 0, @@ -336,5 +345,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_renewal_info.py b/plugins/modules/acme_certificate_renewal_info.py index d1ab8206..e414d226 100644 --- a/plugins/modules/acme_certificate_renewal_info.py +++ b/plugins/modules/acme_certificate_renewal_info.py @@ -183,110 +183,144 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import def main(): argument_spec = create_default_argspec(with_account=False) argument_spec.update_argspec( - certificate_path=dict(type='path'), - certificate_content=dict(type='str'), - use_ari=dict(type='bool', default=True), - ari_algorithm=dict(type='str', choices=['standard', 'start'], default='standard'), - remaining_days=dict(type='int'), - remaining_percentage=dict(type='float'), - now=dict(type='str'), - treat_parsing_error_as_non_existing=dict(type='bool', default=False), + certificate_path=dict(type="path"), + certificate_content=dict(type="str"), + use_ari=dict(type="bool", default=True), + ari_algorithm=dict( + type="str", choices=["standard", "start"], default="standard" + ), + remaining_days=dict(type="int"), + remaining_percentage=dict(type="float"), + now=dict(type="str"), + treat_parsing_error_as_non_existing=dict(type="bool", default=False), ) argument_spec.update( - mutually_exclusive=( - ['certificate_path', 'certificate_content'], - ), + mutually_exclusive=(["certificate_path", "certificate_content"],), ) module = argument_spec.create_ansible_module(supports_check_mode=True) backend = create_backend(module, True) result = dict( changed=False, - msg='The certificate is still valid and no condition was reached', + msg="The certificate is still valid and no condition was reached", exists=False, parsable=False, supports_ari=False, ) def complete(should_renew, **kwargs): - result['should_renew'] = should_renew + result["should_renew"] = should_renew result.update(kwargs) module.exit_json(**result) - if not module.params['certificate_path'] and not module.params['certificate_content']: - complete(True, msg='No certificate was specified') + if ( + not module.params["certificate_path"] + and not module.params["certificate_content"] + ): + complete(True, msg="No certificate was specified") - if module.params['certificate_path'] is not None: - if not os.path.exists(module.params['certificate_path']): - complete(True, msg='The certificate file does not exist') - if module.params['treat_parsing_error_as_non_existing']: + if module.params["certificate_path"] is not None: + if not os.path.exists(module.params["certificate_path"]): + complete(True, msg="The certificate file does not exist") + if module.params["treat_parsing_error_as_non_existing"]: try: - read_file(module.params['certificate_path']) + read_file(module.params["certificate_path"]) except ModuleFailException as e: e.do_fail(module) - result['exists'] = True + result["exists"] = True try: cert_info = backend.get_cert_information( - cert_filename=module.params['certificate_path'], - cert_content=module.params['certificate_content'], + cert_filename=module.params["certificate_path"], + cert_content=module.params["certificate_content"], ) except ModuleFailException as e: - if module.params['treat_parsing_error_as_non_existing']: - complete(True, msg='Certificate cannot be parsed: {0}'.format(e.msg)) + if module.params["treat_parsing_error_as_non_existing"]: + complete(True, msg="Certificate cannot be parsed: {0}".format(e.msg)) e.do_fail(module) - result['parsable'] = True + result["parsable"] = True try: - cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True) + cert_id = compute_cert_id( + backend, cert_info=cert_info, none_if_required_information_is_missing=True + ) if cert_id is not None: - result['cert_id'] = cert_id + result["cert_id"] = cert_id - if module.params['now']: - now = backend.parse_module_parameter(module.params['now'], 'now') + if module.params["now"]: + now = backend.parse_module_parameter(module.params["now"], "now") else: now = backend.get_now() if now >= cert_info.not_valid_after: - complete(True, msg='The certificate has already expired') + complete(True, msg="The certificate has already expired") client = ACMEClient(module, backend) - if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint(): + if ( + cert_id is not None + and module.params["use_ari"] + and client.directory.has_renewal_info_endpoint() + ): renewal_info = client.get_renewal_info(cert_id=cert_id) - window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start']) - window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end']) - msg_append = '' - if 'explanationURL' in renewal_info: - msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL']) - result['supports_ari'] = True + window_start = backend.parse_acme_timestamp( + renewal_info["suggestedWindow"]["start"] + ) + window_end = backend.parse_acme_timestamp( + renewal_info["suggestedWindow"]["end"] + ) + msg_append = "" + if "explanationURL" in renewal_info: + msg_append = ". Information on renewal interval: {0}".format( + renewal_info["explanationURL"] + ) + result["supports_ari"] = True if now > window_end: - complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append)) - if module.params['ari_algorithm'] == 'start': + complete( + True, + msg="The suggested renewal interval provided by ARI is in the past{0}".format( + msg_append + ), + ) + if module.params["ari_algorithm"] == "start": if now > window_start: - complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append)) + complete( + True, + msg="The suggested renewal interval provided by ARI has begun{0}".format( + msg_append + ), + ) else: - random_time = backend.interpolate_timestamp(window_start, window_end, random.random()) + random_time = backend.interpolate_timestamp( + window_start, window_end, random.random() + ) if now > random_time: complete( True, - msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format( + msg="The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}".format( random_time, msg_append, ), ) - if module.params['remaining_days'] is not None: + if module.params["remaining_days"] is not None: remaining_days = (cert_info.not_valid_after - now).days - if remaining_days < module.params['remaining_days']: - complete(True, msg='The certificate expires in {0} days'.format(remaining_days)) + if remaining_days < module.params["remaining_days"]: + complete( + True, + msg="The certificate expires in {0} days".format(remaining_days), + ) - if module.params['remaining_percentage'] is not None: - timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage']) + if module.params["remaining_percentage"] is not None: + timestamp = backend.interpolate_timestamp( + cert_info.not_valid_before, + cert_info.not_valid_after, + 1 - module.params["remaining_percentage"], + ) if timestamp < now: complete( True, msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format( - module.params['remaining_percentage'] * 100, + module.params["remaining_percentage"] * 100, timestamp, ), ) @@ -296,5 +330,5 @@ def main(): e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_certificate_revoke.py b/plugins/modules/acme_certificate_revoke.py index e76f62b5..b6510f8e 100644 --- a/plugins/modules/acme_certificate_revoke.py +++ b/plugins/modules/acme_certificate_revoke.py @@ -137,18 +137,28 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import def main(): argument_spec = create_default_argspec(require_account_key=False) argument_spec.update_argspec( - private_key_src=dict(type='path'), - private_key_content=dict(type='str', no_log=True), - private_key_passphrase=dict(type='str', no_log=True), - certificate=dict(type='path', required=True), - revoke_reason=dict(type='int'), + private_key_src=dict(type="path"), + private_key_content=dict(type="str", no_log=True), + private_key_passphrase=dict(type="str", no_log=True), + certificate=dict(type="path", required=True), + revoke_reason=dict(type="int"), ) argument_spec.update( required_one_of=( - ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], + [ + "account_key_src", + "account_key_content", + "private_key_src", + "private_key_content", + ], ), mutually_exclusive=( - ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], + [ + "account_key_src", + "account_key_content", + "private_key_src", + "private_key_content", + ], ), ) module = argument_spec.create_ansible_module() @@ -158,70 +168,86 @@ def main(): client = ACMEClient(module, backend) account = ACMEAccount(client) # Load certificate - certificate = pem_to_der(module.params.get('certificate')) + certificate = pem_to_der(module.params.get("certificate")) certificate = nopad_b64(certificate) # Construct payload - payload = { - 'certificate': certificate - } - if module.params.get('revoke_reason') is not None: - payload['reason'] = module.params.get('revoke_reason') + payload = {"certificate": certificate} + if module.params.get("revoke_reason") is not None: + payload["reason"] = module.params.get("revoke_reason") # Determine endpoint - if module.params.get('acme_version') == 1: - endpoint = client.directory['revoke-cert'] - payload['resource'] = 'revoke-cert' + if module.params.get("acme_version") == 1: + endpoint = client.directory["revoke-cert"] + payload["resource"] = "revoke-cert" else: - endpoint = client.directory['revokeCert'] + endpoint = client.directory["revokeCert"] # Get hold of private key (if available) and make sure it comes from disk - private_key = module.params.get('private_key_src') - private_key_content = module.params.get('private_key_content') + private_key = module.params.get("private_key_src") + private_key_content = module.params.get("private_key_content") # Revoke certificate if private_key or private_key_content: - passphrase = module.params['private_key_passphrase'] + passphrase = module.params["private_key_passphrase"] # Step 1: load and parse private key try: - private_key_data = client.parse_key(private_key, private_key_content, passphrase=passphrase) + private_key_data = client.parse_key( + private_key, private_key_content, passphrase=passphrase + ) except KeyParsingError as e: - raise ModuleFailException("Error while parsing private key: {msg}".format(msg=e.msg)) + raise ModuleFailException( + "Error while parsing private key: {msg}".format(msg=e.msg) + ) # Step 2: sign revokation request with private key jws_header = { - "alg": private_key_data['alg'], - "jwk": private_key_data['jwk'], + "alg": private_key_data["alg"], + "jwk": private_key_data["jwk"], } result, info = client.send_signed_request( - endpoint, payload, key_data=private_key_data, jws_header=jws_header, fail_on_error=False) + endpoint, + payload, + key_data=private_key_data, + jws_header=jws_header, + fail_on_error=False, + ) else: # Step 1: get hold of account URI created, account_data = account.setup_account(allow_creation=False) if created: - raise AssertionError('Unwanted account creation') + raise AssertionError("Unwanted account creation") if account_data is None: - raise ModuleFailException(msg='Account does not exist or is deactivated.') + raise ModuleFailException( + msg="Account does not exist or is deactivated." + ) # Step 2: sign revokation request with account key - result, info = client.send_signed_request(endpoint, payload, fail_on_error=False) - if info['status'] != 200: + result, info = client.send_signed_request( + endpoint, payload, fail_on_error=False + ) + if info["status"] != 200: already_revoked = False # Standardized error from draft 14 on (https://tools.ietf.org/html/rfc8555#section-7.6) - if result.get('type') == 'urn:ietf:params:acme:error:alreadyRevoked': + if result.get("type") == "urn:ietf:params:acme:error:alreadyRevoked": already_revoked = True else: # Hack for Boulder errors - if module.params.get('acme_version') == 1: - error_type = 'urn:acme:error:malformed' + if module.params.get("acme_version") == 1: + error_type = "urn:acme:error:malformed" else: - error_type = 'urn:ietf:params:acme:error:malformed' - if result.get('type') == error_type and result.get('detail') == 'Certificate already revoked': + error_type = "urn:ietf:params:acme:error:malformed" + if ( + result.get("type") == error_type + and result.get("detail") == "Certificate already revoked" + ): # Fallback: boulder returns this in case the certificate was already revoked. already_revoked = True # If we know the certificate was already revoked, we do not fail, # but successfully terminate while indicating no change if already_revoked: module.exit_json(changed=False) - raise ACMEProtocolException(module, 'Failed to revoke certificate', info=info, content_json=result) + raise ACMEProtocolException( + module, "Failed to revoke certificate", info=info, content_json=result + ) module.exit_json(changed=True) except ModuleFailException as e: e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_challenge_cert_helper.py b/plugins/modules/acme_challenge_cert_helper.py index fb748bf7..612a2e74 100644 --- a/plugins/modules/acme_challenge_cert_helper.py +++ b/plugins/modules/acme_challenge_cert_helper.py @@ -190,7 +190,8 @@ try: import cryptography.hazmat.primitives.serialization import cryptography.x509 import cryptography.x509.oid - HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3')) + + HAS_CRYPTOGRAPHY = LooseVersion(cryptography.__version__) >= LooseVersion("1.3") _cryptography_backend = cryptography.hazmat.backends.default_backend() except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -199,121 +200,137 @@ except ImportError: # Convert byte string to ASN1 encoded octet string if sys.version_info[0] >= 3: + def encode_octet_string(octet_string): if len(octet_string) >= 128: - raise ModuleFailException('Cannot handle octet strings with more than 128 bytes') + raise ModuleFailException( + "Cannot handle octet strings with more than 128 bytes" + ) return bytes([0x4, len(octet_string)]) + octet_string + else: + def encode_octet_string(octet_string): if len(octet_string) >= 128: - raise ModuleFailException('Cannot handle octet strings with more than 128 bytes') - return b'\x04' + chr(len(octet_string)) + octet_string + raise ModuleFailException( + "Cannot handle octet strings with more than 128 bytes" + ) + return b"\x04" + chr(len(octet_string)) + octet_string def main(): module = AnsibleModule( argument_spec=dict( - challenge=dict(type='str', required=True, choices=['tls-alpn-01']), - challenge_data=dict(type='dict', required=True), - private_key_src=dict(type='path'), - private_key_content=dict(type='str', no_log=True), - private_key_passphrase=dict(type='str', no_log=True), - ), - required_one_of=( - ['private_key_src', 'private_key_content'], - ), - mutually_exclusive=( - ['private_key_src', 'private_key_content'], + challenge=dict(type="str", required=True, choices=["tls-alpn-01"]), + challenge_data=dict(type="dict", required=True), + private_key_src=dict(type="path"), + private_key_content=dict(type="str", no_log=True), + private_key_passphrase=dict(type="str", no_log=True), ), + required_one_of=(["private_key_src", "private_key_content"],), + mutually_exclusive=(["private_key_src", "private_key_content"],), ) if not HAS_CRYPTOGRAPHY: # Some callbacks die when exception is provided with value None if CRYPTOGRAPHY_IMP_ERR: - module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR) - module.fail_json(msg=missing_required_lib('cryptography >= 1.3')) + module.fail_json( + msg=missing_required_lib("cryptography >= 1.3"), + exception=CRYPTOGRAPHY_IMP_ERR, + ) + module.fail_json(msg=missing_required_lib("cryptography >= 1.3")) try: # Get parameters - challenge = module.params['challenge'] - challenge_data = module.params['challenge_data'] + challenge = module.params["challenge"] + challenge_data = module.params["challenge_data"] # Get hold of private key - private_key_content = module.params.get('private_key_content') - private_key_passphrase = module.params.get('private_key_passphrase') + private_key_content = module.params.get("private_key_content") + private_key_passphrase = module.params.get("private_key_passphrase") if private_key_content is None: - private_key_content = read_file(module.params['private_key_src']) + private_key_content = read_file(module.params["private_key_src"]) else: private_key_content = to_bytes(private_key_content) try: - private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key( - private_key_content, - password=to_bytes(private_key_passphrase) if private_key_passphrase is not None else None, - backend=_cryptography_backend) + private_key = ( + cryptography.hazmat.primitives.serialization.load_pem_private_key( + private_key_content, + password=( + to_bytes(private_key_passphrase) + if private_key_passphrase is not None + else None + ), + backend=_cryptography_backend, + ) + ) except Exception as e: - raise ModuleFailException('Error while loading private key: {0}'.format(e)) + raise ModuleFailException("Error while loading private key: {0}".format(e)) # Some common attributes - domain = to_text(challenge_data['resource']) - identifier_type, identifier = to_text(challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split(':', 1) + domain = to_text(challenge_data["resource"]) + identifier_type, identifier = to_text( + challenge_data.get("resource_original", "dns:" + challenge_data["resource"]) + ).split(":", 1) subject = issuer = cryptography.x509.Name([]) now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE) not_valid_before = now not_valid_after = now + datetime.timedelta(days=10) - if identifier_type == 'dns': + if identifier_type == "dns": san = cryptography.x509.DNSName(identifier) - elif identifier_type == 'ip': + elif identifier_type == "ip": san = cryptography.x509.IPAddress(ipaddress.ip_address(identifier)) else: - raise ModuleFailException('Unsupported identifier type "{0}"'.format(identifier_type)) + raise ModuleFailException( + 'Unsupported identifier type "{0}"'.format(identifier_type) + ) # Generate regular self-signed certificate - cert_builder = cryptography.x509.CertificateBuilder().subject_name( - subject - ).issuer_name( - issuer - ).public_key( - private_key.public_key() - ).serial_number( - cryptography.x509.random_serial_number() - ).add_extension( - cryptography.x509.SubjectAlternativeName([san]), - critical=False, + cert_builder = ( + cryptography.x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(cryptography.x509.random_serial_number()) + .add_extension( + cryptography.x509.SubjectAlternativeName([san]), + critical=False, + ) ) cert_builder = set_not_valid_before(cert_builder, not_valid_before) cert_builder = set_not_valid_after(cert_builder, not_valid_after) regular_certificate = cert_builder.sign( private_key, cryptography.hazmat.primitives.hashes.SHA256(), - _cryptography_backend + _cryptography_backend, ) # Process challenge - if challenge == 'tls-alpn-01': - value = base64.b64decode(challenge_data['resource_value']) - cert_builder = cryptography.x509.CertificateBuilder().subject_name( - subject - ).issuer_name( - issuer - ).public_key( - private_key.public_key() - ).serial_number( - cryptography.x509.random_serial_number() - ).add_extension( - cryptography.x509.SubjectAlternativeName([san]), - critical=False, - ).add_extension( - cryptography.x509.UnrecognizedExtension( - cryptography.x509.ObjectIdentifier("1.3.6.1.5.5.7.1.31"), - encode_octet_string(value), - ), - critical=True, + if challenge == "tls-alpn-01": + value = base64.b64decode(challenge_data["resource_value"]) + cert_builder = ( + cryptography.x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(cryptography.x509.random_serial_number()) + .add_extension( + cryptography.x509.SubjectAlternativeName([san]), + critical=False, + ) + .add_extension( + cryptography.x509.UnrecognizedExtension( + cryptography.x509.ObjectIdentifier("1.3.6.1.5.5.7.1.31"), + encode_octet_string(value), + ), + critical=True, + ) ) cert_builder = set_not_valid_before(cert_builder, not_valid_before) cert_builder = set_not_valid_after(cert_builder, not_valid_after) challenge_certificate = cert_builder.sign( private_key, cryptography.hazmat.primitives.hashes.SHA256(), - _cryptography_backend + _cryptography_backend, ) module.exit_json( @@ -321,12 +338,16 @@ def main(): domain=domain, identifier_type=identifier_type, identifier=identifier, - challenge_certificate=challenge_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM), - regular_certificate=regular_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM) + challenge_certificate=challenge_certificate.public_bytes( + cryptography.hazmat.primitives.serialization.Encoding.PEM + ), + regular_certificate=regular_certificate.public_bytes( + cryptography.hazmat.primitives.serialization.Encoding.PEM + ), ) except ModuleFailException as e: e.do_fail(module) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/acme_inspect.py b/plugins/modules/acme_inspect.py index dbb3e00a..b84abaa5 100644 --- a/plugins/modules/acme_inspect.py +++ b/plugins/modules/acme_inspect.py @@ -252,17 +252,19 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor def main(): argument_spec = create_default_argspec(require_account_key=False) argument_spec.update_argspec( - url=dict(type='str'), - method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'), - content=dict(type='str'), - fail_on_acme_error=dict(type='bool', default=True), + url=dict(type="str"), + method=dict( + type="str", choices=["get", "post", "directory-only"], default="get" + ), + content=dict(type="str"), + fail_on_acme_error=dict(type="bool", default=True), ) argument_spec.update( required_if=( - ['method', 'get', ['url']], - ['method', 'post', ['url', 'content']], - ['method', 'get', ['account_key_src', 'account_key_content'], True], - ['method', 'post', ['account_key_src', 'account_key_content'], True], + ["method", "get", ["url"]], + ["method", "post", ["url", "content"]], + ["method", "get", ["account_key_src", "account_key_content"], True], + ["method", "post", ["account_key_src", "account_key_content"], True], ), ) module = argument_spec.create_ansible_module() @@ -273,31 +275,40 @@ def main(): try: # Get hold of ACMEClient and ACMEAccount objects (includes directory) client = ACMEClient(module, backend) - method = module.params['method'] - result['directory'] = client.directory.directory + method = module.params["method"] + result["directory"] = client.directory.directory # Do we have to do more requests? - if method != 'directory-only': - url = module.params['url'] - fail_on_acme_error = module.params['fail_on_acme_error'] + if method != "directory-only": + url = module.params["url"] + fail_on_acme_error = module.params["fail_on_acme_error"] # Do request - if method == 'get': - data, info = client.get_request(url, parse_json_result=False, fail_on_error=False) - elif method == 'post': + if method == "get": + data, info = client.get_request( + url, parse_json_result=False, fail_on_error=False + ) + elif method == "post": changed = True # only POSTs can change data, info = client.send_signed_request( - url, to_bytes(module.params['content']), parse_json_result=False, encode_payload=False, fail_on_error=False) + url, + to_bytes(module.params["content"]), + parse_json_result=False, + encode_payload=False, + fail_on_error=False, + ) # Update results - result.update(dict( - headers=info, - output_text=to_native(data), - )) + result.update( + dict( + headers=info, + output_text=to_native(data), + ) + ) # See if we can parse the result as JSON try: - result['output_json'] = module.from_json(to_text(data)) + result["output_json"] = module.from_json(to_text(data)) except Exception: pass # Fail if error was returned - if fail_on_acme_error and info['status'] >= 400: + if fail_on_acme_error and info["status"] >= 400: raise ACMEProtocolException(module, info=info, content=data) # Done! module.exit_json(changed=changed, **result) @@ -305,5 +316,5 @@ def main(): e.do_fail(module, **result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/certificate_complete_chain.py b/plugins/modules/certificate_complete_chain.py index ba6524d5..abb946ad 100644 --- a/plugins/modules/certificate_complete_chain.py +++ b/plugins/modules/certificate_complete_chain.py @@ -156,7 +156,8 @@ try: import cryptography.hazmat.primitives.serialization import cryptography.x509 import cryptography.x509.oid - HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5')) + + HAS_CRYPTOGRAPHY = LooseVersion(cryptography.__version__) >= LooseVersion("1.5") _cryptography_backend = cryptography.hazmat.backends.default_backend() except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -164,44 +165,55 @@ except ImportError: class Certificate(object): - ''' + """ Stores PEM with parsed certificate. - ''' + """ + def __init__(self, pem, cert): - if not (pem.endswith('\n') or pem.endswith('\r')): - pem = pem + '\n' + if not (pem.endswith("\n") or pem.endswith("\r")): + pem = pem + "\n" self.pem = pem self.cert = cert def is_parent(module, cert, potential_parent): - ''' + """ Tests whether the given certificate has been issued by the potential parent certificate. - ''' + """ # Check issuer if cert.cert.issuer != potential_parent.cert.subject: return False # Check signature public_key = potential_parent.cert.public_key() try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + if isinstance( + public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey + ): public_key.verify( cert.cert.signature, cert.cert.tbs_certificate_bytes, cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15(), - cert.cert.signature_hash_algorithm + cert.cert.signature_hash_algorithm, ) - elif isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): + elif isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, + ): public_key.verify( cert.cert.signature, cert.cert.tbs_certificate_bytes, - cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cert.cert.signature_hash_algorithm), + cryptography.hazmat.primitives.asymmetric.ec.ECDSA( + cert.cert.signature_hash_algorithm + ), ) elif CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance( - public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey): + public_key, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey, + ): public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes) elif CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance( - public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey): + public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey + ): public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes) else: # Unknown public key type @@ -211,24 +223,30 @@ def is_parent(module, cert, potential_parent): except cryptography.exceptions.InvalidSignature: return False except cryptography.exceptions.UnsupportedAlgorithm: - module.warn('Unsupported algorithm "{0}"'.format(cert.cert.signature_hash_algorithm)) + module.warn( + 'Unsupported algorithm "{0}"'.format(cert.cert.signature_hash_algorithm) + ) return False except Exception as e: - module.fail_json(msg='Unknown error on signature validation: {0}'.format(e)) + module.fail_json(msg="Unknown error on signature validation: {0}".format(e)) def parse_PEM_list(module, text, source, fail_on_error=True): - ''' + """ Parse concatenated PEM certificates. Return list of ``Certificate`` objects. - ''' + """ result = [] for cert_pem in split_pem_list(text): # Try to load PEM certificate try: - cert = cryptography.x509.load_pem_x509_certificate(to_bytes(cert_pem), _cryptography_backend) + cert = cryptography.x509.load_pem_x509_certificate( + to_bytes(cert_pem), _cryptography_backend + ) result.append(Certificate(cert_pem, cert)) except Exception as e: - msg = 'Cannot parse certificate #{0} from {1}: {2}'.format(len(result) + 1, source, e) + msg = "Cannot parse certificate #{0} from {1}: {2}".format( + len(result) + 1, source, e + ) if fail_on_error: module.fail_json(msg=msg) else: @@ -237,14 +255,19 @@ def parse_PEM_list(module, text, source, fail_on_error=True): def load_PEM_list(module, path, fail_on_error=True): - ''' + """ Load concatenated PEM certificates from file. Return list of ``Certificate`` objects. - ''' + """ try: with open(path, "rb") as f: - return parse_PEM_list(module, f.read().decode('utf-8'), source=path, fail_on_error=fail_on_error) + return parse_PEM_list( + module, + f.read().decode("utf-8"), + source=path, + fail_on_error=fail_on_error, + ) except Exception as e: - msg = 'Cannot read certificate file {0}: {1}'.format(path, e) + msg = "Cannot read certificate file {0}: {1}".format(path, e) if fail_on_error: module.fail_json(msg=msg) else: @@ -253,9 +276,9 @@ def load_PEM_list(module, path, fail_on_error=True): class CertificateSet(object): - ''' + """ Stores a set of certificates. Allows to search for parent (issuer of a certificate). - ''' + """ def __init__(self, module): self.module = module @@ -273,10 +296,10 @@ class CertificateSet(object): self.certificate_by_cert[cert.cert] = cert def load(self, path): - ''' + """ Load lists of PEM certificates from a file or a directory. - ''' - b_path = to_bytes(path, errors='surrogate_or_strict') + """ + b_path = to_bytes(path, errors="surrogate_or_strict") if os.path.isdir(b_path): for directory, dummy, files in os.walk(b_path, followlinks=True): for file in files: @@ -285,9 +308,9 @@ class CertificateSet(object): self._load_file(b_path) def find_parent(self, cert): - ''' + """ Search for the parent (issuer) of a certificate. Return ``None`` if none was found. - ''' + """ potential_parents = self.certificates_by_issuer.get(cert.cert.issuer, []) for potential_parent in potential_parents: if is_parent(self.module, cert, potential_parent): @@ -296,55 +319,62 @@ class CertificateSet(object): def format_cert(cert): - ''' + """ Return human readable representation of certificate for error messages. - ''' + """ return str(cert.cert) def check_cycle(module, occured_certificates, next): - ''' + """ Make sure that next is not in occured_certificates so far, and add it. - ''' + """ next_cert = next.cert if next_cert in occured_certificates: - module.fail_json(msg='Found cycle while building certificate chain') + module.fail_json(msg="Found cycle while building certificate chain") occured_certificates.add(next_cert) def main(): module = AnsibleModule( argument_spec=dict( - input_chain=dict(type='str', required=True), - root_certificates=dict(type='list', required=True, elements='path'), - intermediate_certificates=dict(type='list', default=[], elements='path'), + input_chain=dict(type="str", required=True), + root_certificates=dict(type="list", required=True, elements="path"), + intermediate_certificates=dict(type="list", default=[], elements="path"), ), supports_check_mode=True, ) if not HAS_CRYPTOGRAPHY: - module.fail_json(msg=missing_required_lib('cryptography >= 1.5'), exception=CRYPTOGRAPHY_IMP_ERR) + module.fail_json( + msg=missing_required_lib("cryptography >= 1.5"), + exception=CRYPTOGRAPHY_IMP_ERR, + ) # Load chain - chain = parse_PEM_list(module, module.params['input_chain'], source='input chain') + chain = parse_PEM_list(module, module.params["input_chain"], source="input chain") if len(chain) == 0: - module.fail_json(msg='Input chain must contain at least one certificate') + module.fail_json(msg="Input chain must contain at least one certificate") # Check chain for i, parent in enumerate(chain): if i > 0: if not is_parent(module, chain[i - 1], parent): - module.fail_json(msg=('Cannot verify input chain: certificate #{2}: {3} is not issuer ' + - 'of certificate #{0}: {1}').format(i, format_cert(chain[i - 1]), i + 1, format_cert(parent))) + module.fail_json( + msg=( + "Cannot verify input chain: certificate #{2}: {3} is not issuer " + + "of certificate #{0}: {1}" + ).format(i, format_cert(chain[i - 1]), i + 1, format_cert(parent)) + ) # Load intermediate certificates intermediates = CertificateSet(module) - for path in module.params['intermediate_certificates']: + for path in module.params["intermediate_certificates"]: intermediates.load(path) # Load root certificates roots = CertificateSet(module) - for path in module.params['root_certificates']: + for path in module.params["root_certificates"]: roots.load(path) # Try to complete chain @@ -366,7 +396,11 @@ def main(): completed.append(intermediate) current = intermediate else: - module.fail_json(msg='Cannot complete chain. Stuck at certificate {0}'.format(format_cert(current))) + module.fail_json( + msg="Cannot complete chain. Stuck at certificate {0}".format( + format_cert(current) + ) + ) # Return results complete_chain = chain + completed diff --git a/plugins/modules/crypto_info.py b/plugins/modules/crypto_info.py index 9da97392..ac3c1c27 100644 --- a/plugins/modules/crypto_info.py +++ b/plugins/modules/crypto_info.py @@ -198,33 +198,33 @@ else: CURVES = ( - ('secp224r1', 'SECP224R1'), - ('secp256k1', 'SECP256K1'), - ('secp256r1', 'SECP256R1'), - ('secp384r1', 'SECP384R1'), - ('secp521r1', 'SECP521R1'), - ('secp192r1', 'SECP192R1'), - ('sect163k1', 'SECT163K1'), - ('sect163r2', 'SECT163R2'), - ('sect233k1', 'SECT233K1'), - ('sect233r1', 'SECT233R1'), - ('sect283k1', 'SECT283K1'), - ('sect283r1', 'SECT283R1'), - ('sect409k1', 'SECT409K1'), - ('sect409r1', 'SECT409R1'), - ('sect571k1', 'SECT571K1'), - ('sect571r1', 'SECT571R1'), - ('brainpoolP256r1', 'BrainpoolP256R1'), - ('brainpoolP384r1', 'BrainpoolP384R1'), - ('brainpoolP512r1', 'BrainpoolP512R1'), + ("secp224r1", "SECP224R1"), + ("secp256k1", "SECP256K1"), + ("secp256r1", "SECP256R1"), + ("secp384r1", "SECP384R1"), + ("secp521r1", "SECP521R1"), + ("secp192r1", "SECP192R1"), + ("sect163k1", "SECT163K1"), + ("sect163r2", "SECT163R2"), + ("sect233k1", "SECT233K1"), + ("sect233r1", "SECT233R1"), + ("sect283k1", "SECT283K1"), + ("sect283r1", "SECT283R1"), + ("sect409k1", "SECT409K1"), + ("sect409r1", "SECT409R1"), + ("sect571k1", "SECT571K1"), + ("sect571r1", "SECT571R1"), + ("brainpoolP256r1", "BrainpoolP256R1"), + ("brainpoolP384r1", "BrainpoolP384R1"), + ("brainpoolP512r1", "BrainpoolP512R1"), ) def add_crypto_information(module): result = {} - result['python_cryptography_installed'] = HAS_CRYPTOGRAPHY + result["python_cryptography_installed"] = HAS_CRYPTOGRAPHY if not HAS_CRYPTOGRAPHY: - result['python_cryptography_import_error'] = CRYPTOGRAPHY_IMP_ERR + result["python_cryptography_import_error"] = CRYPTOGRAPHY_IMP_ERR return result has_ed25519 = CRYPTOGRAPHY_HAS_ED25519 @@ -233,7 +233,8 @@ def add_crypto_information(module): from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, ) - Ed25519PrivateKey.from_private_bytes(b'') + + Ed25519PrivateKey.from_private_bytes(b"") except ValueError: pass except UnsupportedAlgorithm: @@ -243,7 +244,8 @@ def add_crypto_information(module): if has_ed448: try: from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey - Ed448PrivateKey.from_private_bytes(b'') + + Ed448PrivateKey.from_private_bytes(b"") except ValueError: pass except UnsupportedAlgorithm: @@ -255,8 +257,9 @@ def add_crypto_information(module): from cryptography.hazmat.primitives.asymmetric.x25519 import ( X25519PrivateKey, ) + if CRYPTOGRAPHY_HAS_X25519_FULL: - X25519PrivateKey.from_private_bytes(b'') + X25519PrivateKey.from_private_bytes(b"") else: # Some versions do not support serialization and deserialization - use generate() instead X25519PrivateKey.generate() @@ -269,7 +272,8 @@ def add_crypto_information(module): if has_x448: try: from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey - X448PrivateKey.from_private_bytes(b'') + + X448PrivateKey.from_private_bytes(b"") except ValueError: pass except UnsupportedAlgorithm: @@ -282,59 +286,65 @@ def add_crypto_information(module): backend = cryptography.hazmat.backends.default_backend() for curve_name, constructor_name in CURVES: - ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(constructor_name) + ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get( + constructor_name + ) if ecclass: try: - cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(curve=ecclass(), backend=backend) + cryptography.hazmat.primitives.asymmetric.ec.generate_private_key( + curve=ecclass(), backend=backend + ) curves.append(curve_name) except UnsupportedAlgorithm: pass - except CryptographyInternalError: # pylint: disable=duplicate-except,bad-except-order + except ( # pylint: disable=duplicate-except,bad-except-order + CryptographyInternalError + ): # On Fedora 41, some curves result in InternalError. This is probably because # Fedora's cryptography is linked against the system libssl, which has the # curves removed. pass info = { - 'version': CRYPTOGRAPHY_VERSION, - 'curves': curves, - 'has_ec': CRYPTOGRAPHY_HAS_EC, - 'has_ec_sign': CRYPTOGRAPHY_HAS_EC_SIGN, - 'has_ed25519': has_ed25519, - 'has_ed25519_sign': has_ed25519 and CRYPTOGRAPHY_HAS_ED25519_SIGN, - 'has_ed448': has_ed448, - 'has_ed448_sign': has_ed448 and CRYPTOGRAPHY_HAS_ED448_SIGN, - 'has_dsa': CRYPTOGRAPHY_HAS_DSA, - 'has_dsa_sign': CRYPTOGRAPHY_HAS_DSA_SIGN, - 'has_rsa': CRYPTOGRAPHY_HAS_RSA, - 'has_rsa_sign': CRYPTOGRAPHY_HAS_RSA_SIGN, - 'has_x25519': has_x25519, - 'has_x25519_serialization': has_x25519 and CRYPTOGRAPHY_HAS_X25519_FULL, - 'has_x448': has_x448, + "version": CRYPTOGRAPHY_VERSION, + "curves": curves, + "has_ec": CRYPTOGRAPHY_HAS_EC, + "has_ec_sign": CRYPTOGRAPHY_HAS_EC_SIGN, + "has_ed25519": has_ed25519, + "has_ed25519_sign": has_ed25519 and CRYPTOGRAPHY_HAS_ED25519_SIGN, + "has_ed448": has_ed448, + "has_ed448_sign": has_ed448 and CRYPTOGRAPHY_HAS_ED448_SIGN, + "has_dsa": CRYPTOGRAPHY_HAS_DSA, + "has_dsa_sign": CRYPTOGRAPHY_HAS_DSA_SIGN, + "has_rsa": CRYPTOGRAPHY_HAS_RSA, + "has_rsa_sign": CRYPTOGRAPHY_HAS_RSA_SIGN, + "has_x25519": has_x25519, + "has_x25519_serialization": has_x25519 and CRYPTOGRAPHY_HAS_X25519_FULL, + "has_x448": has_x448, } - result['python_cryptography_capabilities'] = info + result["python_cryptography_capabilities"] = info return result def add_openssl_information(module): - openssl_binary = module.get_bin_path('openssl') + openssl_binary = module.get_bin_path("openssl") result = { - 'openssl_present': openssl_binary is not None, + "openssl_present": openssl_binary is not None, } if openssl_binary is None: return result openssl_result = { - 'path': openssl_binary, + "path": openssl_binary, } - result['openssl'] = openssl_result + result["openssl"] = openssl_result - rc, out, err = module.run_command([openssl_binary, 'version']) + rc, out, err = module.run_command([openssl_binary, "version"]) if rc == 0: - openssl_result['version_output'] = out + openssl_result["version_output"] = out parts = out.split(None, 2) if len(parts) > 1: - openssl_result['version'] = parts[1] + openssl_result["version"] = parts[1] return result @@ -353,5 +363,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/ecs_certificate.py b/plugins/modules/ecs_certificate.py index d5076d9a..35337d0f 100644 --- a/plugins/modules/ecs_certificate.py +++ b/plugins/modules/ecs_certificate.py @@ -578,6 +578,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( CRYPTOGRAPHY_IMP_ERR = None try: import cryptography + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -585,14 +586,20 @@ except ImportError: else: CRYPTOGRAPHY_FOUND = True -MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.6" def validate_cert_expiry(cert_expiry): - search_string_partial = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\Z') - search_string_full = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):' - r'([0-5][0-9]|60)(.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))\Z') - if search_string_partial.match(cert_expiry) or search_string_full.match(cert_expiry): + search_string_partial = re.compile( + r"^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\Z" + ) + search_string_full = re.compile( + r"^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):" + r"([0-5][0-9]|60)(.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))\Z" + ) + if search_string_partial.match(cert_expiry) or search_string_full.match( + cert_expiry + ): return True return False @@ -600,7 +607,9 @@ def validate_cert_expiry(cert_expiry): def calculate_cert_days(expires_after): cert_days = 0 if expires_after: - expires_after_datetime = datetime.datetime.strptime(expires_after, '%Y-%m-%dT%H:%M:%SZ') + expires_after_datetime = datetime.datetime.strptime( + expires_after, "%Y-%m-%dT%H:%M:%SZ" + ) cert_days = (expires_after_datetime - datetime.datetime.now()).days return cert_days @@ -612,24 +621,24 @@ def convert_module_param_to_json_bool(module, dict_param_name, param_name): body = {} if module.params[param_name] is not None: if module.params[param_name]: - body[dict_param_name] = 'true' + body[dict_param_name] = "true" else: - body[dict_param_name] = 'false' + body[dict_param_name] = "false" return body class EcsCertificate(object): - ''' + """ Entrust Certificate Services certificate class. - ''' + """ def __init__(self, module): - self.path = module.params['path'] - self.full_chain_path = module.params['full_chain_path'] - self.force = module.params['force'] - self.backup = module.params['backup'] - self.request_type = module.params['request_type'] - self.csr = module.params['csr'] + self.path = module.params["path"] + self.full_chain_path = module.params["full_chain_path"] + self.force = module.params["force"] + self.backup = module.params["backup"] + self.request_type = module.params["request_type"] + self.csr = module.params["csr"] # All return values self.changed = False @@ -646,82 +655,94 @@ class EcsCertificate(object): self.ecs_client = None if self.path and os.path.exists(self.path): try: - self.cert = load_certificate(self.path, backend='cryptography') + self.cert = load_certificate(self.path, backend="cryptography") except Exception: self.cert = None # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( - entrust_api_user=module.params['entrust_api_user'], - entrust_api_key=module.params['entrust_api_key'], - entrust_api_cert=module.params['entrust_api_client_cert_path'], - entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], - entrust_api_specification_path=module.params['entrust_api_specification_path'] + entrust_api_user=module.params["entrust_api_user"], + entrust_api_key=module.params["entrust_api_key"], + entrust_api_cert=module.params["entrust_api_client_cert_path"], + entrust_api_cert_key=module.params["entrust_api_client_cert_key_path"], + entrust_api_specification_path=module.params[ + "entrust_api_specification_path" + ], ) except SessionConfigurationException as e: - module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) + module.fail_json( + msg="Failed to initialize Entrust Provider: {0}".format(to_native(e)) + ) try: self.ecs_client.GetAppVersion() except RestOperationException as e: - module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message))) + module.fail_json( + msg="Please verify credential information. Received exception when testing ECS connection: {0}".format( + to_native(e.message) + ) + ) # Conversion of the fields that go into the 'tracking' parameter of the request object def convert_tracking_params(self, module): body = {} tracking = {} - if module.params['requester_name']: - tracking['requesterName'] = module.params['requester_name'] - if module.params['requester_email']: - tracking['requesterEmail'] = module.params['requester_email'] - if module.params['requester_phone']: - tracking['requesterPhone'] = module.params['requester_phone'] - if module.params['tracking_info']: - tracking['trackingInfo'] = module.params['tracking_info'] - if module.params['custom_fields']: + if module.params["requester_name"]: + tracking["requesterName"] = module.params["requester_name"] + if module.params["requester_email"]: + tracking["requesterEmail"] = module.params["requester_email"] + if module.params["requester_phone"]: + tracking["requesterPhone"] = module.params["requester_phone"] + if module.params["tracking_info"]: + tracking["trackingInfo"] = module.params["tracking_info"] + if module.params["custom_fields"]: # Omit custom fields from submitted dict if not present, instead of submitting them with value of 'null' # The ECS API does technically accept null without error, but it complicates debugging user escalations and is unnecessary bandwidth. custom_fields = {} - for k, v in module.params['custom_fields'].items(): + for k, v in module.params["custom_fields"].items(): if v is not None: custom_fields[k] = v - tracking['customFields'] = custom_fields - if module.params['additional_emails']: - tracking['additionalEmails'] = module.params['additional_emails'] - body['tracking'] = tracking + tracking["customFields"] = custom_fields + if module.params["additional_emails"]: + tracking["additionalEmails"] = module.params["additional_emails"] + body["tracking"] = tracking return body def convert_cert_subject_params(self, module): body = {} - if module.params['subject_alt_name']: - body['subjectAltName'] = module.params['subject_alt_name'] - if module.params['org']: - body['org'] = module.params['org'] - if module.params['ou']: - body['ou'] = module.params['ou'] + if module.params["subject_alt_name"]: + body["subjectAltName"] = module.params["subject_alt_name"] + if module.params["org"]: + body["org"] = module.params["org"] + if module.params["ou"]: + body["ou"] = module.params["ou"] return body def convert_general_params(self, module): body = {} - if module.params['eku']: - body['eku'] = module.params['eku'] - if self.request_type == 'new': - body['certType'] = module.params['cert_type'] - body['clientId'] = module.params['client_id'] - body.update(convert_module_param_to_json_bool(module, 'ctLog', 'ct_log')) - body.update(convert_module_param_to_json_bool(module, 'endUserKeyStorageAgreement', 'end_user_key_storage_agreement')) + if module.params["eku"]: + body["eku"] = module.params["eku"] + if self.request_type == "new": + body["certType"] = module.params["cert_type"] + body["clientId"] = module.params["client_id"] + body.update(convert_module_param_to_json_bool(module, "ctLog", "ct_log")) + body.update( + convert_module_param_to_json_bool( + module, "endUserKeyStorageAgreement", "end_user_key_storage_agreement" + ) + ) return body def convert_expiry_params(self, module): body = {} - if module.params['cert_lifetime']: - body['certLifetime'] = module.params['cert_lifetime'] - elif module.params['cert_expiry']: - body['certExpiryDate'] = module.params['cert_expiry'] + if module.params["cert_lifetime"]: + body["certLifetime"] = module.params["cert_lifetime"] + elif module.params["cert_expiry"]: + body["certExpiryDate"] = module.params["cert_expiry"] # If neither cerTLifetime or certExpiryDate was specified and the request type is new, default to 365 days - elif self.request_type != 'reissue': + elif self.request_type != "reissue": gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())) expiry = gmt_now + datetime.timedelta(days=365) - body['certExpiryDate'] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") + body["certExpiryDate"] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") return body def set_tracking_id_by_serial_number(self, module): @@ -729,21 +750,30 @@ class EcsCertificate(object): # Use serial_number to identify if certificate is an Entrust Certificate # with an associated tracking ID serial_number = "{0:X}".format(self.cert.serial_number) - cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {}) + cert_results = self.ecs_client.GetCertificates( + serialNumber=serial_number + ).get("certificates", {}) if len(cert_results) == 1: - self.tracking_id = cert_results[0].get('trackingId') + self.tracking_id = cert_results[0].get("trackingId") except RestOperationException: # If we fail to find a cert by serial number, that's fine, we just do not set self.tracking_id return def set_cert_details(self, module): try: - self.cert_details = self.ecs_client.GetCertificate(trackingId=self.tracking_id) - self.cert_status = self.cert_details.get('status') - self.serial_number = self.cert_details.get('serialNumber') - self.cert_days = calculate_cert_days(self.cert_details.get('expiresAfter')) + self.cert_details = self.ecs_client.GetCertificate( + trackingId=self.tracking_id + ) + self.cert_status = self.cert_details.get("status") + self.serial_number = self.cert_details.get("serialNumber") + self.cert_days = calculate_cert_days(self.cert_details.get("expiresAfter")) except RestOperationException as e: - module.fail_json('Failed to get details of certificate with tracking_id="{0}", Error: '.format(self.tracking_id), to_native(e.message)) + module.fail_json( + 'Failed to get details of certificate with tracking_id="{0}", Error: '.format( + self.tracking_id + ), + to_native(e.message), + ) def check(self, module): if self.cert: @@ -751,22 +781,34 @@ class EcsCertificate(object): # We will only set updated tracking ID based on certificate in "path" if it is managed by entrust. self.set_tracking_id_by_serial_number(module) - if module.params['tracking_id'] and self.tracking_id and module.params['tracking_id'] != self.tracking_id: - module.warn('tracking_id parameter of "{0}" provided, but will be ignored. Valid certificate was present in path "{1}" with ' - 'tracking_id of "{2}".'.format(module.params['tracking_id'], self.path, self.tracking_id)) + if ( + module.params["tracking_id"] + and self.tracking_id + and module.params["tracking_id"] != self.tracking_id + ): + module.warn( + 'tracking_id parameter of "{0}" provided, but will be ignored. Valid certificate was present in path "{1}" with ' + 'tracking_id of "{2}".'.format( + module.params["tracking_id"], self.path, self.tracking_id + ) + ) # If we did not end up setting tracking_id based on existing cert, get from module params if not self.tracking_id: - self.tracking_id = module.params['tracking_id'] + self.tracking_id = module.params["tracking_id"] if not self.tracking_id: return False self.set_cert_details(module) - if self.cert_status == 'EXPIRED' or self.cert_status == 'SUSPENDED' or self.cert_status == 'REVOKED': + if ( + self.cert_status == "EXPIRED" + or self.cert_status == "SUSPENDED" + or self.cert_status == "REVOKED" + ): return False - if self.cert_days < module.params['remaining_days']: + if self.cert_days < module.params["remaining_days"]: return False return True @@ -777,20 +819,25 @@ class EcsCertificate(object): # Read the CSR contents if self.csr and os.path.exists(self.csr): - with open(self.csr, 'r') as csr_file: - body['csr'] = csr_file.read() + with open(self.csr, "r") as csr_file: + body["csr"] = csr_file.read() # Check if the path is already a cert # tracking_id may be set as a parameter or by get_cert_details if an entrust cert is in 'path'. If tracking ID is null # We will be performing a reissue operation. - if self.request_type != 'new' and not self.tracking_id: - module.warn('No existing Entrust certificate found in path={0} and no tracking_id was provided, setting request_type to "new" for this task' - 'run. Future playbook runs that point to the pathination file in {1} will use request_type={2}' - .format(self.path, self.path, self.request_type)) - self.request_type = 'new' - elif self.request_type == 'new' and self.tracking_id: - module.warn('Existing certificate being acted upon, but request_type is "new", so will be a new certificate issuance rather than a' - 'reissue or renew') + if self.request_type != "new" and not self.tracking_id: + module.warn( + 'No existing Entrust certificate found in path={0} and no tracking_id was provided, setting request_type to "new" for this task' + "run. Future playbook runs that point to the pathination file in {1} will use request_type={2}".format( + self.path, self.path, self.request_type + ) + ) + self.request_type = "new" + elif self.request_type == "new" and self.tracking_id: + module.warn( + 'Existing certificate being acted upon, but request_type is "new", so will be a new certificate issuance rather than a' + "reissue or renew" + ) # Use cases where request type is new and no existing certificate, or where request type is reissue/renew and a valid # existing certificate is found, do not need warnings. @@ -801,135 +848,161 @@ class EcsCertificate(object): if not module.check_mode: try: - if self.request_type == 'validate_only': - body['validateOnly'] = 'true' + if self.request_type == "validate_only": + body["validateOnly"] = "true" result = self.ecs_client.NewCertRequest(Body=body) - if self.request_type == 'new': + if self.request_type == "new": result = self.ecs_client.NewCertRequest(Body=body) - elif self.request_type == 'renew': - result = self.ecs_client.RenewCertRequest(trackingId=self.tracking_id, Body=body) - elif self.request_type == 'reissue': - result = self.ecs_client.ReissueCertRequest(trackingId=self.tracking_id, Body=body) - self.tracking_id = result.get('trackingId') + elif self.request_type == "renew": + result = self.ecs_client.RenewCertRequest( + trackingId=self.tracking_id, Body=body + ) + elif self.request_type == "reissue": + result = self.ecs_client.ReissueCertRequest( + trackingId=self.tracking_id, Body=body + ) + self.tracking_id = result.get("trackingId") self.set_cert_details(module) except RestOperationException as e: - module.fail_json(msg='Failed to request new certificate from Entrust (ECS) {0}'.format(e.message)) + module.fail_json( + msg="Failed to request new certificate from Entrust (ECS) {0}".format( + e.message + ) + ) - if self.request_type != 'validate_only': + if self.request_type != "validate_only": if self.backup: self.backup_file = module.backup_local(self.path) - write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) - if self.full_chain_path and self.cert_details.get('chainCerts'): + write_file(module, to_bytes(self.cert_details.get("endEntityCert"))) + if self.full_chain_path and self.cert_details.get("chainCerts"): if self.backup: - self.backup_full_chain_file = module.backup_local(self.full_chain_path) - chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' - write_file(module, to_bytes(chain_string), path=self.full_chain_path) + self.backup_full_chain_file = module.backup_local( + self.full_chain_path + ) + chain_string = ( + "\n".join(self.cert_details.get("chainCerts")) + "\n" + ) + write_file( + module, to_bytes(chain_string), path=self.full_chain_path + ) self.changed = True # If there is no certificate present in path but a tracking ID was specified, save it to disk elif not os.path.exists(self.path) and self.tracking_id: if not module.check_mode: - write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) - if self.full_chain_path and self.cert_details.get('chainCerts'): - chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' - write_file(module, to_bytes(chain_string), path=self.full_chain_path) + write_file(module, to_bytes(self.cert_details.get("endEntityCert"))) + if self.full_chain_path and self.cert_details.get("chainCerts"): + chain_string = "\n".join(self.cert_details.get("chainCerts")) + "\n" + write_file( + module, to_bytes(chain_string), path=self.full_chain_path + ) self.changed = True def dump(self): result = { - 'changed': self.changed, - 'filename': self.path, - 'tracking_id': self.tracking_id, - 'cert_status': self.cert_status, - 'serial_number': self.serial_number, - 'cert_days': self.cert_days, - 'cert_details': self.cert_details, + "changed": self.changed, + "filename": self.path, + "tracking_id": self.tracking_id, + "cert_status": self.cert_status, + "serial_number": self.serial_number, + "cert_days": self.cert_days, + "cert_details": self.cert_details, } if self.backup_file: - result['backup_file'] = self.backup_file - result['backup_full_chain_file'] = self.backup_full_chain_file + result["backup_file"] = self.backup_file + result["backup_full_chain_file"] = self.backup_full_chain_file return result def custom_fields_spec(): return dict( - text1=dict(type='str'), - text2=dict(type='str'), - text3=dict(type='str'), - text4=dict(type='str'), - text5=dict(type='str'), - text6=dict(type='str'), - text7=dict(type='str'), - text8=dict(type='str'), - text9=dict(type='str'), - text10=dict(type='str'), - text11=dict(type='str'), - text12=dict(type='str'), - text13=dict(type='str'), - text14=dict(type='str'), - text15=dict(type='str'), - number1=dict(type='float'), - number2=dict(type='float'), - number3=dict(type='float'), - number4=dict(type='float'), - number5=dict(type='float'), - date1=dict(type='str'), - date2=dict(type='str'), - date3=dict(type='str'), - date4=dict(type='str'), - date5=dict(type='str'), - email1=dict(type='str'), - email2=dict(type='str'), - email3=dict(type='str'), - email4=dict(type='str'), - email5=dict(type='str'), - dropdown1=dict(type='str'), - dropdown2=dict(type='str'), - dropdown3=dict(type='str'), - dropdown4=dict(type='str'), - dropdown5=dict(type='str'), + text1=dict(type="str"), + text2=dict(type="str"), + text3=dict(type="str"), + text4=dict(type="str"), + text5=dict(type="str"), + text6=dict(type="str"), + text7=dict(type="str"), + text8=dict(type="str"), + text9=dict(type="str"), + text10=dict(type="str"), + text11=dict(type="str"), + text12=dict(type="str"), + text13=dict(type="str"), + text14=dict(type="str"), + text15=dict(type="str"), + number1=dict(type="float"), + number2=dict(type="float"), + number3=dict(type="float"), + number4=dict(type="float"), + number5=dict(type="float"), + date1=dict(type="str"), + date2=dict(type="str"), + date3=dict(type="str"), + date4=dict(type="str"), + date5=dict(type="str"), + email1=dict(type="str"), + email2=dict(type="str"), + email3=dict(type="str"), + email4=dict(type="str"), + email5=dict(type="str"), + dropdown1=dict(type="str"), + dropdown2=dict(type="str"), + dropdown3=dict(type="str"), + dropdown4=dict(type="str"), + dropdown5=dict(type="str"), ) def ecs_certificate_argument_spec(): return dict( - backup=dict(type='bool', default=False), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - full_chain_path=dict(type='path'), - tracking_id=dict(type='int'), - remaining_days=dict(type='int', default=30), - request_type=dict(type='str', default='new', choices=['new', 'renew', 'reissue', 'validate_only']), - cert_type=dict(type='str', choices=['STANDARD_SSL', - 'ADVANTAGE_SSL', - 'UC_SSL', - 'EV_SSL', - 'WILDCARD_SSL', - 'PRIVATE_SSL', - 'PD_SSL', - 'CODE_SIGNING', - 'EV_CODE_SIGNING', - 'CDS_INDIVIDUAL', - 'CDS_GROUP', - 'CDS_ENT_LITE', - 'CDS_ENT_PRO', - 'SMIME_ENT', - ]), - csr=dict(type='str'), - subject_alt_name=dict(type='list', elements='str'), - eku=dict(type='str', choices=['SERVER_AUTH', 'CLIENT_AUTH', 'SERVER_AND_CLIENT_AUTH']), - ct_log=dict(type='bool'), - client_id=dict(type='int', default=1), - org=dict(type='str'), - ou=dict(type='list', elements='str'), - end_user_key_storage_agreement=dict(type='bool'), - tracking_info=dict(type='str'), - requester_name=dict(type='str', required=True), - requester_email=dict(type='str', required=True), - requester_phone=dict(type='str', required=True), - additional_emails=dict(type='list', elements='str'), - custom_fields=dict(type='dict', default=None, options=custom_fields_spec()), - cert_expiry=dict(type='str'), - cert_lifetime=dict(type='str', choices=['P1Y', 'P2Y', 'P3Y']), + backup=dict(type="bool", default=False), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + full_chain_path=dict(type="path"), + tracking_id=dict(type="int"), + remaining_days=dict(type="int", default=30), + request_type=dict( + type="str", + default="new", + choices=["new", "renew", "reissue", "validate_only"], + ), + cert_type=dict( + type="str", + choices=[ + "STANDARD_SSL", + "ADVANTAGE_SSL", + "UC_SSL", + "EV_SSL", + "WILDCARD_SSL", + "PRIVATE_SSL", + "PD_SSL", + "CODE_SIGNING", + "EV_CODE_SIGNING", + "CDS_INDIVIDUAL", + "CDS_GROUP", + "CDS_ENT_LITE", + "CDS_ENT_PRO", + "SMIME_ENT", + ], + ), + csr=dict(type="str"), + subject_alt_name=dict(type="list", elements="str"), + eku=dict( + type="str", choices=["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"] + ), + ct_log=dict(type="bool"), + client_id=dict(type="int", default=1), + org=dict(type="str"), + ou=dict(type="list", elements="str"), + end_user_key_storage_agreement=dict(type="bool"), + tracking_info=dict(type="str"), + requester_name=dict(type="str", required=True), + requester_email=dict(type="str", required=True), + requester_phone=dict(type="str", required=True), + additional_emails=dict(type="list", elements="str"), + custom_fields=dict(type="dict", default=None, options=custom_fields_spec()), + cert_expiry=dict(type="str"), + cert_lifetime=dict(type="str", choices=["P1Y", "P2Y", "P3Y"]), ) @@ -939,54 +1012,91 @@ def main(): module = AnsibleModule( argument_spec=ecs_argument_spec, required_if=( - ['request_type', 'new', ['cert_type']], - ['request_type', 'validate_only', ['cert_type']], - ['cert_type', 'CODE_SIGNING', ['end_user_key_storage_agreement']], - ['cert_type', 'EV_CODE_SIGNING', ['end_user_key_storage_agreement']], - ), - mutually_exclusive=( - ['cert_expiry', 'cert_lifetime'], + ["request_type", "new", ["cert_type"]], + ["request_type", "validate_only", ["cert_type"]], + ["cert_type", "CODE_SIGNING", ["end_user_key_storage_agreement"]], + ["cert_type", "EV_CODE_SIGNING", ["end_user_key_storage_agreement"]], ), + mutually_exclusive=(["cert_expiry", "cert_lifetime"],), supports_check_mode=True, ) - if not CRYPTOGRAPHY_FOUND or CRYPTOGRAPHY_VERSION < LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION): - module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), - exception=CRYPTOGRAPHY_IMP_ERR) + if not CRYPTOGRAPHY_FOUND or CRYPTOGRAPHY_VERSION < LooseVersion( + MINIMAL_CRYPTOGRAPHY_VERSION + ): + module.fail_json( + msg=missing_required_lib( + "cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION) + ), + exception=CRYPTOGRAPHY_IMP_ERR, + ) # If validate_only is used, pointing to an existing tracking_id is an invalid operation - if module.params['tracking_id']: - if module.params['request_type'] == 'new' or module.params['request_type'] == 'validate_only': - module.fail_json(msg='The tracking_id field is invalid when request_type="{0}".'.format(module.params['request_type'])) + if module.params["tracking_id"]: + if ( + module.params["request_type"] == "new" + or module.params["request_type"] == "validate_only" + ): + module.fail_json( + msg='The tracking_id field is invalid when request_type="{0}".'.format( + module.params["request_type"] + ) + ) # A reissued request can not specify an expiration date or lifetime - if module.params['request_type'] == 'reissue': - if module.params['cert_expiry']: - module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".') - elif module.params['cert_lifetime']: - module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".') + if module.params["request_type"] == "reissue": + if module.params["cert_expiry"]: + module.fail_json( + msg='The cert_expiry field is invalid when request_type="reissue".' + ) + elif module.params["cert_lifetime"]: + module.fail_json( + msg='The cert_lifetime field is invalid when request_type="reissue".' + ) # Reissued or renew request can omit the CSR - elif module.params['request_type'] != 'renew': - module_params_csr = module.params['csr'] + elif module.params["request_type"] != "renew": + module_params_csr = module.params["csr"] if module_params_csr is None: - module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type'])) + module.fail_json( + msg="The csr field is required when request_type={0}".format( + module.params["request_type"] + ) + ) elif not os.path.exists(module_params_csr): - module.fail_json(msg='The csr field of {0} was not a valid path. csr is required when request_type={1}'.format( - module_params_csr, module.params['request_type'])) + module.fail_json( + msg="The csr field of {0} was not a valid path. csr is required when request_type={1}".format( + module_params_csr, module.params["request_type"] + ) + ) - if module.params['ou'] and len(module.params['ou']) > 1: + if module.params["ou"] and len(module.params["ou"]) > 1: module.fail_json(msg='Multiple "ou" values are not currently supported.') - if module.params['end_user_key_storage_agreement']: - if module.params['cert_type'] != 'CODE_SIGNING' and module.params['cert_type'] != 'EV_CODE_SIGNING': - module.fail_json(msg='Parameter "end_user_key_storage_agreement" is valid only for cert_types "CODE_SIGNING" and "EV_CODE_SIGNING"') + if module.params["end_user_key_storage_agreement"]: + if ( + module.params["cert_type"] != "CODE_SIGNING" + and module.params["cert_type"] != "EV_CODE_SIGNING" + ): + module.fail_json( + msg='Parameter "end_user_key_storage_agreement" is valid only for cert_types "CODE_SIGNING" and "EV_CODE_SIGNING"' + ) - if module.params['org'] and module.params['client_id'] != 1 and module.params['cert_type'] != 'PD_SSL': - module.fail_json(msg='The "org" parameter is not supported when client_id parameter is set to a value other than 1, unless cert_type is "PD_SSL".') + if ( + module.params["org"] + and module.params["client_id"] != 1 + and module.params["cert_type"] != "PD_SSL" + ): + module.fail_json( + msg='The "org" parameter is not supported when client_id parameter is set to a value other than 1, unless cert_type is "PD_SSL".' + ) - if module.params['cert_expiry']: - if not validate_cert_expiry(module.params['cert_expiry']): - module.fail_json(msg='The "cert_expiry" parameter of "{0}" is not a valid date or date-time'.format(module.params['cert_expiry'])) + if module.params["cert_expiry"]: + if not validate_cert_expiry(module.params["cert_expiry"]): + module.fail_json( + msg='The "cert_expiry" parameter of "{0}" is not a valid date or date-time'.format( + module.params["cert_expiry"] + ) + ) certificate = EcsCertificate(module) certificate.request_cert(module) @@ -994,5 +1104,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/ecs_domain.py b/plugins/modules/ecs_domain.py index d80d0efb..6c72dd54 100644 --- a/plugins/modules/ecs_domain.py +++ b/plugins/modules/ecs_domain.py @@ -237,15 +237,15 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import ( def calculate_days_remaining(expiry_date): days_remaining = None if expiry_date: - expiry_datetime = datetime.datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ') + expiry_datetime = datetime.datetime.strptime(expiry_date, "%Y-%m-%dT%H:%M:%SZ") days_remaining = (expiry_datetime - datetime.datetime.now()).days return days_remaining class EcsDomain(object): - ''' + """ Entrust Certificate Services domain class. - ''' + """ def __init__(self, module): self.changed = False @@ -270,53 +270,76 @@ class EcsDomain(object): # Instantiate the ECS client and then try a no-op connection to verify credentials are valid try: self.ecs_client = ECSClient( - entrust_api_user=module.params['entrust_api_user'], - entrust_api_key=module.params['entrust_api_key'], - entrust_api_cert=module.params['entrust_api_client_cert_path'], - entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], - entrust_api_specification_path=module.params['entrust_api_specification_path'] + entrust_api_user=module.params["entrust_api_user"], + entrust_api_key=module.params["entrust_api_key"], + entrust_api_cert=module.params["entrust_api_client_cert_path"], + entrust_api_cert_key=module.params["entrust_api_client_cert_key_path"], + entrust_api_specification_path=module.params[ + "entrust_api_specification_path" + ], ) except SessionConfigurationException as e: - module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) + module.fail_json( + msg="Failed to initialize Entrust Provider: {0}".format(to_native(e)) + ) try: self.ecs_client.GetAppVersion() except RestOperationException as e: - module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message))) + module.fail_json( + msg="Please verify credential information. Received exception when testing ECS connection: {0}".format( + to_native(e.message) + ) + ) def set_domain_details(self, domain_details): - if domain_details.get('verificationMethod'): - self.verification_method = domain_details['verificationMethod'].lower() - self.domain_status = domain_details['verificationStatus'] - self.ov_eligible = domain_details.get('ovEligible') - self.ov_days_remaining = calculate_days_remaining(domain_details.get('ovExpiry')) - self.ev_eligible = domain_details.get('evEligible') - self.ev_days_remaining = calculate_days_remaining(domain_details.get('evExpiry')) - self.client_id = domain_details['clientId'] + if domain_details.get("verificationMethod"): + self.verification_method = domain_details["verificationMethod"].lower() + self.domain_status = domain_details["verificationStatus"] + self.ov_eligible = domain_details.get("ovEligible") + self.ov_days_remaining = calculate_days_remaining( + domain_details.get("ovExpiry") + ) + self.ev_eligible = domain_details.get("evEligible") + self.ev_days_remaining = calculate_days_remaining( + domain_details.get("evExpiry") + ) + self.client_id = domain_details["clientId"] - if self.verification_method == 'dns' and domain_details.get('dnsMethod'): - self.dns_location = domain_details['dnsMethod']['recordDomain'] - self.dns_resource_type = domain_details['dnsMethod']['recordType'] - self.dns_contents = domain_details['dnsMethod']['recordValue'] - elif self.verification_method == 'web_server' and domain_details.get('webServerMethod'): - self.file_location = domain_details['webServerMethod']['fileLocation'] - self.file_contents = domain_details['webServerMethod']['fileContents'] - elif self.verification_method == 'email' and domain_details.get('emailMethod'): - self.emails = domain_details['emailMethod'] + if self.verification_method == "dns" and domain_details.get("dnsMethod"): + self.dns_location = domain_details["dnsMethod"]["recordDomain"] + self.dns_resource_type = domain_details["dnsMethod"]["recordType"] + self.dns_contents = domain_details["dnsMethod"]["recordValue"] + elif self.verification_method == "web_server" and domain_details.get( + "webServerMethod" + ): + self.file_location = domain_details["webServerMethod"]["fileLocation"] + self.file_contents = domain_details["webServerMethod"]["fileContents"] + elif self.verification_method == "email" and domain_details.get("emailMethod"): + self.emails = domain_details["emailMethod"] def check(self, module): try: - domain_details = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + domain_details = self.ecs_client.GetDomain( + clientId=module.params["client_id"], domain=module.params["domain_name"] + ) self.set_domain_details(domain_details) - if self.domain_status != 'APPROVED' and self.domain_status != 'INITIAL_VERIFICATION' and self.domain_status != 'RE_VERIFICATION': + if ( + self.domain_status != "APPROVED" + and self.domain_status != "INITIAL_VERIFICATION" + and self.domain_status != "RE_VERIFICATION" + ): return False # If domain verification is in process, we want to return the random values and treat it as a valid. - if self.domain_status == 'INITIAL_VERIFICATION' or self.domain_status == 'RE_VERIFICATION': + if ( + self.domain_status == "INITIAL_VERIFICATION" + or self.domain_status == "RE_VERIFICATION" + ): # Unless the verification method has changed, in which case we need to do a reverify request. - if self.verification_method != module.params['verification_method']: + if self.verification_method != module.params["verification_method"]: return False - if self.domain_status == 'EXPIRING': + if self.domain_status == "EXPIRING": return False return True @@ -327,83 +350,112 @@ class EcsDomain(object): if not self.check(module): body = {} - body['verificationMethod'] = module.params['verification_method'].upper() - if module.params['verification_method'] == 'email': + body["verificationMethod"] = module.params["verification_method"].upper() + if module.params["verification_method"] == "email": emailMethod = {} - if module.params['verification_email']: - emailMethod['emailSource'] = 'SPECIFIED' - emailMethod['email'] = module.params['verification_email'] + if module.params["verification_email"]: + emailMethod["emailSource"] = "SPECIFIED" + emailMethod["email"] = module.params["verification_email"] else: - emailMethod['emailSource'] = 'INCLUDE_WHOIS' - body['emailMethod'] = emailMethod + emailMethod["emailSource"] = "INCLUDE_WHOIS" + body["emailMethod"] = emailMethod # Only populate domain name in body if it is not an existing domain if not self.domain_status: - body['domainName'] = module.params['domain_name'] + body["domainName"] = module.params["domain_name"] try: if not self.domain_status: - self.ecs_client.AddDomain(clientId=module.params['client_id'], Body=body) + self.ecs_client.AddDomain( + clientId=module.params["client_id"], Body=body + ) else: - self.ecs_client.ReverifyDomain(clientId=module.params['client_id'], domain=module.params['domain_name'], Body=body) + self.ecs_client.ReverifyDomain( + clientId=module.params["client_id"], + domain=module.params["domain_name"], + Body=body, + ) time.sleep(5) - result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + result = self.ecs_client.GetDomain( + clientId=module.params["client_id"], + domain=module.params["domain_name"], + ) # It takes a bit of time before the random values are available - if module.params['verification_method'] == 'dns' or module.params['verification_method'] == 'web_server': + if ( + module.params["verification_method"] == "dns" + or module.params["verification_method"] == "web_server" + ): for i in range(4): # Check both that random values are now available, and that they're different than were populated by previous 'check' - if module.params['verification_method'] == 'dns': - if result.get('dnsMethod') and result['dnsMethod']['recordValue'] != self.dns_contents: + if module.params["verification_method"] == "dns": + if ( + result.get("dnsMethod") + and result["dnsMethod"]["recordValue"] + != self.dns_contents + ): break - elif module.params['verification_method'] == 'web_server': - if result.get('webServerMethod') and result['webServerMethod']['fileContents'] != self.file_contents: + elif module.params["verification_method"] == "web_server": + if ( + result.get("webServerMethod") + and result["webServerMethod"]["fileContents"] + != self.file_contents + ): break time.sleep(10) - result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + result = self.ecs_client.GetDomain( + clientId=module.params["client_id"], + domain=module.params["domain_name"], + ) self.changed = True self.set_domain_details(result) except RestOperationException as e: - module.fail_json(msg='Failed to request domain validation from Entrust (ECS) {0}'.format(e.message)) + module.fail_json( + msg="Failed to request domain validation from Entrust (ECS) {0}".format( + e.message + ) + ) def dump(self): result = { - 'changed': self.changed, - 'client_id': self.client_id, - 'domain_status': self.domain_status, + "changed": self.changed, + "client_id": self.client_id, + "domain_status": self.domain_status, } if self.verification_method: - result['verification_method'] = self.verification_method + result["verification_method"] = self.verification_method if self.ov_eligible is not None: - result['ov_eligible'] = self.ov_eligible + result["ov_eligible"] = self.ov_eligible if self.ov_days_remaining: - result['ov_days_remaining'] = self.ov_days_remaining + result["ov_days_remaining"] = self.ov_days_remaining if self.ev_eligible is not None: - result['ev_eligible'] = self.ev_eligible + result["ev_eligible"] = self.ev_eligible if self.ev_days_remaining: - result['ev_days_remaining'] = self.ev_days_remaining + result["ev_days_remaining"] = self.ev_days_remaining if self.emails: - result['emails'] = self.emails + result["emails"] = self.emails - if self.verification_method == 'dns': - result['dns_location'] = self.dns_location - result['dns_contents'] = self.dns_contents - result['dns_resource_type'] = self.dns_resource_type - elif self.verification_method == 'web_server': - result['file_location'] = self.file_location - result['file_contents'] = self.file_contents - elif self.verification_method == 'email': - result['emails'] = self.emails + if self.verification_method == "dns": + result["dns_location"] = self.dns_location + result["dns_contents"] = self.dns_contents + result["dns_resource_type"] = self.dns_resource_type + elif self.verification_method == "web_server": + result["file_location"] = self.file_location + result["file_contents"] = self.file_contents + elif self.verification_method == "email": + result["emails"] = self.emails return result def ecs_domain_argument_spec(): return dict( - client_id=dict(type='int', default=1), - domain_name=dict(type='str', required=True), - verification_method=dict(type='str', required=True, choices=['dns', 'email', 'manual', 'web_server']), - verification_email=dict(type='str'), + client_id=dict(type="int", default=1), + domain_name=dict(type="str", required=True), + verification_method=dict( + type="str", required=True, choices=["dns", "email", "manual", "web_server"] + ), + verification_email=dict(type="str"), ) @@ -415,8 +467,15 @@ def main(): supports_check_mode=False, ) - if module.params['verification_email'] and module.params['verification_method'] != 'email': - module.fail_json(msg='The verification_email field is invalid when verification_method="{0}".'.format(module.params['verification_method'])) + if ( + module.params["verification_email"] + and module.params["verification_method"] != "email" + ): + module.fail_json( + msg='The verification_email field is invalid when verification_method="{0}".'.format( + module.params["verification_method"] + ) + ) domain = EcsDomain(module) domain.request_domain(module) @@ -424,5 +483,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 0e84bf2c..4df56a47 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -294,7 +294,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.6" CREATE_DEFAULT_CONTEXT_IMP_ERR = None try: @@ -311,6 +311,7 @@ try: import cryptography.exceptions import cryptography.x509 from cryptography.hazmat.backends import default_backend as cryptography_backend + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -320,85 +321,100 @@ else: def send_starttls_packet(sock, server_type): - if server_type == 'mysql': + if server_type == "mysql": ssl_request_packet = ( - b'\x20\x00\x00\x01\x85\xae\x7f\x00' + - b'\x00\x00\x00\x01\x21\x00\x00\x00' + - b'\x00\x00\x00\x00\x00\x00\x00\x00' + - b'\x00\x00\x00\x00\x00\x00\x00\x00' + - b'\x00\x00\x00\x00' + b"\x20\x00\x00\x01\x85\xae\x7f\x00" + + b"\x00\x00\x00\x01\x21\x00\x00\x00" + + b"\x00\x00\x00\x00\x00\x00\x00\x00" + + b"\x00\x00\x00\x00\x00\x00\x00\x00" + + b"\x00\x00\x00\x00" ) - sock.recv(8192) # discard initial handshake from server for this naive implementation + sock.recv( + 8192 + ) # discard initial handshake from server for this naive implementation sock.send(ssl_request_packet) def main(): module = AnsibleModule( argument_spec=dict( - ca_cert=dict(type='path'), - host=dict(type='str', required=True), - port=dict(type='int', required=True), - proxy_host=dict(type='str'), - proxy_port=dict(type='int', default=8080), - server_name=dict(type='str'), - timeout=dict(type='int', default=10), - select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'), - starttls=dict(type='str', choices=['mysql']), - ciphers=dict(type='list', elements='str'), - asn1_base64=dict(type='bool'), - tls_ctx_options=dict(type='list', elements='raw'), - get_certificate_chain=dict(type='bool', default=False), + ca_cert=dict(type="path"), + host=dict(type="str", required=True), + port=dict(type="int", required=True), + proxy_host=dict(type="str"), + proxy_port=dict(type="int", default=8080), + server_name=dict(type="str"), + timeout=dict(type="int", default=10), + select_crypto_backend=dict( + type="str", choices=["auto", "cryptography"], default="auto" + ), + starttls=dict(type="str", choices=["mysql"]), + ciphers=dict(type="list", elements="str"), + asn1_base64=dict(type="bool"), + tls_ctx_options=dict(type="list", elements="raw"), + get_certificate_chain=dict(type="bool", default=False), ), ) - ca_cert = module.params.get('ca_cert') - host = module.params.get('host') - port = module.params.get('port') - proxy_host = module.params.get('proxy_host') - proxy_port = module.params.get('proxy_port') - timeout = module.params.get('timeout') - server_name = module.params.get('server_name') - start_tls_server_type = module.params.get('starttls') - ciphers = module.params.get('ciphers') - asn1_base64 = module.params['asn1_base64'] - tls_ctx_options = module.params['tls_ctx_options'] - get_certificate_chain = module.params['get_certificate_chain'] + ca_cert = module.params.get("ca_cert") + host = module.params.get("host") + port = module.params.get("port") + proxy_host = module.params.get("proxy_host") + proxy_port = module.params.get("proxy_port") + timeout = module.params.get("timeout") + server_name = module.params.get("server_name") + start_tls_server_type = module.params.get("starttls") + ciphers = module.params.get("ciphers") + asn1_base64 = module.params["asn1_base64"] + tls_ctx_options = module.params["tls_ctx_options"] + get_certificate_chain = module.params["get_certificate_chain"] if asn1_base64 is None: module.deprecate( - 'The default value `false` for asn1_base64 is deprecated and will change to `true` in ' - 'community.crypto 3.0.0. If you need this value, it is best to set the value explicitly ' - 'and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible', - version='3.0.0', - collection_name='community.crypto', + "The default value `false` for asn1_base64 is deprecated and will change to `true` in " + "community.crypto 3.0.0. If you need this value, it is best to set the value explicitly " + "and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible", + version="3.0.0", + collection_name="community.crypto", ) asn1_base64 = False if get_certificate_chain and sys.version_info < (3, 10): module.fail_json( - msg='get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). ' - 'The Python version used to run the get_certificate module is %s' % sys.version + msg="get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). " + "The Python version used to run the get_certificate module is %s" + % sys.version ) - backend = module.params.get('select_crypto_backend') - if backend == 'auto': + backend = module.params.get("select_crypto_backend") + 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, + ) result = dict( changed=False, @@ -417,19 +433,27 @@ def main(): if not HAS_CREATE_DEFAULT_CONTEXT: # Python < 2.7.9 if proxy_host: - module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.', - exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) + module.fail_json( + msg="To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.", + exception=CREATE_DEFAULT_CONTEXT_IMP_ERR, + ) if ciphers is not None: - module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.', - exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) + module.fail_json( + msg="To use ciphers, you must run the get_certificate module with Python 2.7 or newer.", + exception=CREATE_DEFAULT_CONTEXT_IMP_ERR, + ) if tls_ctx_options is not None: - module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.', - exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) + module.fail_json( + msg="To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.", + exception=CREATE_DEFAULT_CONTEXT_IMP_ERR, + ) try: # Note: get_server_certificate does not support SNI! cert = get_server_certificate((host, port), ca_certs=ca_cert) except Exception as e: - module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e)) + module.fail_json( + msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e) + ) else: # Python >= 2.7.9 try: @@ -478,21 +502,35 @@ def main(): tls_ctx_option_int = tls_ctx_option_attr # If tls_ctx_option_attr is not an integer else: - module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str)) + module.fail_json( + msg="Failed to determine the numeric value for {0}".format( + tls_ctx_option_str + ) + ) # If the item is an integer elif isinstance(tls_ctx_option, int): # Set tls_ctx_option_int to the item value tls_ctx_option_int = tls_ctx_option # If the item is not a string nor integer else: - module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option)) - tls_ctx_option_int = 0 # make pylint happy; this code is actually unreachable + module.fail_json( + msg="tls_ctx_options must be a string or integer, got {0!r}".format( + tls_ctx_option + ) + ) + tls_ctx_option_int = ( + 0 # make pylint happy; this code is actually unreachable + ) try: # Add the int value of the item to ctx options ctx.options |= tls_ctx_option_int except Exception: - module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int)) + module.fail_json( + msg="Failed to add {0} to CTX options".format( + tls_ctx_option_str or tls_ctx_option_int + ) + ) tls_sock = ctx.wrap_socket(sock, server_hostname=server_name or host) cert = tls_sock.getpeercert(True) @@ -511,7 +549,9 @@ def main(): ssl_obj = tls_sock._sslobj # This is of type ssl._ssl._SSLSocket verified_der_chain = _convert_chain(ssl_obj.get_verified_chain()) - unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain()) + unverified_der_chain = _convert_chain( + ssl_obj.get_unverified_chain() + ) else: # This works with Python 3.13+ @@ -521,70 +561,95 @@ def main(): # if they are not byte strings to work around this. def _convert_chain(chain): return [ - c if isinstance(c, bytes) else c.public_bytes(ssl._ssl.ENCODING_DER) + ( + c + if isinstance(c, bytes) + else c.public_bytes(ssl._ssl.ENCODING_DER) + ) for c in chain ] verified_der_chain = _convert_chain(tls_sock.get_verified_chain()) - unverified_der_chain = _convert_chain(tls_sock.get_unverified_chain()) + unverified_der_chain = _convert_chain( + tls_sock.get_unverified_chain() + ) verified_chain = [DER_cert_to_PEM_cert(c) for c in verified_der_chain] - unverified_chain = [DER_cert_to_PEM_cert(c) for c in unverified_der_chain] + unverified_chain = [ + DER_cert_to_PEM_cert(c) for c in unverified_der_chain + ] except Exception as e: if proxy_host: - module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format( - proxy_host, proxy_port, host, port, e)) + module.fail_json( + msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format( + proxy_host, proxy_port, host, port, e + ) + ) else: - module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e)) + module.fail_json( + msg="Failed to get cert from {0}:{1}, error: {2}".format( + host, port, e + ) + ) - result['cert'] = cert + result["cert"] = cert - if backend == 'cryptography': - x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend()) - result['subject'] = {} + if backend == "cryptography": + x509 = cryptography.x509.load_pem_x509_certificate( + to_bytes(cert), cryptography_backend() + ) + result["subject"] = {} for attribute in x509.subject: - result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value + result["subject"][cryptography_oid_to_name(attribute.oid, short=True)] = ( + attribute.value + ) - result['expired'] = get_not_valid_after(x509) < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE) + result["expired"] = get_not_valid_after(x509) < get_now_datetime( + with_timezone=CRYPTOGRAPHY_TIMEZONE + ) - result['extensions'] = [] + result["extensions"] = [] for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items(): oid = cryptography.x509.oid.ObjectIdentifier(dotted_number) ext = { - 'critical': entry['critical'], - 'asn1_data': entry['value'], - 'name': cryptography_oid_to_name(oid, short=True), + "critical": entry["critical"], + "asn1_data": entry["value"], + "name": cryptography_oid_to_name(oid, short=True), } if not asn1_base64: - ext['asn1_data'] = base64.b64decode(ext['asn1_data']) - result['extensions'].append(ext) + ext["asn1_data"] = base64.b64decode(ext["asn1_data"]) + result["extensions"].append(ext) - result['issuer'] = {} + result["issuer"] = {} for attribute in x509.issuer: - result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value + result["issuer"][cryptography_oid_to_name(attribute.oid, short=True)] = ( + attribute.value + ) - result['not_after'] = get_not_valid_after(x509).strftime('%Y%m%d%H%M%SZ') - result['not_before'] = get_not_valid_before(x509).strftime('%Y%m%d%H%M%SZ') + result["not_after"] = get_not_valid_after(x509).strftime("%Y%m%d%H%M%SZ") + result["not_before"] = get_not_valid_before(x509).strftime("%Y%m%d%H%M%SZ") - result['serial_number'] = x509.serial_number - result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid) + result["serial_number"] = x509.serial_number + result["signature_algorithm"] = cryptography_oid_to_name( + x509.signature_algorithm_oid + ) # We need the -1 offset to get the same values as pyOpenSSL if x509.version == cryptography.x509.Version.v1: - result['version'] = 1 - 1 + result["version"] = 1 - 1 elif x509.version == cryptography.x509.Version.v3: - result['version'] = 3 - 1 + result["version"] = 3 - 1 else: - result['version'] = "unknown" + result["version"] = "unknown" if verified_chain is not None: - result['verified_chain'] = verified_chain + result["verified_chain"] = verified_chain if unverified_chain is not None: - result['unverified_chain'] = unverified_chain + result["unverified_chain"] = unverified_chain module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/luks_device.py b/plugins/modules/luks_device.py index e0e7f31c..ab18e867 100644 --- a/plugins/modules/luks_device.py +++ b/plugins/modules/luks_device.py @@ -435,23 +435,33 @@ STDERR = 2 # used to get out of lsblk output in format 'crypt ' # regex takes care of any possible blank characters -LUKS_NAME_REGEX = re.compile(r'^crypt\s+([^\s]*)\s*$') +LUKS_NAME_REGEX = re.compile(r"^crypt\s+([^\s]*)\s*$") # used to get out of lsblk output # in format 'device: ' -LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*') +LUKS_DEVICE_REGEX = re.compile(r"\s*device:\s+([^\s]*)\s*") # See https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf -LUKS_HEADER = b'LUKS\xba\xbe' +LUKS_HEADER = b"LUKS\xba\xbe" LUKS_HEADER_L = 6 # See https://gitlab.com/cryptsetup/LUKS2-docs/-/blob/master/luks2_doc_wip.pdf -LUKS2_HEADER_OFFSETS = [0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000] -LUKS2_HEADER2 = b'SKUL\xba\xbe' +LUKS2_HEADER_OFFSETS = [ + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, +] +LUKS2_HEADER2 = b"SKUL\xba\xbe" def wipe_luks_headers(device): wipe_offsets = [] - with open(device, 'rb') as f: + with open(device, "rb") as f: # f.seek(0) data = f.read(LUKS_HEADER_L) if data == LUKS_HEADER: @@ -463,85 +473,89 @@ def wipe_luks_headers(device): wipe_offsets.append(offset) if wipe_offsets: - with open(device, 'wb') as f: + with open(device, "wb") as f: for offset in wipe_offsets: f.seek(offset) - f.write(b'\x00\x00\x00\x00\x00\x00') + f.write(b"\x00\x00\x00\x00\x00\x00") class Handler(object): def __init__(self, module): self._module = module - self._lsblk_bin = self._module.get_bin_path('lsblk', True) - self._passphrase_encoding = module.params['passphrase_encoding'] + self._lsblk_bin = self._module.get_bin_path("lsblk", True) + self._passphrase_encoding = module.params["passphrase_encoding"] def get_passphrase_from_module_params(self, parameter_name): passphrase = self._module.params[parameter_name] if passphrase is None: return None - if self._passphrase_encoding == 'text': + if self._passphrase_encoding == "text": return to_bytes(passphrase) try: return b64decode(to_native(passphrase)) except Exception as exc: - self._module.fail_json("Error while base64-decoding '{parameter_name}': {exc}".format(parameter_name=parameter_name, exc=exc)) + self._module.fail_json( + "Error while base64-decoding '{parameter_name}': {exc}".format( + parameter_name=parameter_name, exc=exc + ) + ) def _run_command(self, command, data=None): return self._module.run_command(command, data=data, binary_data=True) def get_device_by_uuid(self, uuid): - ''' Returns the device that holds UUID passed by user - ''' - self._blkid_bin = self._module.get_bin_path('blkid', True) - uuid = self._module.params['uuid'] + """Returns the device that holds UUID passed by user""" + self._blkid_bin = self._module.get_bin_path("blkid", True) + uuid = self._module.params["uuid"] if uuid is None: return None - result = self._run_command([self._blkid_bin, '--uuid', uuid]) + result = self._run_command([self._blkid_bin, "--uuid", uuid]) if result[RETURN_CODE] != 0: return None return result[STDOUT].strip() def get_device_by_label(self, label): - ''' Returns the device that holds label passed by user - ''' - self._blkid_bin = self._module.get_bin_path('blkid', True) - label = self._module.params['label'] + """Returns the device that holds label passed by user""" + self._blkid_bin = self._module.get_bin_path("blkid", True) + label = self._module.params["label"] if label is None: return None - result = self._run_command([self._blkid_bin, '--label', label]) + result = self._run_command([self._blkid_bin, "--label", label]) if result[RETURN_CODE] != 0: return None return result[STDOUT].strip() def generate_luks_name(self, device): - ''' Generate name for luks based on device UUID ('luks-'). - Raises ValueError when obtaining of UUID fails. - ''' - result = self._run_command([self._lsblk_bin, '-n', device, '-o', 'UUID']) + """Generate name for luks based on device UUID ('luks-'). + Raises ValueError when obtaining of UUID fails. + """ + result = self._run_command([self._lsblk_bin, "-n", device, "-o", "UUID"]) if result[RETURN_CODE] != 0: - raise ValueError('Error while generating LUKS name for %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while generating LUKS name for %s: %s" % (device, result[STDERR]) + ) dev_uuid = result[STDOUT].strip() - return 'luks-%s' % dev_uuid + return "luks-%s" % dev_uuid class CryptHandler(Handler): def __init__(self, module): super(CryptHandler, self).__init__(module) - self._cryptsetup_bin = self._module.get_bin_path('cryptsetup', True) + self._cryptsetup_bin = self._module.get_bin_path("cryptsetup", True) def get_container_name_by_device(self, device): - ''' obtain LUKS container name based on the device where it is located - return None if not found - raise ValueError if lsblk command fails - ''' - result = self._run_command([self._lsblk_bin, device, '-nlo', 'type,name']) + """obtain LUKS container name based on the device where it is located + return None if not found + raise ValueError if lsblk command fails + """ + result = self._run_command([self._lsblk_bin, device, "-nlo", "type,name"]) if result[RETURN_CODE] != 0: - raise ValueError('Error while obtaining LUKS name for %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while obtaining LUKS name for %s: %s" % (device, result[STDERR]) + ) for line in result[STDOUT].splitlines(False): m = LUKS_NAME_REGEX.match(line) @@ -550,12 +564,12 @@ class CryptHandler(Handler): return None def get_container_device_by_name(self, name): - ''' obtain device name based on the LUKS container name - return None if not found - raise ValueError if lsblk command fails - ''' + """obtain device name based on the LUKS container name + return None if not found + raise ValueError if lsblk command fails + """ # apparently each device can have only one LUKS container on it - result = self._run_command([self._cryptsetup_bin, 'status', name]) + result = self._run_command([self._cryptsetup_bin, "status", name]) if result[RETURN_CODE] != 0: return None @@ -564,124 +578,148 @@ class CryptHandler(Handler): return device def is_luks(self, device): - ''' check if the LUKS container does exist - ''' - result = self._run_command([self._cryptsetup_bin, 'isLuks', device]) + """check if the LUKS container does exist""" + result = self._run_command([self._cryptsetup_bin, "isLuks", device]) return result[RETURN_CODE] == 0 def get_luks_type(self, device): - ''' get the luks type of a device - ''' + """get the luks type of a device""" if self.is_luks(device): - with open(device, 'rb') as f: + with open(device, "rb") as f: for offset in LUKS2_HEADER_OFFSETS: f.seek(offset) data = f.read(LUKS_HEADER_L) if data == LUKS2_HEADER2: - return 'luks2' - return 'luks1' + return "luks2" + return "luks1" return None def is_luks_slot_set(self, device, keyslot): - ''' check if a keyslot is set - ''' - result = self._run_command([self._cryptsetup_bin, 'luksDump', device]) + """check if a keyslot is set""" + result = self._run_command([self._cryptsetup_bin, "luksDump", device]) if result[RETURN_CODE] != 0: - raise ValueError('Error while dumping LUKS header from %s' % (device, )) - result_luks1 = 'Key Slot %d: ENABLED' % (keyslot) in result[STDOUT] - result_luks2 = ' %d: luks2' % (keyslot) in result[STDOUT] + raise ValueError("Error while dumping LUKS header from %s" % (device,)) + result_luks1 = "Key Slot %d: ENABLED" % (keyslot) in result[STDOUT] + result_luks2 = " %d: luks2" % (keyslot) in result[STDOUT] return result_luks1 or result_luks2 def _add_pbkdf_options(self, options, pbkdf): - if pbkdf['iteration_time'] is not None: - options.extend(['--iter-time', str(int(pbkdf['iteration_time'] * 1000))]) - if pbkdf['iteration_count'] is not None: - options.extend(['--pbkdf-force-iterations', str(pbkdf['iteration_count'])]) - if pbkdf['algorithm'] is not None: - options.extend(['--pbkdf', pbkdf['algorithm']]) - if pbkdf['memory'] is not None: - options.extend(['--pbkdf-memory', str(pbkdf['memory'])]) - if pbkdf['parallel'] is not None: - options.extend(['--pbkdf-parallel', str(pbkdf['parallel'])]) + if pbkdf["iteration_time"] is not None: + options.extend(["--iter-time", str(int(pbkdf["iteration_time"] * 1000))]) + if pbkdf["iteration_count"] is not None: + options.extend(["--pbkdf-force-iterations", str(pbkdf["iteration_count"])]) + if pbkdf["algorithm"] is not None: + options.extend(["--pbkdf", pbkdf["algorithm"]]) + if pbkdf["memory"] is not None: + options.extend(["--pbkdf-memory", str(pbkdf["memory"])]) + if pbkdf["parallel"] is not None: + options.extend(["--pbkdf-parallel", str(pbkdf["parallel"])]) - def run_luks_create(self, device, keyfile, passphrase, keyslot, keysize, cipher, hash_, sector_size, pbkdf): + def run_luks_create( + self, + device, + keyfile, + passphrase, + keyslot, + keysize, + cipher, + hash_, + sector_size, + pbkdf, + ): # create a new luks container; use batch mode to auto confirm - luks_type = self._module.params['type'] - label = self._module.params['label'] + luks_type = self._module.params["type"] + label = self._module.params["label"] options = [] if keysize is not None: - options.append('--key-size=' + str(keysize)) + options.append("--key-size=" + str(keysize)) if label is not None: - options.extend(['--label', label]) - luks_type = 'luks2' + options.extend(["--label", label]) + luks_type = "luks2" if luks_type is not None: - options.extend(['--type', luks_type]) + options.extend(["--type", luks_type]) if cipher is not None: - options.extend(['--cipher', cipher]) + options.extend(["--cipher", cipher]) if hash_ is not None: - options.extend(['--hash', hash_]) + options.extend(["--hash", hash_]) if pbkdf is not None: self._add_pbkdf_options(options, pbkdf) if sector_size is not None: - options.extend(['--sector-size', str(sector_size)]) + options.extend(["--sector-size", str(sector_size)]) if keyslot is not None: - options.extend(['--key-slot', str(keyslot)]) + options.extend(["--key-slot", str(keyslot)]) - args = [self._cryptsetup_bin, 'luksFormat'] + args = [self._cryptsetup_bin, "luksFormat"] args.extend(options) - args.extend(['-q', device]) + args.extend(["-q", device]) if keyfile: args.append(keyfile) else: - args.append('-') + args.append("-") result = self._run_command(args, data=passphrase) if result[RETURN_CODE] != 0: - raise ValueError('Error while creating LUKS on %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while creating LUKS on %s: %s" % (device, result[STDERR]) + ) - def run_luks_open(self, device, keyfile, passphrase, perf_same_cpu_crypt, perf_submit_from_crypt_cpus, - perf_no_read_workqueue, perf_no_write_workqueue, persistent, allow_discards, name): + def run_luks_open( + self, + device, + keyfile, + passphrase, + perf_same_cpu_crypt, + perf_submit_from_crypt_cpus, + perf_no_read_workqueue, + perf_no_write_workqueue, + persistent, + allow_discards, + name, + ): args = [self._cryptsetup_bin] if keyfile: - args.extend(['--key-file', keyfile]) + args.extend(["--key-file", keyfile]) else: - args.extend(['--key-file', '-']) + args.extend(["--key-file", "-"]) if perf_same_cpu_crypt: - args.extend(['--perf-same_cpu_crypt']) + args.extend(["--perf-same_cpu_crypt"]) if perf_submit_from_crypt_cpus: - args.extend(['--perf-submit_from_crypt_cpus']) + args.extend(["--perf-submit_from_crypt_cpus"]) if perf_no_read_workqueue: - args.extend(['--perf-no_read_workqueue']) + args.extend(["--perf-no_read_workqueue"]) if perf_no_write_workqueue: - args.extend(['--perf-no_write_workqueue']) + args.extend(["--perf-no_write_workqueue"]) if persistent: - args.extend(['--persistent']) + args.extend(["--persistent"]) if allow_discards: - args.extend(['--allow-discards']) - args.extend(['open', '--type', 'luks', device, name]) + args.extend(["--allow-discards"]) + args.extend(["open", "--type", "luks", device, name]) result = self._run_command(args, data=passphrase) if result[RETURN_CODE] != 0: - raise ValueError('Error while opening LUKS container on %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while opening LUKS container on %s: %s" + % (device, result[STDERR]) + ) def run_luks_close(self, name): - result = self._run_command([self._cryptsetup_bin, 'close', name]) + result = self._run_command([self._cryptsetup_bin, "close", name]) if result[RETURN_CODE] != 0: - raise ValueError('Error while closing LUKS container %s' % (name)) + raise ValueError("Error while closing LUKS container %s" % (name)) def run_luks_remove(self, device): - wipefs_bin = self._module.get_bin_path('wipefs', True) + wipefs_bin = self._module.get_bin_path("wipefs", True) name = self.get_container_name_by_device(device) if name is not None: self.run_luks_close(name) - result = self._run_command([wipefs_bin, '--all', device]) + result = self._run_command([wipefs_bin, "--all", device]) if result[RETURN_CODE] != 0: - raise ValueError('Error while wiping LUKS container signatures for %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while wiping LUKS container signatures for %s: %s" + % (device, result[STDERR]) + ) # For LUKS2, sometimes both `cryptsetup erase` and `wipefs` do **not** # erase all LUKS signatures (they seem to miss the second header). That's @@ -689,62 +727,75 @@ class CryptHandler(Handler): try: wipe_luks_headers(device) except Exception as exc: - raise ValueError('Error while wiping LUKS container signatures for %s: %s' % (device, exc)) + raise ValueError( + "Error while wiping LUKS container signatures for %s: %s" + % (device, exc) + ) - def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile, - new_passphrase, new_keyslot, pbkdf): - ''' Add new key from a keyfile or passphrase to given 'device'; - authentication done using 'keyfile' or 'passphrase'. - Raises ValueError when command fails. - ''' + def run_luks_add_key( + self, + device, + keyfile, + passphrase, + new_keyfile, + new_passphrase, + new_keyslot, + pbkdf, + ): + """Add new key from a keyfile or passphrase to given 'device'; + authentication done using 'keyfile' or 'passphrase'. + Raises ValueError when command fails. + """ data = [] - args = [self._cryptsetup_bin, 'luksAddKey', device] + args = [self._cryptsetup_bin, "luksAddKey", device] if pbkdf is not None: self._add_pbkdf_options(args, pbkdf) if new_keyslot is not None: - args.extend(['--key-slot', str(new_keyslot)]) + args.extend(["--key-slot", str(new_keyslot)]) if keyfile: - args.extend(['--key-file', keyfile]) + args.extend(["--key-file", keyfile]) else: - args.extend(['--key-file', '-', '--keyfile-size', str(len(passphrase))]) + args.extend(["--key-file", "-", "--keyfile-size", str(len(passphrase))]) data.append(passphrase) if new_keyfile: args.append(new_keyfile) else: - args.append('-') + args.append("-") data.append(new_passphrase) - result = self._run_command(args, data=b''.join(data) or None) + result = self._run_command(args, data=b"".join(data) or None) if result[RETURN_CODE] != 0: - raise ValueError('Error while adding new LUKS keyslot to %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while adding new LUKS keyslot to %s: %s" + % (device, result[STDERR]) + ) - def run_luks_remove_key(self, device, keyfile, passphrase, keyslot, - force_remove_last_key=False): - ''' Remove key from given device - Raises ValueError when command fails - ''' + def run_luks_remove_key( + self, device, keyfile, passphrase, keyslot, force_remove_last_key=False + ): + """Remove key from given device + Raises ValueError when command fails + """ if not force_remove_last_key: - result = self._run_command([self._cryptsetup_bin, 'luksDump', device]) + result = self._run_command([self._cryptsetup_bin, "luksDump", device]) if result[RETURN_CODE] != 0: - raise ValueError('Error while dumping LUKS header from %s' - % (device, )) + raise ValueError("Error while dumping LUKS header from %s" % (device,)) keyslot_count = 0 keyslot_area = False - keyslot_re = re.compile(r'^Key Slot [0-9]+: ENABLED') + keyslot_re = re.compile(r"^Key Slot [0-9]+: ENABLED") for line in result[STDOUT].splitlines(): - if line.startswith('Keyslots:'): + if line.startswith("Keyslots:"): keyslot_area = True - elif line.startswith(' '): + elif line.startswith(" "): # LUKS2 header dumps use human-readable indented output. # Thus we have to look out for 'Keyslots:' and count the # number of indented keyslot numbers. - if keyslot_area and line[2] in '0123456789': + if keyslot_area and line[2] in "0123456789": keyslot_count += 1 - elif line.startswith('\t'): + elif line.startswith("\t"): pass elif keyslot_re.match(line): # LUKS1 header dumps have one line per keyslot with ENABLED @@ -753,57 +804,67 @@ class CryptHandler(Handler): else: keyslot_area = False if keyslot_count < 2: - self._module.fail_json(msg="LUKS device %s has less than two active keyslots. " - "To be able to remove a key, please set " - "`force_remove_last_key` to `true`." % device) + self._module.fail_json( + msg="LUKS device %s has less than two active keyslots. " + "To be able to remove a key, please set " + "`force_remove_last_key` to `true`." % device + ) if keyslot is None: - args = [self._cryptsetup_bin, 'luksRemoveKey', device, '-q'] + args = [self._cryptsetup_bin, "luksRemoveKey", device, "-q"] if keyfile: - args.extend(['--key-file', keyfile]) + args.extend(["--key-file", keyfile]) elif passphrase is not None: - args.extend(['--key-file', '-']) + args.extend(["--key-file", "-"]) else: # Since we supply -q no passphrase is needed - args = [self._cryptsetup_bin, 'luksKillSlot', device, '-q', str(keyslot)] + args = [self._cryptsetup_bin, "luksKillSlot", device, "-q", str(keyslot)] passphrase = None result = self._run_command(args, data=passphrase) if result[RETURN_CODE] != 0: - raise ValueError('Error while removing LUKS key from %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while removing LUKS key from %s: %s" % (device, result[STDERR]) + ) def luks_test_key(self, device, keyfile, passphrase, keyslot=None): - ''' Check whether the keyfile or passphrase works. - Raises ValueError when command fails. - ''' + """Check whether the keyfile or passphrase works. + Raises ValueError when command fails. + """ data = None - args = [self._cryptsetup_bin, 'luksOpen', '--test-passphrase', device] + args = [self._cryptsetup_bin, "luksOpen", "--test-passphrase", device] if keyfile: - args.extend(['--key-file', keyfile]) + args.extend(["--key-file", keyfile]) else: - args.extend(['--key-file', '-']) + args.extend(["--key-file", "-"]) data = passphrase if keyslot is not None: - args.extend(['--key-slot', str(keyslot)]) + args.extend(["--key-slot", str(keyslot)]) result = self._run_command(args, data=data) if result[RETURN_CODE] == 0: return True for output in (STDOUT, STDERR): - if 'No key available with this passphrase' in result[output]: + if "No key available with this passphrase" in result[output]: return False - if 'No usable keyslot is available.' in result[output]: + if "No usable keyslot is available." in result[output]: return False # This check is necessary due to cryptsetup in version 2.0.3 not printing 'No usable keyslot is available' # when using the --key-slot parameter in combination with --test-passphrase - if result[RETURN_CODE] == 1 and keyslot is not None and result[STDOUT] == '' and result[STDERR] == '': + if ( + result[RETURN_CODE] == 1 + and keyslot is not None + and result[STDOUT] == "" + and result[STDERR] == "" + ): return False - raise ValueError('Error while testing whether keyslot exists on %s: %s' - % (device, result[STDERR])) + raise ValueError( + "Error while testing whether keyslot exists on %s: %s" + % (device, result[STDERR]) + ) class ConditionsHandler(Handler): @@ -814,10 +875,10 @@ class ConditionsHandler(Handler): self.device = self.get_device_name() def get_device_name(self): - device = self._module.params.get('device') - label = self._module.params.get('label') - uuid = self._module.params.get('uuid') - name = self._module.params.get('name') + device = self._module.params.get("device") + label = self._module.params.get("label") + uuid = self._module.params.get("uuid") + name = self._module.params.get("name") if device is None and label is not None: device = self.get_device_by_label(label) @@ -829,21 +890,23 @@ class ConditionsHandler(Handler): return device def luks_create(self): - return (self.device is not None and - (self._module.params['keyfile'] is not None or - self._module.params['passphrase'] is not None) and - self._module.params['state'] in ('present', - 'opened', - 'closed') and - not self._crypthandler.is_luks(self.device)) + return ( + self.device is not None + and ( + self._module.params["keyfile"] is not None + or self._module.params["passphrase"] is not None + ) + and self._module.params["state"] in ("present", "opened", "closed") + and not self._crypthandler.is_luks(self.device) + ) def opened_luks_name(self): - ''' If luks is already opened, return its name. - If 'name' parameter is specified and differs - from obtained value, fail. - Return None otherwise - ''' - if self._module.params['state'] != 'opened': + """If luks is already opened, return its name. + If 'name' parameter is specified and differs + from obtained value, fail. + Return None otherwise + """ + if self._module.params["state"] != "opened": return None # try to obtain luks name - it may be already opened @@ -853,24 +916,30 @@ class ConditionsHandler(Handler): # container is not open return None - if self._module.params['name'] is None: + if self._module.params["name"] is None: # container is already opened return name - if name != self._module.params['name']: + if name != self._module.params["name"]: # the container is already open but with different name: # suspicious. back off - self._module.fail_json(msg="LUKS container is already opened " - "under different name '%s'." % name) + self._module.fail_json( + msg="LUKS container is already opened " + "under different name '%s'." % name + ) # container is opened and the names match return name def luks_open(self): - if ((self._module.params['keyfile'] is None and - self._module.params['passphrase'] is None) or - self.device is None or - self._module.params['state'] != 'opened'): + if ( + ( + self._module.params["keyfile"] is None + and self._module.params["passphrase"] is None + ) + or self.device is None + or self._module.params["state"] != "opened" + ): # conditions for open not fulfilled return False @@ -881,8 +950,9 @@ class ConditionsHandler(Handler): return False def luks_close(self): - if ((self._module.params['name'] is None and self.device is None) or - self._module.params['state'] != 'closed'): + if ( + self._module.params["name"] is None and self.device is None + ) or self._module.params["state"] != "closed": # conditions for close not fulfilled return False @@ -891,163 +961,200 @@ class ConditionsHandler(Handler): # successfully getting name based on device means that luks is open luks_is_open = name is not None - if self._module.params['name'] is not None: + if self._module.params["name"] is not None: self.device = self._crypthandler.get_container_device_by_name( - self._module.params['name']) + self._module.params["name"] + ) # successfully getting device based on name means that luks is open luks_is_open = self.device is not None return luks_is_open def luks_add_key(self): - if (self.device is None or - (self._module.params['keyfile'] is None and - self._module.params['passphrase'] is None) or - (self._module.params['new_keyfile'] is None and - self._module.params['new_passphrase'] is None)): + if ( + self.device is None + or ( + self._module.params["keyfile"] is None + and self._module.params["passphrase"] is None + ) + or ( + self._module.params["new_keyfile"] is None + and self._module.params["new_passphrase"] is None + ) + ): # conditions for adding a key not fulfilled return False - if self._module.params['state'] == 'absent': - self._module.fail_json(msg="Contradiction in setup: Asking to " - "add a key to absent LUKS.") + if self._module.params["state"] == "absent": + self._module.fail_json( + msg="Contradiction in setup: Asking to " "add a key to absent LUKS." + ) key_present = self._crypthandler.luks_test_key( self.device, - self._module.params['new_keyfile'], - self.get_passphrase_from_module_params('new_passphrase'), + self._module.params["new_keyfile"], + self.get_passphrase_from_module_params("new_passphrase"), ) - if self._module.params['new_keyslot'] is not None: + if self._module.params["new_keyslot"] is not None: key_present_slot = self._crypthandler.luks_test_key( - self.device, self._module.params['new_keyfile'], - self.get_passphrase_from_module_params('new_passphrase'), - self._module.params['new_keyslot'], + self.device, + self._module.params["new_keyfile"], + self.get_passphrase_from_module_params("new_passphrase"), + self._module.params["new_keyslot"], ) if key_present and not key_present_slot: - self._module.fail_json(msg="Trying to add key that is already present in another slot") + self._module.fail_json( + msg="Trying to add key that is already present in another slot" + ) return not key_present def luks_remove_key(self): - if (self.device is None or - (self._module.params['remove_keyfile'] is None and - self._module.params['remove_passphrase'] is None and - self._module.params['remove_keyslot'] is None)): + if self.device is None or ( + self._module.params["remove_keyfile"] is None + and self._module.params["remove_passphrase"] is None + and self._module.params["remove_keyslot"] is None + ): # conditions for removing a key not fulfilled return False - if self._module.params['state'] == 'absent': - self._module.fail_json(msg="Contradiction in setup: Asking to " - "remove a key from absent LUKS.") + if self._module.params["state"] == "absent": + self._module.fail_json( + msg="Contradiction in setup: Asking to " + "remove a key from absent LUKS." + ) - if self._module.params['remove_keyslot'] is not None: - if not self._crypthandler.is_luks_slot_set(self.device, self._module.params['remove_keyslot']): + if self._module.params["remove_keyslot"] is not None: + if not self._crypthandler.is_luks_slot_set( + self.device, self._module.params["remove_keyslot"] + ): return False result = self._crypthandler.luks_test_key( self.device, - self._module.params['keyfile'], - self.get_passphrase_from_module_params('passphrase'), + self._module.params["keyfile"], + self.get_passphrase_from_module_params("passphrase"), ) if self._crypthandler.luks_test_key( self.device, - self._module.params['keyfile'], - self.get_passphrase_from_module_params('passphrase'), - self._module.params['remove_keyslot'], + self._module.params["keyfile"], + self.get_passphrase_from_module_params("passphrase"), + self._module.params["remove_keyslot"], ): - self._module.fail_json(msg='Cannot remove keyslot with keyfile or passphrase in same slot.') + self._module.fail_json( + msg="Cannot remove keyslot with keyfile or passphrase in same slot." + ) return result return self._crypthandler.luks_test_key( self.device, - self._module.params['remove_keyfile'], - self.get_passphrase_from_module_params('remove_passphrase'), + self._module.params["remove_keyfile"], + self.get_passphrase_from_module_params("remove_passphrase"), ) def luks_remove(self): - return (self.device is not None and - self._module.params['state'] == 'absent' and - self._crypthandler.is_luks(self.device)) + return ( + self.device is not None + and self._module.params["state"] == "absent" + and self._crypthandler.is_luks(self.device) + ) def validate_keyslot(self, param, luks_type): if self._module.params[param] is not None: - if luks_type is None and param == 'keyslot': + if luks_type is None and param == "keyslot": if 8 <= self._module.params[param] <= 31: - self._module.fail_json(msg="You must specify type=luks2 when creating a new LUKS device to use keyslots 8-31.") + self._module.fail_json( + msg="You must specify type=luks2 when creating a new LUKS device to use keyslots 8-31." + ) elif not (0 <= self._module.params[param] <= 7): - self._module.fail_json(msg="When not specifying a type, only the keyslots 0-7 are allowed.") + self._module.fail_json( + msg="When not specifying a type, only the keyslots 0-7 are allowed." + ) - if luks_type == 'luks1' and not 0 <= self._module.params[param] <= 7: - self._module.fail_json(msg="%s must be between 0 and 7 when using LUKS1." % self._module.params[param]) - elif luks_type == 'luks2' and not 0 <= self._module.params[param] <= 31: - self._module.fail_json(msg="%s must be between 0 and 31 when using LUKS2." % self._module.params[param]) + if luks_type == "luks1" and not 0 <= self._module.params[param] <= 7: + self._module.fail_json( + msg="%s must be between 0 and 7 when using LUKS1." + % self._module.params[param] + ) + elif luks_type == "luks2" and not 0 <= self._module.params[param] <= 31: + self._module.fail_json( + msg="%s must be between 0 and 31 when using LUKS2." + % self._module.params[param] + ) def run_module(): # available arguments/parameters that a user can pass module_args = dict( - state=dict(type='str', default='present', choices=['present', 'absent', 'opened', 'closed']), - device=dict(type='str'), - name=dict(type='str'), - keyfile=dict(type='path'), - new_keyfile=dict(type='path'), - remove_keyfile=dict(type='path'), - passphrase=dict(type='str', no_log=True), - new_passphrase=dict(type='str', no_log=True), - remove_passphrase=dict(type='str', no_log=True), - passphrase_encoding=dict(type='str', default='text', choices=['text', 'base64'], no_log=False), - keyslot=dict(type='int', no_log=False), - new_keyslot=dict(type='int', no_log=False), - remove_keyslot=dict(type='int', no_log=False), - force_remove_last_key=dict(type='bool', default=False), - keysize=dict(type='int'), - label=dict(type='str'), - uuid=dict(type='str'), - type=dict(type='str', choices=['luks1', 'luks2']), - cipher=dict(type='str'), - hash=dict(type='str'), - pbkdf=dict( - type='dict', - options=dict( - iteration_time=dict(type='float'), - iteration_count=dict(type='int'), - algorithm=dict(type='str', choices=['argon2i', 'argon2id', 'pbkdf2']), - memory=dict(type='int'), - parallel=dict(type='int'), - ), - mutually_exclusive=[('iteration_time', 'iteration_count')], + state=dict( + type="str", + default="present", + choices=["present", "absent", "opened", "closed"], ), - sector_size=dict(type='int'), - perf_same_cpu_crypt=dict(type='bool', default=False), - perf_submit_from_crypt_cpus=dict(type='bool', default=False), - perf_no_read_workqueue=dict(type='bool', default=False), - perf_no_write_workqueue=dict(type='bool', default=False), - persistent=dict(type='bool', default=False), - allow_discards=dict(type='bool', default=False), + device=dict(type="str"), + name=dict(type="str"), + keyfile=dict(type="path"), + new_keyfile=dict(type="path"), + remove_keyfile=dict(type="path"), + passphrase=dict(type="str", no_log=True), + new_passphrase=dict(type="str", no_log=True), + remove_passphrase=dict(type="str", no_log=True), + passphrase_encoding=dict( + type="str", default="text", choices=["text", "base64"], no_log=False + ), + keyslot=dict(type="int", no_log=False), + new_keyslot=dict(type="int", no_log=False), + remove_keyslot=dict(type="int", no_log=False), + force_remove_last_key=dict(type="bool", default=False), + keysize=dict(type="int"), + label=dict(type="str"), + uuid=dict(type="str"), + type=dict(type="str", choices=["luks1", "luks2"]), + cipher=dict(type="str"), + hash=dict(type="str"), + pbkdf=dict( + type="dict", + options=dict( + iteration_time=dict(type="float"), + iteration_count=dict(type="int"), + algorithm=dict(type="str", choices=["argon2i", "argon2id", "pbkdf2"]), + memory=dict(type="int"), + parallel=dict(type="int"), + ), + mutually_exclusive=[("iteration_time", "iteration_count")], + ), + sector_size=dict(type="int"), + perf_same_cpu_crypt=dict(type="bool", default=False), + perf_submit_from_crypt_cpus=dict(type="bool", default=False), + perf_no_read_workqueue=dict(type="bool", default=False), + perf_no_write_workqueue=dict(type="bool", default=False), + persistent=dict(type="bool", default=False), + allow_discards=dict(type="bool", default=False), ) mutually_exclusive = [ - ('keyfile', 'passphrase'), - ('new_keyfile', 'new_passphrase'), - ('remove_keyfile', 'remove_passphrase', 'remove_keyslot') + ("keyfile", "passphrase"), + ("new_keyfile", "new_passphrase"), + ("remove_keyfile", "remove_passphrase", "remove_keyslot"), ] # seed the result dict in the object - result = dict( - changed=False, - name=None + result = dict(changed=False, name=None) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + module.run_command_environ_update = dict( + LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C" ) - module = AnsibleModule(argument_spec=module_args, - supports_check_mode=True, - mutually_exclusive=mutually_exclusive) - module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') - - if module.params['device'] is not None: + if module.params["device"] is not None: try: - statinfo = os.stat(module.params['device']) + statinfo = os.stat(module.params["device"]) mode = statinfo.st_mode if not stat.S_ISBLK(mode) and not stat.S_ISCHR(mode): - raise Exception('{0} is not a device'.format(module.params['device'])) + raise Exception("{0} is not a device".format(module.params["device"])) except Exception as e: module.fail_json(msg=str(e)) @@ -1055,19 +1162,29 @@ def run_module(): conditions = ConditionsHandler(module, crypt) # conditions not allowed to run - if module.params['label'] is not None and module.params['type'] == 'luks1': - module.fail_json(msg='You cannot combine type luks1 with the label option.') + if module.params["label"] is not None and module.params["type"] == "luks1": + module.fail_json(msg="You cannot combine type luks1 with the label option.") - if module.params['keyslot'] is not None or module.params['new_keyslot'] is not None or module.params['remove_keyslot'] is not None: + if ( + module.params["keyslot"] is not None + or module.params["new_keyslot"] is not None + or module.params["remove_keyslot"] is not None + ): luks_type = crypt.get_luks_type(conditions.get_device_name()) - if luks_type is None and module.params['type'] is not None: - luks_type = module.params['type'] - for param in ['keyslot', 'new_keyslot', 'remove_keyslot']: + if luks_type is None and module.params["type"] is not None: + luks_type = module.params["type"] + for param in ["keyslot", "new_keyslot", "remove_keyslot"]: conditions.validate_keyslot(param, luks_type) - for param in ['new_keyslot', 'remove_keyslot']: - if module.params[param] is not None and module.params['keyfile'] is None and module.params['passphrase'] is None: - module.fail_json(msg="Removing a keyslot requires the passphrase or keyfile of another slot.") + for param in ["new_keyslot", "remove_keyslot"]: + if ( + module.params[param] is not None + and module.params["keyfile"] is None + and module.params["passphrase"] is None + ): + module.fail_json( + msg="Removing a keyslot requires the passphrase or keyfile of another slot." + ) # The conditions are in order to allow more operations in one run. # (e.g. create luks and add a key to it) @@ -1078,18 +1195,18 @@ def run_module(): try: crypt.run_luks_create( conditions.device, - module.params['keyfile'], - conditions.get_passphrase_from_module_params('passphrase'), - module.params['keyslot'], - module.params['keysize'], - module.params['cipher'], - module.params['hash'], - module.params['sector_size'], - module.params['pbkdf'], + module.params["keyfile"], + conditions.get_passphrase_from_module_params("passphrase"), + module.params["keyslot"], + module.params["keysize"], + module.params["cipher"], + module.params["hash"], + module.params["sector_size"], + module.params["pbkdf"], ) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['changed'] = True + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1097,10 +1214,10 @@ def run_module(): name = conditions.opened_luks_name() if name is not None: - result['name'] = name + result["name"] = name if conditions.luks_open(): - name = module.params['name'] + name = module.params["name"] if name is None: try: name = crypt.generate_luks_name(conditions.device) @@ -1110,20 +1227,20 @@ def run_module(): try: crypt.run_luks_open( conditions.device, - module.params['keyfile'], - conditions.get_passphrase_from_module_params('passphrase'), - module.params['perf_same_cpu_crypt'], - module.params['perf_submit_from_crypt_cpus'], - module.params['perf_no_read_workqueue'], - module.params['perf_no_write_workqueue'], - module.params['persistent'], - module.params['allow_discards'], + module.params["keyfile"], + conditions.get_passphrase_from_module_params("passphrase"), + module.params["perf_same_cpu_crypt"], + module.params["perf_submit_from_crypt_cpus"], + module.params["perf_no_read_workqueue"], + module.params["perf_no_write_workqueue"], + module.params["persistent"], + module.params["allow_discards"], name, ) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['name'] = name - result['changed'] = True + result["name"] = name + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1131,19 +1248,18 @@ def run_module(): if conditions.luks_close(): if conditions.device is not None: try: - name = crypt.get_container_name_by_device( - conditions.device) + name = crypt.get_container_name_by_device(conditions.device) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) else: - name = module.params['name'] + name = module.params["name"] if not module.check_mode: try: crypt.run_luks_close(name) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['name'] = name - result['changed'] = True + result["name"] = name + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1153,16 +1269,16 @@ def run_module(): try: crypt.run_luks_add_key( conditions.device, - module.params['keyfile'], - conditions.get_passphrase_from_module_params('passphrase'), - module.params['new_keyfile'], - conditions.get_passphrase_from_module_params('new_passphrase'), - module.params['new_keyslot'], - module.params['pbkdf'], + module.params["keyfile"], + conditions.get_passphrase_from_module_params("passphrase"), + module.params["new_keyfile"], + conditions.get_passphrase_from_module_params("new_passphrase"), + module.params["new_keyslot"], + module.params["pbkdf"], ) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['changed'] = True + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1170,17 +1286,17 @@ def run_module(): if conditions.luks_remove_key(): if not module.check_mode: try: - last_key = module.params['force_remove_last_key'] + last_key = module.params["force_remove_last_key"] crypt.run_luks_remove_key( conditions.device, - module.params['remove_keyfile'], - conditions.get_passphrase_from_module_params('remove_passphrase'), - module.params['remove_keyslot'], + module.params["remove_keyfile"], + conditions.get_passphrase_from_module_params("remove_passphrase"), + module.params["remove_keyslot"], force_remove_last_key=last_key, ) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['changed'] = True + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1191,7 +1307,7 @@ def run_module(): crypt.run_luks_remove(conditions.device) except ValueError as e: module.fail_json(msg="luks_device error: %s" % e) - result['changed'] = True + result["changed"] = True if module.check_mode: module.exit_json(**result) @@ -1203,5 +1319,5 @@ def main(): run_module() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssh_cert.py b/plugins/modules/openssh_cert.py index e6da6375..a65d2568 100644 --- a/plugins/modules/openssh_cert.py +++ b/plugins/modules/openssh_cert.py @@ -312,25 +312,29 @@ class Certificate(OpensshModule): super(Certificate, self).__init__(module) self.ssh_keygen = KeygenCommand(self.module) - self.identifier = self.module.params['identifier'] or "" - self.options = self.module.params['options'] or [] - self.path = self.module.params['path'] - self.pkcs11_provider = self.module.params['pkcs11_provider'] - self.principals = self.module.params['principals'] or [] - self.public_key = self.module.params['public_key'] - self.regenerate = self.module.params['regenerate'] if not self.module.params['force'] else 'always' - self.serial_number = self.module.params['serial_number'] - self.signature_algorithm = self.module.params['signature_algorithm'] - self.signing_key = self.module.params['signing_key'] - self.state = self.module.params['state'] - self.type = self.module.params['type'] - self.use_agent = self.module.params['use_agent'] - self.valid_at = self.module.params['valid_at'] - self.ignore_timestamps = self.module.params['ignore_timestamps'] + self.identifier = self.module.params["identifier"] or "" + self.options = self.module.params["options"] or [] + self.path = self.module.params["path"] + self.pkcs11_provider = self.module.params["pkcs11_provider"] + self.principals = self.module.params["principals"] or [] + self.public_key = self.module.params["public_key"] + self.regenerate = ( + self.module.params["regenerate"] + if not self.module.params["force"] + else "always" + ) + self.serial_number = self.module.params["serial_number"] + self.signature_algorithm = self.module.params["signature_algorithm"] + self.signing_key = self.module.params["signing_key"] + self.state = self.module.params["state"] + self.type = self.module.params["type"] + self.use_agent = self.module.params["use_agent"] + self.valid_at = self.module.params["valid_at"] + self.ignore_timestamps = self.module.params["ignore_timestamps"] self._check_if_base_dir(self.path) - if self.state == 'present': + if self.state == "present": self._validate_parameters() self.data = None @@ -339,7 +343,7 @@ class Certificate(OpensshModule): self._load_certificate() self.time_parameters = None - if self.state == 'present': + if self.state == "present": self._set_time_parameters() def _validate_parameters(self): @@ -347,7 +351,9 @@ class Certificate(OpensshModule): self._check_if_base_dir(path) if self.options and self.type == "host": - self.module.fail_json(msg="Options can only be used with user certificates.") + self.module.fail_json( + msg="Options can only be used with user certificates." + ) if self.use_agent: self._use_agent_available() @@ -358,8 +364,8 @@ class Certificate(OpensshModule): self.module.fail_json(msg="Failed to determine ssh version") elif LooseVersion(ssh_version) < LooseVersion("7.6"): self.module.fail_json( - msg="Signing with CA key in ssh agent requires ssh 7.6 or newer." + - " Your version is: %s" % ssh_version + msg="Signing with CA key in ssh agent requires ssh 7.6 or newer." + + " Your version is: %s" % ssh_version ) def _exists(self): @@ -369,21 +375,23 @@ class Certificate(OpensshModule): try: self.original_data = OpensshCertificate.load(self.path) except (TypeError, ValueError) as e: - if self.regenerate in ('never', 'fail'): - self.module.fail_json(msg="Unable to read existing certificate: %s" % to_native(e)) + if self.regenerate in ("never", "fail"): + self.module.fail_json( + msg="Unable to read existing certificate: %s" % to_native(e) + ) self.module.warn("Unable to read existing certificate: %s" % to_native(e)) def _set_time_parameters(self): try: self.time_parameters = OpensshCertificateTimeParameters( - valid_from=self.module.params['valid_from'], - valid_to=self.module.params['valid_to'], + valid_from=self.module.params["valid_from"], + valid_to=self.module.params["valid_to"], ) except ValueError as e: self.module.fail_json(msg=to_native(e)) def _execute(self): - if self.state == 'present': + if self.state == "present": if self._should_generate(): self._generate() self._update_permissions(self.path) @@ -392,44 +400,58 @@ class Certificate(OpensshModule): self._remove() def _should_generate(self): - if self.regenerate == 'never': + if self.regenerate == "never": return self.original_data is None - elif self.regenerate == 'fail': + elif self.regenerate == "fail": if self.original_data and not self._is_fully_valid(): self.module.fail_json( msg="Certificate does not match the provided options.", - cert=get_cert_dict(self.original_data) + cert=get_cert_dict(self.original_data), ) return self.original_data is None - elif self.regenerate == 'partial_idempotence': + elif self.regenerate == "partial_idempotence": return self.original_data is None or not self._is_partially_valid() - elif self.regenerate == 'full_idempotence': + elif self.regenerate == "full_idempotence": return self.original_data is None or not self._is_fully_valid() else: return True def _is_fully_valid(self): - return self._is_partially_valid() and all([ - self._compare_options() if self.original_data.type == 'user' else True, - self.original_data.key_id == self.identifier, - self.original_data.public_key == self._get_key_fingerprint(self.public_key), - self.original_data.signing_key == self._get_key_fingerprint(self.signing_key), - ]) + return self._is_partially_valid() and all( + [ + self._compare_options() if self.original_data.type == "user" else True, + self.original_data.key_id == self.identifier, + self.original_data.public_key + == self._get_key_fingerprint(self.public_key), + self.original_data.signing_key + == self._get_key_fingerprint(self.signing_key), + ] + ) def _is_partially_valid(self): - return all([ - set(self.original_data.principals) == set(self.principals), - self.original_data.signature_type == self.signature_algorithm if self.signature_algorithm else True, - self.original_data.serial == self.serial_number if self.serial_number is not None else True, - self.original_data.type == self.type, - self._compare_time_parameters(), - ]) + return all( + [ + set(self.original_data.principals) == set(self.principals), + ( + self.original_data.signature_type == self.signature_algorithm + if self.signature_algorithm + else True + ), + ( + self.original_data.serial == self.serial_number + if self.serial_number is not None + else True + ), + self.original_data.type == self.type, + self._compare_time_parameters(), + ] + ) def _compare_time_parameters(self): try: original_time_parameters = OpensshCertificateTimeParameters( valid_from=self.original_data.valid_after, - valid_to=self.original_data.valid_before + valid_to=self.original_data.valid_before, ) except ValueError as e: return self.module.fail_json(msg=to_native(e)) @@ -437,10 +459,12 @@ class Certificate(OpensshModule): if self.ignore_timestamps: return original_time_parameters.within_range(self.valid_at) - return all([ - original_time_parameters == self.time_parameters, - original_time_parameters.within_range(self.valid_at) - ]) + return all( + [ + original_time_parameters == self.time_parameters, + original_time_parameters.within_range(self.valid_at), + ] + ) def _compare_options(self): try: @@ -448,10 +472,12 @@ class Certificate(OpensshModule): except ValueError as e: return self.module.fail_json(msg=to_native(e)) - return all([ - set(self.original_data.critical_options) == set(critical_options), - set(self.original_data.extensions) == set(extensions) - ]) + return all( + [ + set(self.original_data.critical_options) == set(critical_options), + set(self.original_data.extensions) == set(extensions), + ] + ) def _get_key_fingerprint(self, path): private_key_content = self.ssh_keygen.get_private_key(path, check_rc=True)[1] @@ -464,12 +490,16 @@ class Certificate(OpensshModule): temp_certificate = self._generate_temp_certificate() self._safe_secure_move([(temp_certificate, self.path)]) except OSError as e: - self.module.fail_json(msg="Unable to write certificate to %s: %s" % (self.path, to_native(e))) + self.module.fail_json( + msg="Unable to write certificate to %s: %s" % (self.path, to_native(e)) + ) try: self.data = OpensshCertificate.load(self.path) except (TypeError, ValueError) as e: - self.module.fail_json(msg="Unable to read new certificate: %s" % to_native(e)) + self.module.fail_json( + msg="Unable to read new certificate: %s" % to_native(e) + ) def _generate_temp_certificate(self): key_copy = os.path.join(self.module.tmpdir, os.path.basename(self.public_key)) @@ -477,16 +507,28 @@ class Certificate(OpensshModule): try: self.module.preserved_copy(self.public_key, key_copy) except OSError as e: - self.module.fail_json(msg="Unable to stage temporary key: %s" % to_native(e)) + self.module.fail_json( + msg="Unable to stage temporary key: %s" % to_native(e) + ) self.module.add_cleanup_file(key_copy) self.ssh_keygen.generate_certificate( - key_copy, self.identifier, self.options, self.pkcs11_provider, self.principals, self.serial_number, - self.signature_algorithm, self.signing_key, self.type, self.time_parameters, self.use_agent, - environ_update=dict(TZ="UTC"), check_rc=True + key_copy, + self.identifier, + self.options, + self.pkcs11_provider, + self.principals, + self.serial_number, + self.signature_algorithm, + self.signing_key, + self.type, + self.time_parameters, + self.use_agent, + environ_update=dict(TZ="UTC"), + check_rc=True, ) - temp_cert = os.path.splitext(key_copy)[0] + '-cert.pub' + temp_cert = os.path.splitext(key_copy)[0] + "-cert.pub" self.module.add_cleanup_file(temp_cert) return temp_cert @@ -497,29 +539,31 @@ class Certificate(OpensshModule): try: os.remove(self.path) except OSError as e: - self.module.fail_json(msg="Unable to remove existing certificate: %s" % to_native(e)) + self.module.fail_json( + msg="Unable to remove existing certificate: %s" % to_native(e) + ) @property def _result(self): - if self.state != 'present': + if self.state != "present": return {} certificate_info = self.ssh_keygen.get_certificate_info( self.path, - check_rc=self.state == 'present' and not self.module.check_mode, + check_rc=self.state == "present" and not self.module.check_mode, )[1] return { - 'type': self.type, - 'filename': self.path, - 'info': format_cert_info(certificate_info), + "type": self.type, + "filename": self.path, + "info": format_cert_info(certificate_info), } @property def diff(self): return { - 'before': get_cert_dict(self.original_data), - 'after': get_cert_dict(self.data) + "before": get_cert_dict(self.original_data), + "after": get_cert_dict(self.data), } @@ -528,7 +572,17 @@ def format_cert_info(cert_info): string = "" for word in cert_info.split(): - if word in ("Type:", "Public", "Signing", "Key", "Serial:", "Valid:", "Principals:", "Critical", "Extensions:"): + if word in ( + "Type:", + "Public", + "Signing", + "Key", + "Serial:", + "Valid:", + "Principals:", + "Critical", + "Extensions:", + ): result.append(string) string = word else: @@ -544,8 +598,8 @@ def get_cert_dict(data): return {} result = data.to_dict() - result.pop('nonce') - result['signature_algorithm'] = data.signature_type + result.pop("nonce") + result["signature_algorithm"] = data.signature_type return result @@ -553,36 +607,50 @@ def get_cert_dict(data): def main(): module = AnsibleModule( argument_spec=dict( - force=dict(type='bool', default=False), - identifier=dict(type='str'), - options=dict(type='list', elements='str'), - path=dict(type='path', required=True), - pkcs11_provider=dict(type='str'), - principals=dict(type='list', elements='str'), - public_key=dict(type='path'), + force=dict(type="bool", default=False), + identifier=dict(type="str"), + options=dict(type="list", elements="str"), + path=dict(type="path", required=True), + pkcs11_provider=dict(type="str"), + principals=dict(type="list", elements="str"), + public_key=dict(type="path"), regenerate=dict( - type='str', - default='partial_idempotence', - choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always'] + type="str", + default="partial_idempotence", + choices=[ + "never", + "fail", + "partial_idempotence", + "full_idempotence", + "always", + ], ), - signature_algorithm=dict(type='str', choices=['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']), - signing_key=dict(type='path'), - serial_number=dict(type='int'), - state=dict(type='str', default='present', choices=['absent', 'present']), - type=dict(type='str', choices=['host', 'user']), - use_agent=dict(type='bool', default=False), - valid_at=dict(type='str'), - valid_from=dict(type='str'), - valid_to=dict(type='str'), - ignore_timestamps=dict(type='bool', default=False), + signature_algorithm=dict( + type="str", choices=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"] + ), + signing_key=dict(type="path"), + serial_number=dict(type="int"), + state=dict(type="str", default="present", choices=["absent", "present"]), + type=dict(type="str", choices=["host", "user"]), + use_agent=dict(type="bool", default=False), + valid_at=dict(type="str"), + valid_from=dict(type="str"), + valid_to=dict(type="str"), + ignore_timestamps=dict(type="bool", default=False), ), supports_check_mode=True, add_file_common_args=True, - required_if=[('state', 'present', ['type', 'signing_key', 'public_key', 'valid_from', 'valid_to'])], + required_if=[ + ( + "state", + "present", + ["type", "signing_key", "public_key", "valid_from", "valid_to"], + ) + ], ) Certificate(module).execute() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssh_keypair.py b/plugins/modules/openssh_keypair.py index 5867ce5b..76f978bd 100644 --- a/plugins/modules/openssh_keypair.py +++ b/plugins/modules/openssh_keypair.py @@ -213,33 +213,48 @@ def main(): module = AnsibleModule( argument_spec=dict( - state=dict(type='str', default='present', choices=['present', 'absent']), - size=dict(type='int'), - type=dict(type='str', default='rsa', choices=['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519']), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - comment=dict(type='str'), - regenerate=dict( - type='str', - default='partial_idempotence', - choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always'] + state=dict(type="str", default="present", choices=["present", "absent"]), + size=dict(type="int"), + type=dict( + type="str", + default="rsa", + choices=["rsa", "dsa", "rsa1", "ecdsa", "ed25519"], ), - passphrase=dict(type='str', no_log=True), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + comment=dict(type="str"), + regenerate=dict( + type="str", + default="partial_idempotence", + choices=[ + "never", + "fail", + "partial_idempotence", + "full_idempotence", + "always", + ], + ), + passphrase=dict(type="str", no_log=True), private_key_format=dict( - type='str', - default='auto', + type="str", + default="auto", no_log=False, - choices=['auto', 'pkcs1', 'pkcs8', 'ssh']), - backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'opensshbin']) + choices=["auto", "pkcs1", "pkcs8", "ssh"], + ), + backend=dict( + type="str", + default="auto", + choices=["auto", "cryptography", "opensshbin"], + ), ), supports_check_mode=True, add_file_common_args=True, ) - keypair = select_backend(module, module.params['backend'])[1] + keypair = select_backend(module, module.params["backend"])[1] keypair.execute() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_csr.py b/plugins/modules/openssl_csr.py index db91fa5c..c018c91c 100644 --- a/plugins/modules/openssl_csr.py +++ b/plugins/modules/openssl_csr.py @@ -266,21 +266,21 @@ class CertificateSigningRequestModule(OpenSSLObject): def __init__(self, module, module_backend): super(CertificateSigningRequestModule, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) self.module_backend = module_backend - self.return_content = module.params['return_content'] + self.return_content = module.params["return_content"] - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None self.module_backend.set_existing(load_file_if_exists(self.path, module)) def generate(self, module): - '''Generate the certificate signing request.''' + """Generate the certificate signing request.""" if self.force or self.module_backend.needs_regeneration(): if not self.check_mode: self.module_backend.generate_csr() @@ -291,10 +291,12 @@ class CertificateSigningRequestModule(OpenSSLObject): self.changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True else: - self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = module.set_fs_attributes_if_different( + file_args, self.changed + ) def remove(self, module): self.module_backend.set_existing(None) @@ -303,43 +305,53 @@ class CertificateSigningRequestModule(OpenSSLObject): super(CertificateSigningRequestModule, self).remove(module) def dump(self): - '''Serialize the object into a dictionary.''' + """Serialize the object into a dictionary.""" result = self.module_backend.dump(include_csr=self.return_content) - result.update({ - 'filename': self.path, - 'changed': self.changed, - }) + result.update( + { + "filename": self.path, + "changed": self.changed, + } + ) if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file return result def main(): argument_spec = get_csr_argument_spec() - argument_spec.argument_spec.update(dict( - state=dict(type='str', default='present', choices=['absent', 'present']), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - return_content=dict(type='bool', default=False), - )) - argument_spec.required_if.extend([('state', 'present', rof, True) for rof in argument_spec.required_one_of]) + argument_spec.argument_spec.update( + dict( + state=dict(type="str", default="present", choices=["absent", "present"]), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + return_content=dict(type="bool", default=False), + ) + ) + argument_spec.required_if.extend( + [("state", "present", rof, True) for rof in argument_spec.required_one_of] + ) argument_spec.required_one_of = [] module = argument_spec.create_ansible_module( add_file_common_args=True, supports_check_mode=True, ) - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): - module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir) + module.fail_json( + name=base_dir, + msg="The directory %s does not exist or the file is not a directory" + % base_dir, + ) try: - backend = module.params['select_crypto_backend'] + backend = module.params["select_crypto_backend"] backend, module_backend = select_backend(module, backend) csr = CertificateSigningRequestModule(module, module_backend) - if module.params['state'] == 'present': + if module.params["state"] == "present": csr.generate(module) else: csr.remove(module) diff --git a/plugins/modules/openssl_csr_info.py b/plugins/modules/openssl_csr_info.py index f4b3dc69..ad24715b 100644 --- a/plugins/modules/openssl_csr_info.py +++ b/plugins/modules/openssl_csr_info.py @@ -325,30 +325,34 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac def main(): module = AnsibleModule( argument_spec=dict( - path=dict(type='path'), - content=dict(type='str'), - name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']), - ), - required_one_of=( - ['path', 'content'], - ), - mutually_exclusive=( - ['path', 'content'], + path=dict(type="path"), + content=dict(type="str"), + name_encoding=dict( + type="str", default="ignore", choices=["ignore", "idna", "unicode"] + ), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography"] + ), ), + required_one_of=(["path", "content"],), + mutually_exclusive=(["path", "content"],), supports_check_mode=True, ) - if module.params['content'] is not None: - data = module.params['content'].encode('utf-8') + if module.params["content"] is not None: + data = module.params["content"].encode("utf-8") else: try: - with open(module.params['path'], 'rb') as f: + with open(module.params["path"], "rb") as f: data = f.read() except (IOError, OSError) as e: - module.fail_json(msg='Error while reading CSR file from disk: {0}'.format(e)) + module.fail_json( + msg="Error while reading CSR file from disk: {0}".format(e) + ) - backend, module_backend = select_backend(module, module.params['select_crypto_backend'], data, validate_signature=True) + backend, module_backend = select_backend( + module, module.params["select_crypto_backend"], data, validate_signature=True + ) try: result = module_backend.get_info() diff --git a/plugins/modules/openssl_csr_pipe.py b/plugins/modules/openssl_csr_pipe.py index a29893a3..fbc5f232 100644 --- a/plugins/modules/openssl_csr_pipe.py +++ b/plugins/modules/openssl_csr_pipe.py @@ -151,46 +151,50 @@ class CertificateSigningRequestModule(object): self.module = module self.module_backend = module_backend self.changed = False - if module.params['content'] is not None: - self.module_backend.set_existing(module.params['content'].encode('utf-8')) + if module.params["content"] is not None: + self.module_backend.set_existing(module.params["content"].encode("utf-8")) def generate(self, module): - '''Generate the certificate signing request.''' + """Generate the certificate signing request.""" if self.module_backend.needs_regeneration(): if not self.check_mode: self.module_backend.generate_csr() else: self.module.deprecate( - 'Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0' - ' to behave the same as without check mode. You can get that behavior right now' - ' by adding `check_mode: false` to the openssl_csr_pipe task. If you think this' - ' breaks your use-case of this module, please create an issue in the' - ' community.crypto repository', - version='3.0.0', - collection_name='community.crypto', + "Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0" + " to behave the same as without check mode. You can get that behavior right now" + " by adding `check_mode: false` to the openssl_csr_pipe task. If you think this" + " breaks your use-case of this module, please create an issue in the" + " community.crypto repository", + version="3.0.0", + collection_name="community.crypto", ) self.changed = True def dump(self): - '''Serialize the object into a dictionary.''' + """Serialize the object into a dictionary.""" result = self.module_backend.dump(include_csr=True) - result.update({ - 'changed': self.changed, - }) + result.update( + { + "changed": self.changed, + } + ) return result def main(): argument_spec = get_csr_argument_spec() - argument_spec.argument_spec.update(dict( - content=dict(type='str'), - )) + argument_spec.argument_spec.update( + dict( + content=dict(type="str"), + ) + ) module = argument_spec.create_ansible_module( supports_check_mode=True, ) try: - backend = module.params['select_crypto_backend'] + backend = module.params["select_crypto_backend"] backend, module_backend = select_backend(module, backend) csr = CertificateSigningRequestModule(module, module_backend) diff --git a/plugins/modules/openssl_dhparam.py b/plugins/modules/openssl_dhparam.py index 89ab69c9..685d6f30 100644 --- a/plugins/modules/openssl_dhparam.py +++ b/plugins/modules/openssl_dhparam.py @@ -153,7 +153,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '2.0' +MINIMAL_CRYPTOGRAPHY_VERSION = "2.0" CRYPTOGRAPHY_IMP_ERR = None try: @@ -162,6 +162,7 @@ try: import cryptography.hazmat.backends import cryptography.hazmat.primitives.asymmetric.dh import cryptography.hazmat.primitives.serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -177,14 +178,14 @@ class DHParameterError(Exception): class DHParameterBase(object): def __init__(self, module): - self.state = module.params['state'] - self.path = module.params['path'] - self.size = module.params['size'] - self.force = module.params['force'] + self.state = module.params["state"] + self.path = module.params["path"] + self.size = module.params["size"] + self.force = module.params["force"] self.changed = False - self.return_content = module.params['return_content'] + self.return_content = module.params["return_content"] - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None @abc.abstractmethod @@ -232,7 +233,7 @@ class DHParameterBase(object): def _check_fs_attributes(self, module): """Checks (and changes if not in check mode!) fs attributes""" file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): return False return not module.set_fs_attributes_if_different(file_args, False) @@ -240,15 +241,15 @@ class DHParameterBase(object): """Serialize the object into a dictionary.""" result = { - 'size': self.size, - 'filename': self.path, - 'changed': self.changed, + "size": self.size, + "filename": self.path, + "changed": self.changed, } if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file if self.return_content: content = load_file_if_exists(self.path, ignore_errors=True) - result['dhparams'] = content.decode('utf-8') if content else None + result["dhparams"] = content.decode("utf-8") if content else None return result @@ -271,7 +272,7 @@ class DHParameterOpenSSL(DHParameterBase): def __init__(self, module): super(DHParameterOpenSSL, self).__init__(module) - self.openssl_bin = module.get_bin_path('openssl', True) + self.openssl_bin = module.get_bin_path("openssl", True) def _do_generate(self, module): """Actually generate the DH params.""" @@ -280,7 +281,7 @@ class DHParameterOpenSSL(DHParameterBase): os.close(fd) module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit # openssl dhparam -out - command = [self.openssl_bin, 'dhparam', '-out', tmpsrc, str(self.size)] + command = [self.openssl_bin, "dhparam", "-out", tmpsrc, str(self.size)] rc, dummy, err = module.run_command(command, check_rc=False) if rc != 0: raise DHParameterError(to_native(err)) @@ -293,7 +294,15 @@ class DHParameterOpenSSL(DHParameterBase): def _check_params_valid(self, module): """Check if the params are in the correct state""" - command = [self.openssl_bin, 'dhparam', '-check', '-text', '-noout', '-in', self.path] + command = [ + self.openssl_bin, + "dhparam", + "-check", + "-text", + "-noout", + "-in", + self.path, + ] rc, out, err = module.run_command(command, check_rc=False) result = to_native(out) if rc != 0: @@ -342,9 +351,11 @@ class DHParameterCryptography(DHParameterBase): """Check if the params are in the correct state""" # Load parameters try: - with open(self.path, 'rb') as f: + with open(self.path, "rb") as f: data = f.read() - params = cryptography.hazmat.primitives.serialization.load_pem_parameters(data, backend=self.crypto_backend) + params = cryptography.hazmat.primitives.serialization.load_pem_parameters( + data, backend=self.crypto_backend + ) except Exception: return False # Check parameters @@ -357,56 +368,70 @@ def main(): module = AnsibleModule( argument_spec=dict( - state=dict(type='str', default='present', choices=['absent', 'present']), - size=dict(type='int', default=4096), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'openssl']), - return_content=dict(type='bool', default=False), + state=dict(type="str", default="present", choices=["absent", "present"]), + size=dict(type="int", default=4096), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography", "openssl"] + ), + return_content=dict(type="bool", default=False), ), supports_check_mode=True, add_file_common_args=True, ) - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg="The directory '%s' does not exist or the file is not a directory" % base_dir + msg="The directory '%s' does not exist or the file is not a directory" + % base_dir, ) - if module.params['state'] == 'present': - backend = module.params['select_crypto_backend'] - if backend == 'auto': + if module.params["state"] == "present": + backend = module.params["select_crypto_backend"] + if backend == "auto": # Detection what is possible - can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) - can_use_openssl = module.get_bin_path('openssl', False) is not None + can_use_cryptography = ( + CRYPTOGRAPHY_FOUND + and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + ) + can_use_openssl = module.get_bin_path("openssl", False) is not None # First try cryptography, then OpenSSL if can_use_cryptography: - backend = 'cryptography' + backend = "cryptography" elif can_use_openssl: - backend = 'openssl' + backend = "openssl" # Success? - if backend == 'auto': - module.fail_json(msg=("Cannot detect either the required Python library cryptography (>= {0}) " - "or the OpenSSL binary openssl").format(MINIMAL_CRYPTOGRAPHY_VERSION)) + if backend == "auto": + module.fail_json( + msg=( + "Cannot detect either the required Python library cryptography (>= {0}) " + "or the OpenSSL binary openssl" + ).format(MINIMAL_CRYPTOGRAPHY_VERSION) + ) - if backend == 'openssl': + if backend == "openssl": dhparam = DHParameterOpenSSL(module) - elif backend == 'cryptography': + elif 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, + ) dhparam = DHParameterCryptography(module) else: - raise AssertionError('Internal error: unknown backend') + raise AssertionError("Internal error: unknown backend") if module.check_mode: result = dhparam.dump() - result['changed'] = module.params['force'] or not dhparam.check(module) + result["changed"] = module.params["force"] or not dhparam.check(module) module.exit_json(**result) try: @@ -418,10 +443,10 @@ def main(): if module.check_mode: result = dhparam.dump() - result['changed'] = os.path.exists(module.params['path']) + result["changed"] = os.path.exists(module.params["path"]) module.exit_json(**result) - if os.path.exists(module.params['path']): + if os.path.exists(module.params["path"]): try: dhparam.remove(module) except Exception as exc: @@ -432,5 +457,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_pkcs12.py b/plugins/modules/openssl_pkcs12.py index 255d67f4..8590ce29 100644 --- a/plugins/modules/openssl_pkcs12.py +++ b/plugins/modules/openssl_pkcs12.py @@ -314,9 +314,9 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '3.0' -MINIMAL_PYOPENSSL_VERSION = '0.15' -MAXIMAL_PYOPENSSL_VERSION = '23.3.0' +MINIMAL_CRYPTOGRAPHY_VERSION = "3.0" +MINIMAL_PYOPENSSL_VERSION = "0.15" +MAXIMAL_PYOPENSSL_VERSION = "23.3.0" PYOPENSSL_IMP_ERR = None try: @@ -325,6 +325,7 @@ try: from OpenSSL.crypto import ( load_pkcs12 as _load_pkcs12, # this got removed in pyOpenSSL 23.3.0 ) + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) except (ImportError, AttributeError): PYOPENSSL_IMP_ERR = traceback.format_exc() @@ -339,6 +340,7 @@ try: from cryptography.hazmat.primitives.serialization.pkcs12 import ( serialize_key_and_certificates, ) + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -352,7 +354,9 @@ try: from cryptography.hazmat.primitives.serialization.pkcs12 import PBES # Try to build encryption builder for compatibility2022 - serialization.PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC).hmac_hash(hashes.SHA1()) + serialization.PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm( + PBES.PBESv1SHA1And3KeyTripleDESCBC + ).hmac_hash(hashes.SHA1()) except Exception: CRYPTOGRAPHY_COMPATIBILITY2022_ERR = traceback.format_exc() CRYPTOGRAPHY_HAS_COMPATIBILITY2022 = False @@ -361,12 +365,15 @@ else: def load_certificate_set(filename, backend): - ''' + """ Load list of concatenated PEM files, and return a list of parsed certificates. - ''' - with open(filename, 'rb') as f: - data = f.read().decode('utf-8') - return [load_certificate(None, content=cert.encode('utf-8'), backend=backend) for cert in split_pem_list(data)] + """ + with open(filename, "rb") as f: + data = f.read().decode("utf-8") + return [ + load_certificate(None, content=cert.encode("utf-8"), backend=backend) + for cert in split_pem_list(data) + ] class PkcsError(OpenSSLObjectError): @@ -376,40 +383,42 @@ class PkcsError(OpenSSLObjectError): class Pkcs(OpenSSLObject): def __init__(self, module, backend, iter_size_default=2048): super(Pkcs, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) self.backend = backend - self.action = module.params['action'] - self.other_certificates = module.params['other_certificates'] - self.other_certificates_parse_all = module.params['other_certificates_parse_all'] - self.other_certificates_content = module.params['other_certificates_content'] - self.certificate_path = module.params['certificate_path'] - self.certificate_content = module.params['certificate_content'] - self.friendly_name = module.params['friendly_name'] - self.iter_size = module.params['iter_size'] or iter_size_default - self.maciter_size = module.params['maciter_size'] or 1 - self.encryption_level = module.params['encryption_level'] - self.passphrase = module.params['passphrase'] + self.action = module.params["action"] + self.other_certificates = module.params["other_certificates"] + self.other_certificates_parse_all = module.params[ + "other_certificates_parse_all" + ] + self.other_certificates_content = module.params["other_certificates_content"] + self.certificate_path = module.params["certificate_path"] + self.certificate_content = module.params["certificate_content"] + self.friendly_name = module.params["friendly_name"] + self.iter_size = module.params["iter_size"] or iter_size_default + self.maciter_size = module.params["maciter_size"] or 1 + self.encryption_level = module.params["encryption_level"] + self.passphrase = module.params["passphrase"] self.pkcs12 = None - self.privatekey_passphrase = module.params['privatekey_passphrase'] - self.privatekey_path = module.params['privatekey_path'] - self.privatekey_content = module.params['privatekey_content'] + self.privatekey_passphrase = module.params["privatekey_passphrase"] + self.privatekey_path = module.params["privatekey_path"] + self.privatekey_content = module.params["privatekey_content"] self.pkcs12_bytes = None - self.return_content = module.params['return_content'] - self.src = module.params['src'] + self.return_content = module.params["return_content"] + self.src = module.params["src"] - if module.params['mode'] is None: - module.params['mode'] = '0400' + if module.params["mode"] is None: + module.params["mode"] = "0400" - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None if self.certificate_path is not None: try: - with open(self.certificate_path, 'rb') as fh: + with open(self.certificate_path, "rb") as fh: self.certificate_content = fh.read() except (IOError, OSError) as exc: raise PkcsError(exc) @@ -418,7 +427,7 @@ class Pkcs(OpenSSLObject): if self.privatekey_path is not None: try: - with open(self.privatekey_path, 'rb') as fh: + with open(self.privatekey_path, "rb") as fh: self.privatekey_content = fh.read() except (IOError, OSError) as exc: raise PkcsError(exc) @@ -430,17 +439,27 @@ class Pkcs(OpenSSLObject): filenames = list(self.other_certificates) self.other_certificates = [] for other_cert_bundle in filenames: - self.other_certificates.extend(load_certificate_set(other_cert_bundle, self.backend)) + self.other_certificates.extend( + load_certificate_set(other_cert_bundle, self.backend) + ) else: self.other_certificates = [ - load_certificate(other_cert, backend=self.backend) for other_cert in self.other_certificates + load_certificate(other_cert, backend=self.backend) + for other_cert in self.other_certificates ] elif self.other_certificates_content: certs = self.other_certificates_content if self.other_certificates_parse_all: - certs = list(itertools.chain.from_iterable(split_pem_list(content) for content in certs)) + certs = list( + itertools.chain.from_iterable( + split_pem_list(content) for content in certs + ) + ) self.other_certificates = [ - load_certificate(None, content=to_bytes(other_cert), backend=self.backend) for other_cert in certs + load_certificate( + None, content=to_bytes(other_cert), backend=self.backend + ) + for other_cert in certs ] @abc.abstractmethod @@ -476,7 +495,12 @@ class Pkcs(OpenSSLObject): def _check_pkey_passphrase(): if self.privatekey_passphrase: try: - load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend) + load_privatekey( + None, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) except OpenSSLObjectError: return False return True @@ -484,28 +508,39 @@ class Pkcs(OpenSSLObject): if not state_and_perms: return state_and_perms - if os.path.exists(self.path) and module.params['action'] == 'export': + if os.path.exists(self.path) and module.params["action"] == "export": self.generate_bytes(module) # ignore result self.src = self.path try: - pkcs12_privatekey, pkcs12_certificate, pkcs12_other_certificates, pkcs12_friendly_name = self.parse() + ( + pkcs12_privatekey, + pkcs12_certificate, + pkcs12_other_certificates, + pkcs12_friendly_name, + ) = self.parse() except OpenSSLObjectError: return False - if (pkcs12_privatekey is not None) and (self.privatekey_content is not None): + if (pkcs12_privatekey is not None) and ( + self.privatekey_content is not None + ): expected_pkey = self._dump_privatekey(self.pkcs12) if pkcs12_privatekey != expected_pkey: return False elif bool(pkcs12_privatekey) != bool(self.privatekey_content): return False - if (pkcs12_certificate is not None) and (self.certificate_content is not None): + if (pkcs12_certificate is not None) and ( + self.certificate_content is not None + ): expected_cert = self._dump_certificate(self.pkcs12) if pkcs12_certificate != expected_cert: return False elif bool(pkcs12_certificate) != bool(self.certificate_content): return False - if (pkcs12_other_certificates is not None) and (self.other_certificates is not None): + if (pkcs12_other_certificates is not None) and ( + self.other_certificates is not None + ): expected_other_certs = self._dump_other_certificates(self.pkcs12) if set(pkcs12_other_certificates) != set(expected_other_certs): return False @@ -516,18 +551,28 @@ class Pkcs(OpenSSLObject): # This check is required because pyOpenSSL will not return a friendly name # if the private key is not set in the file friendly_name = self._get_friendly_name(self.pkcs12) - if ((friendly_name is not None) and (pkcs12_friendly_name is not None)): + if (friendly_name is not None) and (pkcs12_friendly_name is not None): if friendly_name != pkcs12_friendly_name: return False elif bool(friendly_name) != bool(pkcs12_friendly_name): return False - elif module.params['action'] == 'parse' and os.path.exists(self.src) and os.path.exists(self.path): + elif ( + module.params["action"] == "parse" + and os.path.exists(self.src) + and os.path.exists(self.path) + ): try: pkey, cert, other_certs, friendly_name = self.parse() except OpenSSLObjectError: return False expected_content = to_bytes( - ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) + "".join( + [ + to_native(pem) + for pem in [pkey, cert] + other_certs + if pem is not None + ] + ) ) dumped_content = load_file_if_exists(self.path, ignore_errors=True) if expected_content != dumped_content: @@ -541,16 +586,18 @@ class Pkcs(OpenSSLObject): """Serialize the object into a dictionary.""" result = { - 'filename': self.path, + "filename": self.path, } if self.privatekey_path: - result['privatekey_path'] = self.privatekey_path + result["privatekey_path"] = self.privatekey_path if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file if self.return_content: if self.pkcs12_bytes is None: self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True) - result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None + result["pkcs12"] = ( + base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None + ) return result @@ -563,7 +610,7 @@ class Pkcs(OpenSSLObject): """Read PKCS#12 file.""" try: - with open(self.src, 'rb') as pkcs12_fh: + with open(self.src, "rb") as pkcs12_fh: pkcs12_content = pkcs12_fh.read() return self.parse_bytes(pkcs12_content) except IOError as exc: @@ -583,9 +630,11 @@ class Pkcs(OpenSSLObject): class PkcsPyOpenSSL(Pkcs): def __init__(self, module): - super(PkcsPyOpenSSL, self).__init__(module, 'pyopenssl') - if self.encryption_level != 'auto': - module.fail_json(msg='The PyOpenSSL backend only supports encryption_level = auto') + super(PkcsPyOpenSSL, self).__init__(module, "pyopenssl") + if self.encryption_level != "auto": + module.fail_json( + msg="The PyOpenSSL backend only supports encryption_level = auto" + ) def generate_bytes(self, module): """Generate PKCS#12 file archive.""" @@ -595,7 +644,11 @@ class PkcsPyOpenSSL(Pkcs): self.pkcs12.set_ca_certificates(self.other_certificates) if self.certificate_content: - self.pkcs12.set_certificate(load_certificate(None, content=self.certificate_content, backend=self.backend)) + self.pkcs12.set_certificate( + load_certificate( + None, content=self.certificate_content, backend=self.backend + ) + ) if self.friendly_name: self.pkcs12.set_friendlyname(to_bytes(self.friendly_name)) @@ -603,7 +656,13 @@ class PkcsPyOpenSSL(Pkcs): if self.privatekey_content: try: self.pkcs12.set_privatekey( - load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend)) + load_privatekey( + None, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + ) except OpenSSLBadPassphraseError as exc: raise PkcsError(exc) @@ -620,8 +679,10 @@ class PkcsPyOpenSSL(Pkcs): crt = crypto.dump_certificate(crypto.FILETYPE_PEM, crt) other_certs = [] if p12.get_ca_certificates() is not None: - other_certs = [crypto.dump_certificate(crypto.FILETYPE_PEM, - other_cert) for other_cert in p12.get_ca_certificates()] + other_certs = [ + crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert) + for other_cert in p12.get_ca_certificates() + ] friendly_name = p12.get_friendlyname() @@ -651,43 +712,60 @@ class PkcsPyOpenSSL(Pkcs): class PkcsCryptography(Pkcs): def __init__(self, module): - super(PkcsCryptography, self).__init__(module, 'cryptography', iter_size_default=50000) - if self.encryption_level == 'compatibility2022' and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022: + super(PkcsCryptography, self).__init__( + module, "cryptography", iter_size_default=50000 + ) + if ( + self.encryption_level == "compatibility2022" + and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022 + ): module.fail_json( - msg='The installed cryptography version does not support encryption_level = compatibility2022.' - ' You need cryptography >= 38.0.0 and support for SHA1', - exception=CRYPTOGRAPHY_COMPATIBILITY2022_ERR) + msg="The installed cryptography version does not support encryption_level = compatibility2022." + " You need cryptography >= 38.0.0 and support for SHA1", + exception=CRYPTOGRAPHY_COMPATIBILITY2022_ERR, + ) def generate_bytes(self, module): """Generate PKCS#12 file archive.""" pkey = None if self.privatekey_content: try: - pkey = load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend) + pkey = load_privatekey( + None, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) except OpenSSLBadPassphraseError as exc: raise PkcsError(exc) cert = None if self.certificate_content: - cert = load_certificate(None, content=self.certificate_content, backend=self.backend) + cert = load_certificate( + None, content=self.certificate_content, backend=self.backend + ) - friendly_name = to_bytes(self.friendly_name) if self.friendly_name is not None else None + friendly_name = ( + to_bytes(self.friendly_name) if self.friendly_name is not None else None + ) # Store fake object which can be used to retrieve the components back self.pkcs12 = (pkey, cert, self.other_certificates, friendly_name) if not self.passphrase: encryption = serialization.NoEncryption() - elif self.encryption_level == 'compatibility2022': + elif self.encryption_level == "compatibility2022": encryption = ( - serialization.PrivateFormat.PKCS12.encryption_builder(). - kdf_rounds(self.iter_size). - key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC). - hmac_hash(hashes.SHA1()). - build(to_bytes(self.passphrase)) + serialization.PrivateFormat.PKCS12.encryption_builder() + .kdf_rounds(self.iter_size) + .key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC) + .hmac_hash(hashes.SHA1()) + .build(to_bytes(self.passphrase)) ) else: - encryption = serialization.BestAvailableEncryption(to_bytes(self.passphrase)) + encryption = serialization.BestAvailableEncryption( + to_bytes(self.passphrase) + ) return serialize_key_and_certificates( friendly_name, @@ -699,8 +777,9 @@ class PkcsCryptography(Pkcs): def parse_bytes(self, pkcs12_content): try: - private_key, certificate, additional_certificates, friendly_name = parse_pkcs12( - pkcs12_content, self.passphrase) + private_key, certificate, additional_certificates, friendly_name = ( + parse_pkcs12(pkcs12_content, self.passphrase) + ) pkey = None if private_key is not None: @@ -730,103 +809,133 @@ class PkcsCryptography(Pkcs): # self.pkcs12 = (pkey, cert, self.other_certificates, self.friendly_name) def _dump_privatekey(self, pkcs12): - return pkcs12[0].private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ) if pkcs12[0] else None + return ( + pkcs12[0].private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + if pkcs12[0] + else None + ) def _dump_certificate(self, pkcs12): return pkcs12[1].public_bytes(serialization.Encoding.PEM) if pkcs12[1] else None def _dump_other_certificates(self, pkcs12): - return [other_cert.public_bytes(serialization.Encoding.PEM) for other_cert in pkcs12[2]] + return [ + other_cert.public_bytes(serialization.Encoding.PEM) + for other_cert in pkcs12[2] + ] def _get_friendly_name(self, pkcs12): return pkcs12[3] 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) + ) can_use_pyopenssl = ( - PYOPENSSL_FOUND and - PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) and - PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION) + PYOPENSSL_FOUND + and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + and PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION) ) # If no restrictions are provided, first try cryptography, then pyOpenSSL if ( - (module.params['iter_size'] is not None and module.params['encryption_level'] != 'compatibility2022') - or module.params['maciter_size'] is not None - ): + module.params["iter_size"] is not None + and module.params["encryption_level"] != "compatibility2022" + ) or module.params["maciter_size"] is not None: # If iter_size (for encryption_level != compatibility2022) or maciter_size is specified, use pyOpenSSL backend - backend = 'pyopenssl' + backend = "pyopenssl" elif can_use_cryptography: - backend = 'cryptography' + backend = "cryptography" elif can_use_pyopenssl: - backend = 'pyopenssl' + backend = "pyopenssl" # Success? - if backend == 'auto': - module.fail_json(msg=("Cannot detect any of the required Python libraries " - "cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})").format( - MINIMAL_CRYPTOGRAPHY_VERSION, - MINIMAL_PYOPENSSL_VERSION, - MAXIMAL_PYOPENSSL_VERSION)) + if backend == "auto": + module.fail_json( + msg=( + "Cannot detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})" + ).format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION, + MAXIMAL_PYOPENSSL_VERSION, + ) + ) - if backend == 'pyopenssl': + if backend == "pyopenssl": if not PYOPENSSL_FOUND: msg = missing_required_lib( - 'pyOpenSSL >= {0}, < {1}'.format(MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION) + "pyOpenSSL >= {0}, < {1}".format( + MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION + ) ) module.fail_json(msg=msg, exception=PYOPENSSL_IMP_ERR) - module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', - version='3.0.0', collection_name='community.crypto') + module.deprecate( + "The module is using the PyOpenSSL backend. This backend has been deprecated", + version="3.0.0", + collection_name="community.crypto", + ) return backend, PkcsPyOpenSSL(module) - elif backend == 'cryptography': + elif 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, PkcsCryptography(module) else: - raise ValueError('Unsupported value for backend: {0}'.format(backend)) + raise ValueError("Unsupported value for backend: {0}".format(backend)) def main(): argument_spec = dict( - action=dict(type='str', default='export', choices=['export', 'parse']), - other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']), - other_certificates_parse_all=dict(type='bool', default=False), - other_certificates_content=dict(type='list', elements='str'), - certificate_path=dict(type='path'), - certificate_content=dict(type='str'), - force=dict(type='bool', default=False), - friendly_name=dict(type='str', aliases=['name']), - encryption_level=dict(type='str', choices=['auto', 'compatibility2022'], default='auto'), - iter_size=dict(type='int'), - maciter_size=dict(type='int'), - passphrase=dict(type='str', no_log=True), - path=dict(type='path', required=True), - privatekey_passphrase=dict(type='str', no_log=True), - privatekey_path=dict(type='path'), - privatekey_content=dict(type='str', no_log=True), - state=dict(type='str', default='present', choices=['absent', 'present']), - src=dict(type='path'), - backup=dict(type='bool', default=False), - return_content=dict(type='bool', default=False), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + action=dict(type="str", default="export", choices=["export", "parse"]), + other_certificates=dict( + type="list", elements="path", aliases=["ca_certificates"] + ), + other_certificates_parse_all=dict(type="bool", default=False), + other_certificates_content=dict(type="list", elements="str"), + certificate_path=dict(type="path"), + certificate_content=dict(type="str"), + force=dict(type="bool", default=False), + friendly_name=dict(type="str", aliases=["name"]), + encryption_level=dict( + type="str", choices=["auto", "compatibility2022"], default="auto" + ), + iter_size=dict(type="int"), + maciter_size=dict(type="int"), + passphrase=dict(type="str", no_log=True), + path=dict(type="path", required=True), + privatekey_passphrase=dict(type="str", no_log=True), + privatekey_path=dict(type="path"), + privatekey_content=dict(type="str", no_log=True), + state=dict(type="str", default="present", choices=["absent", "present"]), + src=dict(type="path"), + backup=dict(type="bool", default=False), + return_content=dict(type="bool", default=False), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography", "pyopenssl"] + ), ) required_if = [ - ['action', 'parse', ['src']], + ["action", "parse", ["src"]], ] mutually_exclusive = [ - ['privatekey_path', 'privatekey_content'], - ['certificate_path', 'certificate_content'], - ['other_certificates', 'other_certificates_content'], + ["privatekey_path", "privatekey_content"], + ["certificate_path", "certificate_content"], + ["other_certificates", "other_certificates_content"], ] module = AnsibleModule( @@ -837,62 +946,69 @@ def main(): supports_check_mode=True, ) - backend, pkcs12 = select_backend(module, module.params['select_crypto_backend']) + backend, pkcs12 = select_backend(module, module.params["select_crypto_backend"]) - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg="The directory '%s' does not exist or the path is not a directory" % base_dir + msg="The directory '%s' does not exist or the path is not a directory" + % base_dir, ) try: changed = False - if module.params['state'] == 'present': + if module.params["state"] == "present": if module.check_mode: result = pkcs12.dump() - result['changed'] = module.params['force'] or not pkcs12.check(module) + result["changed"] = module.params["force"] or not pkcs12.check(module) module.exit_json(**result) - if not pkcs12.check(module, perms_required=False) or module.params['force']: - if module.params['action'] == 'export': - if not module.params['friendly_name']: - module.fail_json(msg='Friendly_name is required') + if not pkcs12.check(module, perms_required=False) or module.params["force"]: + if module.params["action"] == "export": + if not module.params["friendly_name"]: + module.fail_json(msg="Friendly_name is required") pkcs12_content = pkcs12.generate_bytes(module) pkcs12.write(module, pkcs12_content, 0o600) changed = True else: pkey, cert, other_certs, friendly_name = pkcs12.parse() - dump_content = ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) + dump_content = "".join( + [ + to_native(pem) + for pem in [pkey, cert] + other_certs + if pem is not None + ] + ) pkcs12.write(module, to_bytes(dump_content)) changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): changed = True elif module.set_fs_attributes_if_different(file_args, changed): changed = True else: if module.check_mode: result = pkcs12.dump() - result['changed'] = os.path.exists(module.params['path']) + result["changed"] = os.path.exists(module.params["path"]) module.exit_json(**result) - if os.path.exists(module.params['path']): + if os.path.exists(module.params["path"]): pkcs12.remove(module) changed = True result = pkcs12.dump() - result['changed'] = changed - if os.path.exists(module.params['path']): - file_mode = "%04o" % stat.S_IMODE(os.stat(module.params['path']).st_mode) - result['mode'] = file_mode + result["changed"] = changed + if os.path.exists(module.params["path"]): + file_mode = "%04o" % stat.S_IMODE(os.stat(module.params["path"]).st_mode) + result["mode"] = file_mode module.exit_json(**result) except OpenSSLObjectError as exc: module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_privatekey.py b/plugins/modules/openssl_privatekey.py index 3f345e7a..9f696eb7 100644 --- a/plugins/modules/openssl_privatekey.py +++ b/plugins/modules/openssl_privatekey.py @@ -182,21 +182,21 @@ class PrivateKeyModule(OpenSSLObject): def __init__(self, module, module_backend): super(PrivateKeyModule, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], + module.params["path"], + module.params["state"], + module.params["force"], module.check_mode, ) self.module_backend = module_backend - self.return_content = module.params['return_content'] + self.return_content = module.params["return_content"] if self.force: - module_backend.regenerate = 'always' + module_backend.regenerate = "always" - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None - if module.params['mode'] is None: - module.params['mode'] = '0600' + if module.params["mode"] is None: + module.params["mode"] = "0600" module_backend.set_existing(load_file_if_exists(self.path, module)) @@ -227,10 +227,12 @@ class PrivateKeyModule(OpenSSLObject): self.changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True else: - self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = module.set_fs_attributes_if_different( + file_args, self.changed + ) def remove(self, module): self.module_backend.set_existing(None) @@ -242,10 +244,10 @@ class PrivateKeyModule(OpenSSLObject): """Serialize the object into a dictionary.""" result = self.module_backend.dump(include_key=self.return_content) - result['filename'] = self.path - result['changed'] = self.changed + result["filename"] = self.path + result["changed"] = self.changed if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file return result @@ -253,34 +255,37 @@ class PrivateKeyModule(OpenSSLObject): def main(): argument_spec = get_privatekey_argument_spec() - argument_spec.argument_spec.update(dict( - state=dict(type='str', default='present', choices=['present', 'absent']), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - return_content=dict(type='bool', default=False), - )) + argument_spec.argument_spec.update( + dict( + state=dict(type="str", default="present", choices=["present", "absent"]), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + return_content=dict(type="bool", default=False), + ) + ) module = argument_spec.create_ansible_module( supports_check_mode=True, add_file_common_args=True, ) - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg='The directory %s does not exist or the file is not a directory' % base_dir + msg="The directory %s does not exist or the file is not a directory" + % base_dir, ) backend, module_backend = select_backend( module=module, - backend=module.params['select_crypto_backend'], + backend=module.params["select_crypto_backend"], ) try: private_key = PrivateKeyModule(module, module_backend) - if private_key.state == 'present': + if private_key.state == "present": private_key.generate(module) else: private_key.remove(module) @@ -291,5 +296,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_privatekey_convert.py b/plugins/modules/openssl_privatekey_convert.py index 9f90157f..521858e9 100644 --- a/plugins/modules/openssl_privatekey_convert.py +++ b/plugins/modules/openssl_privatekey_convert.py @@ -86,19 +86,19 @@ from ansible_collections.community.crypto.plugins.module_utils.io import ( class PrivateKeyConvertModule(OpenSSLObject): def __init__(self, module, module_backend): super(PrivateKeyConvertModule, self).__init__( - module.params['dest_path'], - 'present', + module.params["dest_path"], + "present", False, module.check_mode, ) self.module_backend = module_backend - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None - module.params['path'] = module.params['dest_path'] - if module.params['mode'] is None: - module.params['mode'] = '0600' + module.params["path"] = module.params["dest_path"] + if module.params["mode"] is None: + module.params["mode"] = "0600" module_backend.set_existing_destination(load_file_if_exists(self.path, module)) @@ -115,18 +115,20 @@ class PrivateKeyConvertModule(OpenSSLObject): self.changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True else: - self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = module.set_fs_attributes_if_different( + file_args, self.changed + ) def dump(self): """Serialize the object into a dictionary.""" result = self.module_backend.dump() - result['changed'] = self.changed + result["changed"] = self.changed if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file return result @@ -134,20 +136,23 @@ class PrivateKeyConvertModule(OpenSSLObject): def main(): argument_spec = get_privatekey_argument_spec() - argument_spec.argument_spec.update(dict( - dest_path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - )) + argument_spec.argument_spec.update( + dict( + dest_path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + ) + ) module = argument_spec.create_ansible_module( supports_check_mode=True, add_file_common_args=True, ) - base_dir = os.path.dirname(module.params['dest_path']) or '.' + base_dir = os.path.dirname(module.params["dest_path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg='The directory %s does not exist or the file is not a directory' % base_dir + msg="The directory %s does not exist or the file is not a directory" + % base_dir, ) module_backend = select_backend(module=module) @@ -163,5 +168,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_privatekey_info.py b/plugins/modules/openssl_privatekey_info.py index 9a3413e4..9452d205 100644 --- a/plugins/modules/openssl_privatekey_info.py +++ b/plugins/modules/openssl_privatekey_info.py @@ -219,19 +219,17 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac def main(): module = AnsibleModule( argument_spec=dict( - path=dict(type='path'), - content=dict(type='str', no_log=True), - passphrase=dict(type='str', no_log=True), - return_private_key_data=dict(type='bool', default=False), - check_consistency=dict(type='bool', default=False), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']), - ), - required_one_of=( - ['path', 'content'], - ), - mutually_exclusive=( - ['path', 'content'], + path=dict(type="path"), + content=dict(type="str", no_log=True), + passphrase=dict(type="str", no_log=True), + return_private_key_data=dict(type="bool", default=False), + check_consistency=dict(type="bool", default=False), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography"] + ), ), + required_one_of=(["path", "content"],), + mutually_exclusive=(["path", "content"],), supports_check_mode=True, ) @@ -241,24 +239,28 @@ def main(): key_is_consistent=None, ) - if module.params['content'] is not None: - data = module.params['content'].encode('utf-8') + if module.params["content"] is not None: + data = module.params["content"].encode("utf-8") else: try: - with open(module.params['path'], 'rb') as f: + with open(module.params["path"], "rb") as f: data = f.read() except (IOError, OSError) as e: - module.fail_json(msg='Error while reading private key file from disk: {0}'.format(e), **result) + module.fail_json( + msg="Error while reading private key file from disk: {0}".format(e), + **result + ) - result['can_load_key'] = True + result["can_load_key"] = True backend, module_backend = select_backend( module, - module.params['select_crypto_backend'], + module.params["select_crypto_backend"], data, - passphrase=module.params['passphrase'], - return_private_key_data=module.params['return_private_key_data'], - check_consistency=module.params['check_consistency']) + passphrase=module.params["passphrase"], + return_private_key_data=module.params["return_private_key_data"], + check_consistency=module.params["check_consistency"], + ) try: result.update(module_backend.get_info()) diff --git a/plugins/modules/openssl_publickey.py b/plugins/modules/openssl_publickey.py index 904777cc..ee356bdc 100644 --- a/plugins/modules/openssl_publickey.py +++ b/plugins/modules/openssl_publickey.py @@ -216,14 +216,15 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' -MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '1.4' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3" +MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = "1.4" CRYPTOGRAPHY_IMP_ERR = None try: import cryptography from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization as crypto_serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -240,25 +241,25 @@ class PublicKey(OpenSSLObject): def __init__(self, module, backend): super(PublicKey, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) self.module = module - self.format = module.params['format'] - self.privatekey_path = module.params['privatekey_path'] - self.privatekey_content = module.params['privatekey_content'] + self.format = module.params["format"] + 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.privatekey_content = self.privatekey_content.encode("utf-8") + self.privatekey_passphrase = module.params["privatekey_passphrase"] self.privatekey = None self.publickey_bytes = None - self.return_content = module.params['return_content'] + self.return_content = module.params["return_content"] self.fingerprint = {} self.backend = backend - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None self.diff_before = self._get_info(None) @@ -269,9 +270,12 @@ class PublicKey(OpenSSLObject): return dict() result = dict(can_parse_key=False) try: - result.update(get_publickey_info( - self.module, self.backend, content=data, prefer_one_fingerprint=True)) - result['can_parse_key'] = True + result.update( + get_publickey_info( + self.module, self.backend, content=data, prefer_one_fingerprint=True + ) + ) + result["can_parse_key"] = True except PublicKeyParseError as exc: result.update(exc.result) except Exception: @@ -283,18 +287,18 @@ class PublicKey(OpenSSLObject): path=self.privatekey_path, content=self.privatekey_content, passphrase=self.privatekey_passphrase, - backend=self.backend + backend=self.backend, ) - if self.backend == 'cryptography': - if self.format == 'OpenSSH': + if self.backend == "cryptography": + if self.format == "OpenSSH": return self.privatekey.public_key().public_bytes( crypto_serialization.Encoding.OpenSSH, - crypto_serialization.PublicFormat.OpenSSH + crypto_serialization.PublicFormat.OpenSSH, ) else: return self.privatekey.public_key().public_bytes( crypto_serialization.Encoding.PEM, - crypto_serialization.PublicFormat.SubjectPublicKeyInfo + crypto_serialization.PublicFormat.SubjectPublicKeyInfo, ) def generate(self, module): @@ -302,7 +306,7 @@ class PublicKey(OpenSSLObject): if self.privatekey_content is None and not os.path.exists(self.privatekey_path): raise PublicKeyError( - 'The private key %s does not exist' % self.privatekey_path + "The private key %s does not exist" % self.privatekey_path ) if not self.check(module, perms_required=False) or self.force: @@ -329,7 +333,7 @@ class PublicKey(OpenSSLObject): backend=self.backend, ) file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True elif module.set_fs_attributes_if_different(file_args, False): self.changed = True @@ -340,28 +344,34 @@ class PublicKey(OpenSSLObject): state_and_perms = super(PublicKey, self).check(module, perms_required) def _check_privatekey(): - if self.privatekey_content is None and not os.path.exists(self.privatekey_path): + if self.privatekey_content is None and not os.path.exists( + self.privatekey_path + ): return False try: - with open(self.path, 'rb') as public_key_fh: + with open(self.path, "rb") as public_key_fh: publickey_content = public_key_fh.read() self.diff_before = self.diff_after = self._get_info(publickey_content) if self.return_content: self.publickey_bytes = publickey_content - if self.backend == 'cryptography': - if self.format == 'OpenSSH': + if self.backend == "cryptography": + if self.format == "OpenSSH": # Read and dump public key. Makes sure that the comment is stripped off. - current_publickey = crypto_serialization.load_ssh_public_key(publickey_content, backend=default_backend()) + current_publickey = crypto_serialization.load_ssh_public_key( + publickey_content, backend=default_backend() + ) publickey_content = current_publickey.public_bytes( crypto_serialization.Encoding.OpenSSH, - crypto_serialization.PublicFormat.OpenSSH + crypto_serialization.PublicFormat.OpenSSH, ) else: - current_publickey = crypto_serialization.load_pem_public_key(publickey_content, backend=default_backend()) + current_publickey = crypto_serialization.load_pem_public_key( + publickey_content, backend=default_backend() + ) publickey_content = current_publickey.public_bytes( crypto_serialization.Encoding.PEM, - crypto_serialization.PublicFormat.SubjectPublicKeyInfo + crypto_serialization.PublicFormat.SubjectPublicKeyInfo, ) except Exception: return False @@ -387,20 +397,24 @@ class PublicKey(OpenSSLObject): """Serialize the object into a dictionary.""" result = { - 'privatekey': self.privatekey_path, - 'filename': self.path, - 'format': self.format, - 'changed': self.changed, - 'fingerprint': self.fingerprint, + "privatekey": self.privatekey_path, + "filename": self.path, + "format": self.format, + "changed": self.changed, + "fingerprint": self.fingerprint, } if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file if self.return_content: if self.publickey_bytes is None: - self.publickey_bytes = load_file_if_exists(self.path, ignore_errors=True) - result['publickey'] = self.publickey_bytes.decode('utf-8') if self.publickey_bytes else None + self.publickey_bytes = load_file_if_exists( + self.path, ignore_errors=True + ) + result["publickey"] = ( + self.publickey_bytes.decode("utf-8") if self.publickey_bytes else None + ) - result['diff'] = dict( + result["diff"] = dict( before=self.diff_before, after=self.diff_after, ) @@ -412,72 +426,87 @@ def main(): module = AnsibleModule( argument_spec=dict( - state=dict(type='str', default='present', choices=['present', 'absent']), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - privatekey_path=dict(type='path'), - privatekey_content=dict(type='str', no_log=True), - format=dict(type='str', default='PEM', choices=['OpenSSH', 'PEM']), - privatekey_passphrase=dict(type='str', no_log=True), - backup=dict(type='bool', default=False), - select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'), - return_content=dict(type='bool', default=False), + state=dict(type="str", default="present", choices=["present", "absent"]), + force=dict(type="bool", default=False), + path=dict(type="path", required=True), + privatekey_path=dict(type="path"), + privatekey_content=dict(type="str", no_log=True), + format=dict(type="str", default="PEM", choices=["OpenSSH", "PEM"]), + privatekey_passphrase=dict(type="str", no_log=True), + backup=dict(type="bool", default=False), + select_crypto_backend=dict( + type="str", choices=["auto", "cryptography"], default="auto" + ), + return_content=dict(type="bool", default=False), ), supports_check_mode=True, add_file_common_args=True, - required_if=[('state', 'present', ['privatekey_path', 'privatekey_content'], True)], - mutually_exclusive=( - ['privatekey_path', 'privatekey_content'], - ), + required_if=[ + ("state", "present", ["privatekey_path", "privatekey_content"], True) + ], + mutually_exclusive=(["privatekey_path", "privatekey_content"],), ) minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION - if module.params['format'] == 'OpenSSH': + if module.params["format"] == "OpenSSH": minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH - backend = module.params['select_crypto_backend'] - if backend == 'auto': + backend = module.params["select_crypto_backend"] + 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 == "auto": + module.fail_json( + msg=( + "Cannot detect the required Python library " "cryptography (>= {0})" + ).format(minimal_cryptography_version) + ) - if module.params['format'] == 'OpenSSH' and backend != 'cryptography': + if module.params["format"] == "OpenSSH" and backend != "cryptography": module.fail_json(msg="Format OpenSSH requires the cryptography backend.") - 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, + ) - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg="The directory '%s' does not exist or the file is not a directory" % base_dir + msg="The directory '%s' does not exist or the file is not a directory" + % base_dir, ) try: public_key = PublicKey(module, backend) - if public_key.state == 'present': + if public_key.state == "present": if module.check_mode: result = public_key.dump() - result['changed'] = module.params['force'] or not public_key.check(module) + result["changed"] = module.params["force"] or not public_key.check( + module + ) module.exit_json(**result) public_key.generate(module) else: if module.check_mode: result = public_key.dump() - result['changed'] = os.path.exists(module.params['path']) + result["changed"] = os.path.exists(module.params["path"]) module.exit_json(**result) public_key.remove(module) @@ -488,5 +517,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_publickey_info.py b/plugins/modules/openssl_publickey_info.py index cc753ef8..dbe205b0 100644 --- a/plugins/modules/openssl_publickey_info.py +++ b/plugins/modules/openssl_publickey_info.py @@ -170,16 +170,14 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac def main(): module = AnsibleModule( argument_spec=dict( - path=dict(type='path'), - content=dict(type='str', no_log=True), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']), - ), - required_one_of=( - ['path', 'content'], - ), - mutually_exclusive=( - ['path', 'content'], + path=dict(type="path"), + content=dict(type="str", no_log=True), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography"] + ), ), + required_one_of=(["path", "content"],), + mutually_exclusive=(["path", "content"],), supports_check_mode=True, ) @@ -189,19 +187,21 @@ def main(): key_is_consistent=None, ) - if module.params['content'] is not None: - data = module.params['content'].encode('utf-8') + if module.params["content"] is not None: + data = module.params["content"].encode("utf-8") else: try: - with open(module.params['path'], 'rb') as f: + with open(module.params["path"], "rb") as f: data = f.read() except (IOError, OSError) as e: - module.fail_json(msg='Error while reading public key file from disk: {0}'.format(e), **result) + module.fail_json( + msg="Error while reading public key file from disk: {0}".format(e), + **result + ) backend, module_backend = select_backend( - module, - module.params['select_crypto_backend'], - data) + module, module.params["select_crypto_backend"], data + ) try: result.update(module_backend.get_info()) diff --git a/plugins/modules/openssl_signature.py b/plugins/modules/openssl_signature.py index c022813b..c34c736f 100644 --- a/plugins/modules/openssl_signature.py +++ b/plugins/modules/openssl_signature.py @@ -113,13 +113,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.4" CRYPTOGRAPHY_IMP_ERR = None try: import cryptography import cryptography.hazmat.primitives.asymmetric.padding import cryptography.hazmat.primitives.hashes + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -147,19 +148,19 @@ class SignatureBase(OpenSSLObject): def __init__(self, module, backend): super(SignatureBase, self).__init__( - path=module.params['path'], - state='present', + path=module.params["path"], + state="present", force=False, - check_mode=module.check_mode + check_mode=module.check_mode, ) self.backend = backend - self.privatekey_path = module.params['privatekey_path'] - self.privatekey_content = module.params['privatekey_content'] + 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.privatekey_content = self.privatekey_content.encode("utf-8") + self.privatekey_passphrase = module.params["privatekey_passphrase"] def generate(self): # Empty method because OpenSSLObject wants this @@ -196,31 +197,50 @@ class SignatureCryptography(SignatureBase): signature = None if CRYPTOGRAPHY_HAS_DSA_SIGN: - if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey): + if isinstance( + private_key, + cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey, + ): signature = private_key.sign(_in, _hash) if CRYPTOGRAPHY_HAS_EC_SIGN: - if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): - signature = private_key.sign(_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash)) + if isinstance( + private_key, + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey, + ): + signature = private_key.sign( + _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash) + ) if CRYPTOGRAPHY_HAS_ED25519_SIGN: - if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + if isinstance( + private_key, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey, + ): signature = private_key.sign(_in) if CRYPTOGRAPHY_HAS_ED448_SIGN: - if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + if isinstance( + private_key, + cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey, + ): signature = private_key.sign(_in) if CRYPTOGRAPHY_HAS_RSA_SIGN: - if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + if isinstance( + private_key, + cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey, + ): signature = private_key.sign(_in, _padding, _hash) if signature is None: self.module.fail_json( - msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION) + msg="Unsupported key type. Your cryptography version is {0}".format( + CRYPTOGRAPHY_VERSION + ) ) - result['signature'] = base64.b64encode(signature) + result["signature"] = base64.b64encode(signature) return result except Exception as e: @@ -230,45 +250,53 @@ class SignatureCryptography(SignatureBase): def main(): module = AnsibleModule( argument_spec=dict( - privatekey_path=dict(type='path'), - privatekey_content=dict(type='str', no_log=True), - privatekey_passphrase=dict(type='str', no_log=True), - path=dict(type='path', required=True), - select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'), - ), - mutually_exclusive=( - ['privatekey_path', 'privatekey_content'], - ), - required_one_of=( - ['privatekey_path', 'privatekey_content'], + privatekey_path=dict(type="path"), + privatekey_content=dict(type="str", no_log=True), + privatekey_passphrase=dict(type="str", no_log=True), + path=dict(type="path", required=True), + select_crypto_backend=dict( + type="str", choices=["auto", "cryptography"], default="auto" + ), ), + mutually_exclusive=(["privatekey_path", "privatekey_content"],), + required_one_of=(["privatekey_path", "privatekey_content"],), supports_check_mode=True, ) - if not os.path.isfile(module.params['path']): + if not os.path.isfile(module.params["path"]): module.fail_json( - name=module.params['path'], - msg='The file {0} does not exist'.format(module.params['path']) + name=module.params["path"], + msg="The file {0} does not exist".format(module.params["path"]), ) - backend = module.params['select_crypto_backend'] - if backend == 'auto': + backend = module.params["select_crypto_backend"] + 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 == "auto": + module.fail_json( + msg=( + "Cannot detect the required Python library " "cryptography (>= {0})" + ).format(MINIMAL_CRYPTOGRAPHY_VERSION) + ) try: - 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, + ) _sign = SignatureCryptography(module, backend) result = _sign.run() @@ -278,5 +306,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/openssl_signature_info.py b/plugins/modules/openssl_signature_info.py index 21c15474..15049e27 100644 --- a/plugins/modules/openssl_signature_info.py +++ b/plugins/modules/openssl_signature_info.py @@ -102,13 +102,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.4" CRYPTOGRAPHY_IMP_ERR = None try: import cryptography import cryptography.hazmat.primitives.asymmetric.padding import cryptography.hazmat.primitives.hashes + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -136,19 +137,19 @@ class SignatureInfoBase(OpenSSLObject): def __init__(self, module, backend): super(SignatureInfoBase, self).__init__( - path=module.params['path'], - state='present', + path=module.params["path"], + state="present", force=False, - check_mode=module.check_mode + check_mode=module.check_mode, ) self.backend = backend - self.signature = module.params['signature'] - self.certificate_path = module.params['certificate_path'] - self.certificate_content = module.params['certificate_content'] + self.signature = module.params["signature"] + self.certificate_path = module.params["certificate_path"] + self.certificate_content = module.params["certificate_content"] if self.certificate_content is not None: - self.certificate_content = self.certificate_content.encode('utf-8') + self.certificate_content = self.certificate_content.encode("utf-8") def generate(self): # Empty method because OpenSSLObject wants this @@ -187,7 +188,10 @@ class SignatureInfoCryptography(SignatureInfoBase): if CRYPTOGRAPHY_HAS_DSA_SIGN: try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey): + if isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey, + ): public_key.verify(_signature, _in, _hash) verified = True valid = True @@ -197,8 +201,15 @@ class SignatureInfoCryptography(SignatureInfoBase): if CRYPTOGRAPHY_HAS_EC_SIGN: try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): - public_key.verify(_signature, _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash)) + if isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, + ): + public_key.verify( + _signature, + _in, + cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash), + ) verified = True valid = True except cryptography.exceptions.InvalidSignature: @@ -207,7 +218,10 @@ class SignatureInfoCryptography(SignatureInfoBase): if CRYPTOGRAPHY_HAS_ED25519_SIGN: try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey): + if isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey, + ): public_key.verify(_signature, _in) verified = True valid = True @@ -217,7 +231,10 @@ class SignatureInfoCryptography(SignatureInfoBase): if CRYPTOGRAPHY_HAS_ED448_SIGN: try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey): + if isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey, + ): public_key.verify(_signature, _in) verified = True valid = True @@ -227,7 +244,10 @@ class SignatureInfoCryptography(SignatureInfoBase): if CRYPTOGRAPHY_HAS_RSA_SIGN: try: - if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + if isinstance( + public_key, + cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey, + ): public_key.verify(_signature, _in, _padding, _hash) verified = True valid = True @@ -237,9 +257,11 @@ class SignatureInfoCryptography(SignatureInfoBase): if not verified: self.module.fail_json( - msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION) + msg="Unsupported key type. Your cryptography version is {0}".format( + CRYPTOGRAPHY_VERSION + ) ) - result['valid'] = valid + result["valid"] = valid return result except Exception as e: @@ -249,45 +271,54 @@ class SignatureInfoCryptography(SignatureInfoBase): def main(): module = AnsibleModule( argument_spec=dict( - certificate_path=dict(type='path'), - certificate_content=dict(type='str'), - path=dict(type='path', required=True), - signature=dict(type='str', required=True), - select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'), - ), - mutually_exclusive=( - ['certificate_path', 'certificate_content'], - ), - required_one_of=( - ['certificate_path', 'certificate_content'], + certificate_path=dict(type="path"), + certificate_content=dict(type="str"), + path=dict(type="path", required=True), + signature=dict(type="str", required=True), + select_crypto_backend=dict( + type="str", choices=["auto", "cryptography"], default="auto" + ), ), + mutually_exclusive=(["certificate_path", "certificate_content"],), + required_one_of=(["certificate_path", "certificate_content"],), supports_check_mode=True, ) - if not os.path.isfile(module.params['path']): + if not os.path.isfile(module.params["path"]): module.fail_json( - name=module.params['path'], - msg='The file {0} does not exist'.format(module.params['path']) + name=module.params["path"], + msg="The file {0} does not exist".format(module.params["path"]), ) - backend = module.params['select_crypto_backend'] - if backend == 'auto': + backend = module.params["select_crypto_backend"] + 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 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) + ) try: - 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, + ) _sign = SignatureInfoCryptography(module, backend) result = _sign.run() @@ -297,5 +328,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/x509_certificate.py b/plugins/modules/x509_certificate.py index f476826a..de4c93ce 100644 --- a/plugins/modules/x509_certificate.py +++ b/plugins/modules/x509_certificate.py @@ -266,14 +266,14 @@ from ansible_collections.community.crypto.plugins.module_utils.io import ( class CertificateAbsent(OpenSSLObject): def __init__(self, module): super(CertificateAbsent, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) self.module = module - self.return_content = module.params['return_content'] - self.backup = module.params['backup'] + self.return_content = module.params["return_content"] + self.backup = module.params["backup"] self.backup_file = None def generate(self, module): @@ -286,31 +286,32 @@ class CertificateAbsent(OpenSSLObject): def dump(self, check_mode=False): result = { - 'changed': self.changed, - 'filename': self.path, - 'privatekey': self.module.params['privatekey_path'], - 'csr': self.module.params['csr_path'] + "changed": self.changed, + "filename": self.path, + "privatekey": self.module.params["privatekey_path"], + "csr": self.module.params["csr_path"], } if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file if self.return_content: - result['certificate'] = None + result["certificate"] = None return result class GenericCertificate(OpenSSLObject): """Retrieve a certificate using the given module backend.""" + def __init__(self, module, module_backend): super(GenericCertificate, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) self.module = module - self.return_content = module.params['return_content'] - self.backup = module.params['backup'] + self.return_content = module.params["return_content"] + self.backup = module.params["backup"] self.backup_file = None self.module_backend = module_backend @@ -327,23 +328,30 @@ class GenericCertificate(OpenSSLObject): self.changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True else: - self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = module.set_fs_attributes_if_different( + file_args, self.changed + ) def check(self, module, perms_required=True): """Ensure the resource is in its desired state.""" - return super(GenericCertificate, self).check(module, perms_required) and not self.module_backend.needs_regeneration() + return ( + super(GenericCertificate, self).check(module, perms_required) + and not self.module_backend.needs_regeneration() + ) def dump(self, check_mode=False): result = self.module_backend.dump(include_certificate=self.return_content) - result.update({ - 'changed': self.changed, - 'filename': self.path, - }) + result.update( + { + "changed": self.changed, + "filename": self.path, + } + ) if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file return result @@ -353,46 +361,49 @@ def main(): add_entrust_provider_to_argument_spec(argument_spec) add_ownca_provider_to_argument_spec(argument_spec) add_selfsigned_provider_to_argument_spec(argument_spec) - argument_spec.argument_spec.update(dict( - state=dict(type='str', default='present', choices=['present', 'absent']), - path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - return_content=dict(type='bool', default=False), - )) - argument_spec.required_if.append(['state', 'present', ['provider']]) + argument_spec.argument_spec.update( + dict( + state=dict(type="str", default="present", choices=["present", "absent"]), + path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + return_content=dict(type="bool", default=False), + ) + ) + argument_spec.required_if.append(["state", "present", ["provider"]]) module = argument_spec.create_ansible_module( add_file_common_args=True, supports_check_mode=True, ) try: - if module.params['state'] == 'absent': + if module.params["state"] == "absent": certificate = CertificateAbsent(module) if module.check_mode: result = certificate.dump(check_mode=True) - result['changed'] = os.path.exists(module.params['path']) + result["changed"] = os.path.exists(module.params["path"]) module.exit_json(**result) certificate.remove(module) else: - base_dir = os.path.dirname(module.params['path']) or '.' + base_dir = os.path.dirname(module.params["path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg='The directory %s does not exist or the file is not a directory' % base_dir + msg="The directory %s does not exist or the file is not a directory" + % base_dir, ) - provider = module.params['provider'] + provider = module.params["provider"] provider_map = { - 'acme': AcmeCertificateProvider, - 'entrust': EntrustCertificateProvider, - 'ownca': OwnCACertificateProvider, - 'selfsigned': SelfSignedCertificateProvider, + "acme": AcmeCertificateProvider, + "entrust": EntrustCertificateProvider, + "ownca": OwnCACertificateProvider, + "selfsigned": SelfSignedCertificateProvider, } - backend = module.params['select_crypto_backend'] + backend = module.params["select_crypto_backend"] module_backend = select_backend(module, backend, provider_map[provider]()) certificate = GenericCertificate(module, module_backend) certificate.generate(module) diff --git a/plugins/modules/x509_certificate_convert.py b/plugins/modules/x509_certificate_convert.py index 58a9f82f..48907eb2 100644 --- a/plugins/modules/x509_certificate_convert.py +++ b/plugins/modules/x509_certificate_convert.py @@ -137,7 +137,7 @@ from ansible_collections.community.crypto.plugins.module_utils.io import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.6" CRYPTOGRAPHY_IMP_ERR = None try: @@ -152,14 +152,22 @@ else: def parse_certificate(input, strict=False): - input_format = 'pem' if identify_pem_format(input) else 'der' - if input_format == 'pem': + input_format = "pem" if identify_pem_format(input) else "der" + if input_format == "pem": pems = split_pem_list(to_text(input)) if len(pems) > 1 and strict: - raise ValueError('The input contains {count} PEM objects, expecting only one since strict=true'.format(count=len(pems))) + raise ValueError( + "The input contains {count} PEM objects, expecting only one since strict=true".format( + count=len(pems) + ) + ) pem_header_type, content = extract_pem(pems[0], strict=strict) - if strict and pem_header_type not in ('CERTIFICATE', 'X509 CERTIFICATE'): - raise ValueError('type is {type!r}, expecting CERTIFICATE or X509 CERTIFICATE'.format(type=pem_header_type)) + if strict and pem_header_type not in ("CERTIFICATE", "X509 CERTIFICATE"): + raise ValueError( + "type is {type!r}, expecting CERTIFICATE or X509 CERTIFICATE".format( + type=pem_header_type + ) + ) input = base64.b64decode(content) else: pem_header_type = None @@ -169,66 +177,81 @@ def parse_certificate(input, strict=False): class X509CertificateConvertModule(OpenSSLObject): def __init__(self, module): super(X509CertificateConvertModule, self).__init__( - module.params['dest_path'], - 'present', + module.params["dest_path"], + "present", False, module.check_mode, ) - self.src_path = module.params['src_path'] - self.src_content = module.params['src_content'] - self.src_content_base64 = module.params['src_content_base64'] + self.src_path = module.params["src_path"] + self.src_content = module.params["src_content"] + self.src_content_base64 = module.params["src_content_base64"] if self.src_content is not None: self.input = to_bytes(self.src_content) if self.src_content_base64: try: self.input = base64.b64decode(self.input) except Exception as exc: - module.fail_json(msg='Cannot Base64 decode src_content: {exc}'.format(exc=exc)) + module.fail_json( + msg="Cannot Base64 decode src_content: {exc}".format(exc=exc) + ) else: try: - with open(self.src_path, 'rb') as f: + with open(self.src_path, "rb") as f: self.input = f.read() except Exception as exc: - module.fail_json(msg='Failure while reading file {fn}: {exc}'.format(fn=self.src_path, exc=exc)) + module.fail_json( + msg="Failure while reading file {fn}: {exc}".format( + fn=self.src_path, exc=exc + ) + ) - self.format = module.params['format'] - self.strict = module.params['strict'] - self.wanted_pem_type = 'CERTIFICATE' + self.format = module.params["format"] + self.strict = module.params["strict"] + self.wanted_pem_type = "CERTIFICATE" try: - self.input, self.input_format, dummy = parse_certificate(self.input, strict=self.strict) + self.input, self.input_format, dummy = parse_certificate( + self.input, strict=self.strict + ) except Exception as exc: - module.fail_json(msg='Error while parsing PEM: {exc}'.format(exc=exc)) + module.fail_json(msg="Error while parsing PEM: {exc}".format(exc=exc)) - if module.params['verify_cert_parsable']: + if module.params["verify_cert_parsable"]: self.verify_cert_parsable(module) - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None - module.params['path'] = self.path + module.params["path"] = self.path self.dest_content = load_file_if_exists(self.path, module) self.dest_content_format = None self.dest_content_pem_type = None if self.dest_content is not None: try: - self.dest_content, self.dest_content_format, self.dest_content_pem_type = parse_certificate( - self.dest_content, strict=True) + ( + self.dest_content, + self.dest_content_format, + self.dest_content_pem_type, + ) = parse_certificate(self.dest_content, strict=True) except Exception: pass def verify_cert_parsable(self, module): if not CRYPTOGRAPHY_FOUND: module.fail_json( - msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + msg=missing_required_lib( + "cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION) + ), exception=CRYPTOGRAPHY_IMP_ERR, ) try: load_der_x509_certificate(self.input, default_backend()) except Exception as exc: - module.fail_json(msg='Error while parsing certificate: {exc}'.format(exc=exc)) + module.fail_json( + msg="Error while parsing certificate: {exc}".format(exc=exc) + ) def needs_conversion(self): if self.dest_content is None or self.dest_content_format is None: @@ -237,18 +260,20 @@ class X509CertificateConvertModule(OpenSSLObject): return True if self.input != self.dest_content: return True - if self.format == 'pem' and self.dest_content_pem_type != self.wanted_pem_type: + if self.format == "pem" and self.dest_content_pem_type != self.wanted_pem_type: return True return False def get_dest_certificate(self): - if self.format == 'der': + if self.format == "der": return self.input data = to_bytes(base64.b64encode(self.input)) - lines = [to_bytes('{0}{1}{2}'.format(PEM_START, self.wanted_pem_type, PEM_END))] - lines += [data[i:i + 64] for i in range(0, len(data), 64)] - lines.append(to_bytes('{0}{1}{2}\n'.format(PEM_END_START, self.wanted_pem_type, PEM_END))) - return b'\n'.join(lines) + lines = [to_bytes("{0}{1}{2}".format(PEM_START, self.wanted_pem_type, PEM_END))] + lines += [data[i : i + 64] for i in range(0, len(data), 64)] + lines.append( + to_bytes("{0}{1}{2}\n".format(PEM_END_START, self.wanted_pem_type, PEM_END)) + ) + return b"\n".join(lines) def generate(self, module): """Do conversion.""" @@ -262,10 +287,12 @@ class X509CertificateConvertModule(OpenSSLObject): self.changed = True file_args = module.load_file_common_arguments(module.params) - if module.check_file_absent_if_check_mode(file_args['path']): + if module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True else: - self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + self.changed = module.set_fs_attributes_if_different( + file_args, self.changed + ) def dump(self): """Serialize the object into a dictionary.""" @@ -273,35 +300,36 @@ class X509CertificateConvertModule(OpenSSLObject): changed=self.changed, ) if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file return result def main(): argument_spec = dict( - src_path=dict(type='path'), - src_content=dict(type='str'), - src_content_base64=dict(type='bool', default=False), - format=dict(type='str', required=True, choices=['pem', 'der']), - strict=dict(type='bool', default=False), - dest_path=dict(type='path', required=True), - backup=dict(type='bool', default=False), - verify_cert_parsable=dict(type='bool', default=False), + src_path=dict(type="path"), + src_content=dict(type="str"), + src_content_base64=dict(type="bool", default=False), + format=dict(type="str", required=True, choices=["pem", "der"]), + strict=dict(type="bool", default=False), + dest_path=dict(type="path", required=True), + backup=dict(type="bool", default=False), + verify_cert_parsable=dict(type="bool", default=False), ) module = AnsibleModule( argument_spec, supports_check_mode=True, add_file_common_args=True, - required_one_of=[('src_path', 'src_content')], - mutually_exclusive=[('src_path', 'src_content')], + required_one_of=[("src_path", "src_content")], + mutually_exclusive=[("src_path", "src_content")], ) - base_dir = os.path.dirname(module.params['dest_path']) or '.' + base_dir = os.path.dirname(module.params["dest_path"]) or "." if not os.path.isdir(base_dir): module.fail_json( name=base_dir, - msg='The directory %s does not exist or the file is not a directory' % base_dir + msg="The directory %s does not exist or the file is not a directory" + % base_dir, ) try: @@ -313,5 +341,5 @@ def main(): module.fail_json(msg=to_native(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/plugins/modules/x509_certificate_info.py b/plugins/modules/x509_certificate_info.py index 34d276b8..fde0f5b3 100644 --- a/plugins/modules/x509_certificate_info.py +++ b/plugins/modules/x509_certificate_info.py @@ -414,51 +414,61 @@ from ansible_collections.community.crypto.plugins.module_utils.time import ( def main(): module = AnsibleModule( argument_spec=dict( - path=dict(type='path'), - content=dict(type='str'), - valid_at=dict(type='dict'), - name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']), - select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']), - ), - required_one_of=( - ['path', 'content'], - ), - mutually_exclusive=( - ['path', 'content'], + path=dict(type="path"), + content=dict(type="str"), + valid_at=dict(type="dict"), + name_encoding=dict( + type="str", default="ignore", choices=["ignore", "idna", "unicode"] + ), + select_crypto_backend=dict( + type="str", default="auto", choices=["auto", "cryptography"] + ), ), + required_one_of=(["path", "content"],), + mutually_exclusive=(["path", "content"],), supports_check_mode=True, ) - if module.params['content'] is not None: - data = module.params['content'].encode('utf-8') + if module.params["content"] is not None: + data = module.params["content"].encode("utf-8") else: try: - with open(module.params['path'], 'rb') as f: + with open(module.params["path"], "rb") as f: data = f.read() except (IOError, OSError) as e: - module.fail_json(msg='Error while reading certificate file from disk: {0}'.format(e)) + module.fail_json( + msg="Error while reading certificate file from disk: {0}".format(e) + ) - backend, module_backend = select_backend(module, module.params['select_crypto_backend'], data) + backend, module_backend = select_backend( + module, module.params["select_crypto_backend"], data + ) - valid_at = module.params['valid_at'] + valid_at = module.params["valid_at"] if valid_at: for k, v in valid_at.items(): if not isinstance(v, string_types): module.fail_json( - msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v)) + msg="The value for valid_at.{0} must be of type string (got {1})".format( + k, type(v) + ) ) - valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k), with_timezone=CRYPTOGRAPHY_TIMEZONE) + valid_at[k] = get_relative_time_option( + v, "valid_at.{0}".format(k), with_timezone=CRYPTOGRAPHY_TIMEZONE + ) try: - result = module_backend.get_info(der_support_enabled=module.params['content'] is None) + result = module_backend.get_info( + der_support_enabled=module.params["content"] is None + ) not_before = module_backend.get_not_before() not_after = module_backend.get_not_after() - result['valid_at'] = dict() + result["valid_at"] = dict() if valid_at: for k, v in valid_at.items(): - result['valid_at'][k] = not_before <= v <= not_after + result["valid_at"][k] = not_before <= v <= not_after module.exit_json(**result) except OpenSSLObjectError as exc: diff --git a/plugins/modules/x509_certificate_pipe.py b/plugins/modules/x509_certificate_pipe.py index 830cc377..1cde994b 100644 --- a/plugins/modules/x509_certificate_pipe.py +++ b/plugins/modules/x509_certificate_pipe.py @@ -151,13 +151,14 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac class GenericCertificate(object): """Retrieve a certificate using the given module backend.""" + def __init__(self, module, module_backend): self.check_mode = module.check_mode self.module = module self.module_backend = module_backend self.changed = False - if module.params['content'] is not None: - self.module_backend.set_existing(module.params['content'].encode('utf-8')) + if module.params["content"] is not None: + self.module_backend.set_existing(module.params["content"].encode("utf-8")) def generate(self, module): if self.module_backend.needs_regeneration(): @@ -165,46 +166,50 @@ class GenericCertificate(object): self.module_backend.generate_certificate() else: self.module.deprecate( - 'Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0' - ' to behave the same as without check mode. You can get that behavior right now' - ' by adding `check_mode: false` to the x509_certificate_pipe task. If you think this' - ' breaks your use-case of this module, please create an issue in the' - ' community.crypto repository', - version='3.0.0', - collection_name='community.crypto', + "Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0" + " to behave the same as without check mode. You can get that behavior right now" + " by adding `check_mode: false` to the x509_certificate_pipe task. If you think this" + " breaks your use-case of this module, please create an issue in the" + " community.crypto repository", + version="3.0.0", + collection_name="community.crypto", ) self.changed = True def dump(self, check_mode=False): result = self.module_backend.dump(include_certificate=True) - result.update({ - 'changed': self.changed, - }) + result.update( + { + "changed": self.changed, + } + ) return result def main(): argument_spec = get_certificate_argument_spec() - argument_spec.argument_spec['provider']['required'] = True + argument_spec.argument_spec["provider"]["required"] = True add_entrust_provider_to_argument_spec(argument_spec) add_ownca_provider_to_argument_spec(argument_spec) add_selfsigned_provider_to_argument_spec(argument_spec) - argument_spec.argument_spec.update(dict( - content=dict(type='str'), - )) + argument_spec.argument_spec.update( + dict( + content=dict(type="str"), + ) + ) module = argument_spec.create_ansible_module( supports_check_mode=True, ) try: - provider = module.params['provider'] + provider = module.params["provider"] provider_map = { - 'entrust': EntrustCertificateProvider, - 'ownca': OwnCACertificateProvider, - 'selfsigned': SelfSignedCertificateProvider, + "entrust": EntrustCertificateProvider, + "ownca": OwnCACertificateProvider, + "selfsigned": SelfSignedCertificateProvider, } - backend = module.params['select_crypto_backend'] + backend = module.params["select_crypto_backend"] module_backend = select_backend(module, backend, provider_map[provider]()) certificate = GenericCertificate(module, module_backend) certificate.generate(module) diff --git a/plugins/modules/x509_crl.py b/plugins/modules/x509_crl.py index b1007e26..2eb93d0c 100644 --- a/plugins/modules/x509_crl.py +++ b/plugins/modules/x509_crl.py @@ -496,7 +496,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( ) -MINIMAL_CRYPTOGRAPHY_VERSION = '1.2' +MINIMAL_CRYPTOGRAPHY_VERSION = "1.2" CRYPTOGRAPHY_IMP_ERR = None try: @@ -510,6 +510,7 @@ try: NameAttribute, RevokedCertificateBuilder, ) + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) except ImportError: CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() @@ -526,100 +527,122 @@ class CRL(OpenSSLObject): def __init__(self, module): super(CRL, self).__init__( - module.params['path'], - module.params['state'], - module.params['force'], - module.check_mode + module.params["path"], + module.params["state"], + module.params["force"], + module.check_mode, ) - self.format = module.params['format'] + self.format = module.params["format"] - self.update = module.params['crl_mode'] == 'update' - self.ignore_timestamps = module.params['ignore_timestamps'] - self.return_content = module.params['return_content'] - self.name_encoding = module.params['name_encoding'] - self.serial_numbers_format = module.params['serial_numbers'] + self.update = module.params["crl_mode"] == "update" + self.ignore_timestamps = module.params["ignore_timestamps"] + self.return_content = module.params["return_content"] + self.name_encoding = module.params["name_encoding"] + self.serial_numbers_format = module.params["serial_numbers"] self.crl_content = None - self.privatekey_path = module.params['privatekey_path'] - self.privatekey_content = module.params['privatekey_content'] + 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.privatekey_content = self.privatekey_content.encode("utf-8") + self.privatekey_passphrase = module.params["privatekey_passphrase"] try: - if module.params['issuer_ordered']: + if module.params["issuer_ordered"]: self.issuer_ordered = True - self.issuer = parse_ordered_name_field(module.params['issuer_ordered'], 'issuer_ordered') + self.issuer = parse_ordered_name_field( + module.params["issuer_ordered"], "issuer_ordered" + ) else: self.issuer_ordered = False - self.issuer = parse_name_field(module.params['issuer'], 'issuer') + self.issuer = parse_name_field(module.params["issuer"], "issuer") except (TypeError, ValueError) as exc: module.fail_json(msg=to_native(exc)) - self.last_update = get_relative_time_option(module.params['last_update'], 'last_update', with_timezone=CRYPTOGRAPHY_TIMEZONE) - self.next_update = get_relative_time_option(module.params['next_update'], 'next_update', with_timezone=CRYPTOGRAPHY_TIMEZONE) + self.last_update = get_relative_time_option( + module.params["last_update"], + "last_update", + with_timezone=CRYPTOGRAPHY_TIMEZONE, + ) + self.next_update = get_relative_time_option( + module.params["next_update"], + "next_update", + with_timezone=CRYPTOGRAPHY_TIMEZONE, + ) - self.digest = select_message_digest(module.params['digest']) + self.digest = select_message_digest(module.params["digest"]) if self.digest is None: - raise CRLError('The digest "{0}" is not supported'.format(module.params['digest'])) + raise CRLError( + 'The digest "{0}" is not supported'.format(module.params["digest"]) + ) self.module = module self.revoked_certificates = [] - for i, rc in enumerate(module.params['revoked_certificates']): + for i, rc in enumerate(module.params["revoked_certificates"]): result = { - 'serial_number': None, - 'revocation_date': None, - 'issuer': None, - 'issuer_critical': False, - 'reason': None, - 'reason_critical': False, - 'invalidity_date': None, - 'invalidity_date_critical': False, + "serial_number": None, + "revocation_date": None, + "issuer": None, + "issuer_critical": False, + "reason": None, + "reason_critical": False, + "invalidity_date": None, + "invalidity_date_critical": False, } - path_prefix = 'revoked_certificates[{0}].'.format(i) - if rc['path'] is not None or rc['content'] is not None: + path_prefix = "revoked_certificates[{0}].".format(i) + if rc["path"] is not None or rc["content"] is not None: # Load certificate from file or content try: - if rc['content'] is not None: - rc['content'] = rc['content'].encode('utf-8') - cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography') - result['serial_number'] = cryptography_serial_number_of_cert(cert) + if rc["content"] is not None: + rc["content"] = rc["content"].encode("utf-8") + cert = load_certificate( + rc["path"], content=rc["content"], backend="cryptography" + ) + result["serial_number"] = cryptography_serial_number_of_cert(cert) except OpenSSLObjectError as e: - if rc['content'] is not None: + if rc["content"] is not None: module.fail_json( - msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e)) + msg="Cannot parse certificate from {0}content: {1}".format( + path_prefix, to_native(e) + ) ) else: module.fail_json( - msg='Cannot read certificate "{1}" from {0}path: {2}'.format(path_prefix, rc['path'], to_native(e)) + msg='Cannot read certificate "{1}" from {0}path: {2}'.format( + path_prefix, rc["path"], to_native(e) + ) ) else: # Specify serial_number (and potentially issuer) directly - result['serial_number'] = self._parse_serial_number(rc['serial_number'], i) + result["serial_number"] = self._parse_serial_number( + rc["serial_number"], i + ) # All other options - if rc['issuer']: - result['issuer'] = [cryptography_get_name(issuer, 'issuer') for issuer in rc['issuer']] - result['issuer_critical'] = rc['issuer_critical'] - result['revocation_date'] = get_relative_time_option( - rc['revocation_date'], - path_prefix + 'revocation_date', + if rc["issuer"]: + result["issuer"] = [ + cryptography_get_name(issuer, "issuer") for issuer in rc["issuer"] + ] + result["issuer_critical"] = rc["issuer_critical"] + result["revocation_date"] = get_relative_time_option( + rc["revocation_date"], + path_prefix + "revocation_date", with_timezone=CRYPTOGRAPHY_TIMEZONE, ) - if rc['reason']: - result['reason'] = REVOCATION_REASON_MAP[rc['reason']] - result['reason_critical'] = rc['reason_critical'] - if rc['invalidity_date']: - result['invalidity_date'] = get_relative_time_option( - rc['invalidity_date'], - path_prefix + 'invalidity_date', + if rc["reason"]: + result["reason"] = REVOCATION_REASON_MAP[rc["reason"]] + result["reason_critical"] = rc["reason_critical"] + if rc["invalidity_date"]: + result["invalidity_date"] = get_relative_time_option( + rc["invalidity_date"], + path_prefix + "invalidity_date", with_timezone=CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE, ) - result['invalidity_date_critical'] = rc['invalidity_date_critical'] + result["invalidity_date_critical"] = rc["invalidity_date_critical"] self.revoked_certificates.append(result) - self.backup = module.params['backup'] + self.backup = module.params["backup"] self.backup_file = None try: @@ -627,17 +650,17 @@ class CRL(OpenSSLObject): path=self.privatekey_path, content=self.privatekey_content, passphrase=self.privatekey_passphrase, - backend='cryptography' + backend="cryptography", ) except OpenSSLBadPassphraseError as exc: raise CRLError(exc) self.crl = None try: - with open(self.path, 'rb') as f: + with open(self.path, "rb") as f: data = f.read() - self.actual_format = 'pem' if identify_pem_format(data) else 'der' - if self.actual_format == 'pem': + self.actual_format = "pem" if identify_pem_format(data) else "der" + if self.actual_format == "pem": self.crl = x509.load_pem_x509_crl(data, default_backend()) if self.return_content: self.crl_content = data @@ -653,30 +676,36 @@ class CRL(OpenSSLObject): self.diff_after = self.diff_before = self._get_info(data) def _parse_serial_number(self, value, index): - if self.serial_numbers_format == 'integer': + if self.serial_numbers_format == "integer": try: return check_type_int(value) except TypeError as exc: - self.module.fail_json(msg='Error while parsing revoked_certificates[{idx}].serial_number as an integer: {exc}'.format( - idx=index + 1, - exc=to_native(exc), - )) - if self.serial_numbers_format == 'hex-octets': + self.module.fail_json( + msg="Error while parsing revoked_certificates[{idx}].serial_number as an integer: {exc}".format( + idx=index + 1, + exc=to_native(exc), + ) + ) + if self.serial_numbers_format == "hex-octets": try: return parse_serial(check_type_str(value)) except (TypeError, ValueError) as exc: - self.module.fail_json(msg='Error while parsing revoked_certificates[{idx}].serial_number as an colon-separated hex octet string: {exc}'.format( - idx=index + 1, - exc=to_native(exc), - )) - raise RuntimeError('Unexpected value %s of serial_numbers' % (self.serial_numbers_format, )) + self.module.fail_json( + msg="Error while parsing revoked_certificates[{idx}].serial_number as an colon-separated hex octet string: {exc}".format( + idx=index + 1, + exc=to_native(exc), + ) + ) + raise RuntimeError( + "Unexpected value %s of serial_numbers" % (self.serial_numbers_format,) + ) def _get_info(self, data): if data is None: return dict() try: result = get_crl_info(self.module, data) - result['can_parse_crl'] = True + result["can_parse_crl"] = True return result except Exception: return dict(can_parse_crl=False) @@ -688,35 +717,38 @@ class CRL(OpenSSLObject): def _compress_entry(self, entry): issuer = None - if entry['issuer'] is not None: + if entry["issuer"] is not None: # Normalize to IDNA. If this is used-provided, it was already converted to # IDNA (by cryptography_get_name) and thus the `idna` library is present. # If this is coming from cryptography and is not already in IDNA (i.e. ascii), # cryptography < 2.1 must be in use, which depends on `idna`. So this should # not require `idna` except if it was already used by code earlier during # this invocation. - issuer = tuple(cryptography_decode_name(issuer, idn_rewrite='idna') for issuer in entry['issuer']) + issuer = tuple( + cryptography_decode_name(issuer, idn_rewrite="idna") + for issuer in entry["issuer"] + ) if self.ignore_timestamps: # Throw out revocation_date return ( - entry['serial_number'], + entry["serial_number"], issuer, - entry['issuer_critical'], - entry['reason'], - entry['reason_critical'], - entry['invalidity_date'], - entry['invalidity_date_critical'], + entry["issuer_critical"], + entry["reason"], + entry["reason_critical"], + entry["invalidity_date"], + entry["invalidity_date_critical"], ) else: return ( - entry['serial_number'], - entry['revocation_date'], + entry["serial_number"], + entry["revocation_date"], issuer, - entry['issuer_critical'], - entry['reason'], - entry['reason_critical'], - entry['invalidity_date'], - entry['invalidity_date_critical'], + entry["issuer_critical"], + entry["reason"], + entry["reason_critical"], + entry["invalidity_date"], + entry["invalidity_date_critical"], ) def check(self, module, perms_required=True, ignore_conversion=True): @@ -735,13 +767,18 @@ class CRL(OpenSSLObject): if self.next_update != get_next_update(self.crl) and not self.ignore_timestamps: return False if cryptography_key_needs_digest_for_signing(self.privatekey): - if self.crl.signature_hash_algorithm is None or self.digest.name != self.crl.signature_hash_algorithm.name: + if ( + self.crl.signature_hash_algorithm is None + or self.digest.name != self.crl.signature_hash_algorithm.name + ): return False else: if self.crl.signature_hash_algorithm is not None: return False - want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer] + want_issuer = [ + (cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer + ] is_issuer = [(sub.oid, sub.value) for sub in self.crl.issuer] if not self.issuer_ordered: want_issuer = set(want_issuer) @@ -749,7 +786,10 @@ class CRL(OpenSSLObject): if want_issuer != is_issuer: return False - old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl] + old_entries = [ + self._compress_entry(cryptography_decode_revoked_certificate(cert)) + for cert in self.crl + ] new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates] if self.update: # We do not simply use a set so that duplicate entries are treated correctly @@ -772,10 +812,16 @@ class CRL(OpenSSLObject): crl = CertificateRevocationListBuilder() try: - crl = crl.issuer_name(Name([ - NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) - for entry in self.issuer - ])) + crl = crl.issuer_name( + Name( + [ + NameAttribute( + cryptography_name_to_oid(entry[0]), to_text(entry[1]) + ) + for entry in self.issuer + ] + ) + ) except ValueError as e: raise CRLError(e) @@ -783,29 +829,31 @@ class CRL(OpenSSLObject): crl = set_next_update(crl, self.next_update) if self.update and self.crl: - new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates]) + new_entries = set( + [self._compress_entry(entry) for entry in self.revoked_certificates] + ) for entry in self.crl: - decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry)) + decoded_entry = self._compress_entry( + cryptography_decode_revoked_certificate(entry) + ) if decoded_entry not in new_entries: crl = crl.add_revoked_certificate(entry) for entry in self.revoked_certificates: revoked_cert = RevokedCertificateBuilder() - revoked_cert = revoked_cert.serial_number(entry['serial_number']) - revoked_cert = set_revocation_date(revoked_cert, entry['revocation_date']) - if entry['issuer'] is not None: + revoked_cert = revoked_cert.serial_number(entry["serial_number"]) + revoked_cert = set_revocation_date(revoked_cert, entry["revocation_date"]) + if entry["issuer"] is not None: revoked_cert = revoked_cert.add_extension( - x509.CertificateIssuer(entry['issuer']), - entry['issuer_critical'] + x509.CertificateIssuer(entry["issuer"]), entry["issuer_critical"] ) - if entry['reason'] is not None: + if entry["reason"] is not None: revoked_cert = revoked_cert.add_extension( - x509.CRLReason(entry['reason']), - entry['reason_critical'] + x509.CRLReason(entry["reason"]), entry["reason_critical"] ) - if entry['invalidity_date'] is not None: + if entry["invalidity_date"] is not None: revoked_cert = revoked_cert.add_extension( - x509.InvalidityDate(entry['invalidity_date']), - entry['invalidity_date_critical'] + x509.InvalidityDate(entry["invalidity_date"]), + entry["invalidity_date_critical"], ) crl = crl.add_revoked_certificate(revoked_cert.build(backend)) @@ -813,17 +861,23 @@ class CRL(OpenSSLObject): if cryptography_key_needs_digest_for_signing(self.privatekey): digest = self.digest self.crl = crl.sign(self.privatekey, digest, backend=backend) - if self.format == 'pem': + if self.format == "pem": return self.crl.public_bytes(Encoding.PEM) else: return self.crl.public_bytes(Encoding.DER) def generate(self): result = None - if not self.check(self.module, perms_required=False, ignore_conversion=True) or self.force: + if ( + not self.check(self.module, perms_required=False, ignore_conversion=True) + or self.force + ): result = self._generate_crl() - elif not self.check(self.module, perms_required=False, ignore_conversion=False) and self.crl: - if self.format == 'pem': + elif ( + not self.check(self.module, perms_required=False, ignore_conversion=False) + and self.crl + ): + if self.format == "pem": result = self.crl.public_bytes(Encoding.PEM) else: result = self.crl.public_bytes(Encoding.DER) @@ -831,7 +885,7 @@ class CRL(OpenSSLObject): if result is not None: self.diff_after = self._get_info(result) if self.return_content: - if self.format == 'pem': + if self.format == "pem": self.crl_content = result else: self.crl_content = base64.b64encode(result) @@ -841,59 +895,67 @@ class CRL(OpenSSLObject): self.changed = True file_args = self.module.load_file_common_arguments(self.module.params) - if self.module.check_file_absent_if_check_mode(file_args['path']): + if self.module.check_file_absent_if_check_mode(file_args["path"]): self.changed = True elif self.module.set_fs_attributes_if_different(file_args, False): self.changed = True def dump(self, check_mode=False): result = { - 'changed': self.changed, - 'filename': self.path, - 'privatekey': self.privatekey_path, - 'format': self.format, - 'last_update': None, - 'next_update': None, - 'digest': None, - 'issuer_ordered': None, - 'issuer': None, - 'revoked_certificates': [], + "changed": self.changed, + "filename": self.path, + "privatekey": self.privatekey_path, + "format": self.format, + "last_update": None, + "next_update": None, + "digest": None, + "issuer_ordered": None, + "issuer": None, + "revoked_certificates": [], } if self.backup_file: - result['backup_file'] = self.backup_file + result["backup_file"] = self.backup_file if check_mode: - result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT) - result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT) + result["last_update"] = self.last_update.strftime(TIMESTAMP_FORMAT) + result["next_update"] = self.next_update.strftime(TIMESTAMP_FORMAT) # result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid) - result['digest'] = self.module.params['digest'] - result['issuer_ordered'] = self.issuer - result['issuer'] = {} + result["digest"] = self.module.params["digest"] + result["issuer_ordered"] = self.issuer + result["issuer"] = {} for k, v in self.issuer: - result['issuer'][k] = v - result['revoked_certificates'] = [] + result["issuer"][k] = v + result["revoked_certificates"] = [] for entry in self.revoked_certificates: - 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) + ) elif self.crl: - result['last_update'] = get_last_update(self.crl).strftime(TIMESTAMP_FORMAT) - result['next_update'] = get_next_update(self.crl).strftime(TIMESTAMP_FORMAT) - result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl)) + result["last_update"] = get_last_update(self.crl).strftime(TIMESTAMP_FORMAT) + result["next_update"] = get_next_update(self.crl).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'] = {} + issuer.append( + [cryptography_oid_to_name(attribute.oid), attribute.value] + ) + result["issuer_ordered"] = issuer + result["issuer"] = {} for k, v in issuer: - result['issuer'][k] = v - result['revoked_certificates'] = [] + result["issuer"][k] = v + 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) + ) if self.return_content: - result['crl'] = self.crl_content + result["crl"] = self.crl_content - result['diff'] = dict( + result["diff"] = dict( before=self.diff_before, after=self.diff_after, ) @@ -903,100 +965,121 @@ class CRL(OpenSSLObject): def main(): module = AnsibleModule( argument_spec=dict( - state=dict(type='str', default='present', choices=['present', 'absent']), + state=dict(type="str", default="present", choices=["present", "absent"]), crl_mode=dict( - type='str', + type="str", # default='generate', - choices=['generate', 'update'], + choices=["generate", "update"], ), mode=dict( - type='str', + type="str", # default='generate', - choices=['generate', 'update'], - removed_in_version='3.0.0', - removed_from_collection='community.crypto', + choices=["generate", "update"], + removed_in_version="3.0.0", + removed_from_collection="community.crypto", ), - force=dict(type='bool', default=False), - backup=dict(type='bool', default=False), - path=dict(type='path', required=True), - format=dict(type='str', default='pem', choices=['pem', 'der']), - privatekey_path=dict(type='path'), - privatekey_content=dict(type='str', no_log=True), - privatekey_passphrase=dict(type='str', no_log=True), - issuer=dict(type='dict'), - issuer_ordered=dict(type='list', elements='dict'), - last_update=dict(type='str', default='+0s'), - next_update=dict(type='str'), - digest=dict(type='str', default='sha256'), - ignore_timestamps=dict(type='bool', default=False), - return_content=dict(type='bool', default=False), + force=dict(type="bool", default=False), + backup=dict(type="bool", default=False), + path=dict(type="path", required=True), + format=dict(type="str", default="pem", choices=["pem", "der"]), + privatekey_path=dict(type="path"), + privatekey_content=dict(type="str", no_log=True), + privatekey_passphrase=dict(type="str", no_log=True), + issuer=dict(type="dict"), + issuer_ordered=dict(type="list", elements="dict"), + last_update=dict(type="str", default="+0s"), + next_update=dict(type="str"), + digest=dict(type="str", default="sha256"), + ignore_timestamps=dict(type="bool", default=False), + return_content=dict(type="bool", default=False), revoked_certificates=dict( - type='list', - elements='dict', + type="list", + elements="dict", options=dict( - path=dict(type='path'), - content=dict(type='str'), - serial_number=dict(type='raw'), - revocation_date=dict(type='str', default='+0s'), - issuer=dict(type='list', elements='str'), - issuer_critical=dict(type='bool', default=False), + path=dict(type="path"), + content=dict(type="str"), + serial_number=dict(type="raw"), + revocation_date=dict(type="str", default="+0s"), + issuer=dict(type="list", elements="str"), + issuer_critical=dict(type="bool", default=False), reason=dict( - type='str', + type="str", choices=[ - 'unspecified', 'key_compromise', 'ca_compromise', 'affiliation_changed', - 'superseded', 'cessation_of_operation', 'certificate_hold', - 'privilege_withdrawn', 'aa_compromise', 'remove_from_crl' - ] + "unspecified", + "key_compromise", + "ca_compromise", + "affiliation_changed", + "superseded", + "cessation_of_operation", + "certificate_hold", + "privilege_withdrawn", + "aa_compromise", + "remove_from_crl", + ], ), - reason_critical=dict(type='bool', default=False), - invalidity_date=dict(type='str'), - invalidity_date_critical=dict(type='bool', default=False), + reason_critical=dict(type="bool", default=False), + invalidity_date=dict(type="str"), + invalidity_date_critical=dict(type="bool", default=False), ), - required_one_of=[['path', 'content', 'serial_number']], - mutually_exclusive=[['path', 'content', 'serial_number']], + required_one_of=[["path", "content", "serial_number"]], + mutually_exclusive=[["path", "content", "serial_number"]], + ), + name_encoding=dict( + type="str", default="ignore", choices=["ignore", "idna", "unicode"] + ), + serial_numbers=dict( + type="str", default="integer", choices=["integer", "hex-octets"] ), - name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']), - serial_numbers=dict(type='str', default='integer', choices=['integer', 'hex-octets']), ), required_if=[ - ('state', 'present', ['privatekey_path', 'privatekey_content'], True), - ('state', 'present', ['issuer', 'issuer_ordered'], True), - ('state', 'present', ['next_update', 'revoked_certificates'], False), + ("state", "present", ["privatekey_path", "privatekey_content"], True), + ("state", "present", ["issuer", "issuer_ordered"], True), + ("state", "present", ["next_update", "revoked_certificates"], False), ], mutually_exclusive=( - ['privatekey_path', 'privatekey_content'], - ['issuer', 'issuer_ordered'], + ["privatekey_path", "privatekey_content"], + ["issuer", "issuer_ordered"], ), supports_check_mode=True, add_file_common_args=True, ) - if module.params['mode']: - if module.params['crl_mode']: - module.fail_json('You cannot use both `mode` and `crl_mode`. Use `crl_mode`.') - module.params['crl_mode'] = module.params['mode'] + if module.params["mode"]: + if module.params["crl_mode"]: + module.fail_json( + "You cannot use both `mode` and `crl_mode`. Use `crl_mode`." + ) + module.params["crl_mode"] = module.params["mode"] # TODO: in 3.0.0, once the option `mode` has been removed, remove this: - module.params.pop('mode', None) + module.params.pop("mode", None) # From then on, `mode` will be the file mode of the CRL file 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, + ) try: crl = CRL(module) - if module.params['state'] == 'present': + if module.params["state"] == "present": if module.check_mode: result = crl.dump(check_mode=True) - result['changed'] = module.params['force'] or not crl.check(module) or not crl.check(module, ignore_conversion=False) + result["changed"] = ( + module.params["force"] + or not crl.check(module) + or not crl.check(module, ignore_conversion=False) + ) module.exit_json(**result) crl.generate() else: if module.check_mode: result = crl.dump(check_mode=True) - result['changed'] = os.path.exists(module.params['path']) + result["changed"] = os.path.exists(module.params["path"]) module.exit_json(**result) crl.remove() diff --git a/plugins/modules/x509_crl_info.py b/plugins/modules/x509_crl_info.py index 9cb4eb17..c8c1de1f 100644 --- a/plugins/modules/x509_crl_info.py +++ b/plugins/modules/x509_crl_info.py @@ -195,36 +195,42 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import def main(): module = AnsibleModule( argument_spec=dict( - path=dict(type='path'), - content=dict(type='str'), - list_revoked_certificates=dict(type='bool', default=True), - name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']), - ), - required_one_of=( - ['path', 'content'], - ), - mutually_exclusive=( - ['path', 'content'], + path=dict(type="path"), + content=dict(type="str"), + list_revoked_certificates=dict(type="bool", default=True), + name_encoding=dict( + type="str", default="ignore", choices=["ignore", "idna", "unicode"] + ), ), + required_one_of=(["path", "content"],), + mutually_exclusive=(["path", "content"],), supports_check_mode=True, ) - if module.params['content'] is None: + if module.params["content"] is None: try: - with open(module.params['path'], 'rb') as f: + with open(module.params["path"], "rb") as f: data = f.read() except (IOError, OSError) as e: - module.fail_json(msg='Error while reading CRL file from disk: {0}'.format(e)) + module.fail_json( + msg="Error while reading CRL file from disk: {0}".format(e) + ) else: - data = module.params['content'].encode('utf-8') + data = module.params["content"].encode("utf-8") if not identify_pem_format(data): try: - data = base64.b64decode(module.params['content']) + data = base64.b64decode(module.params["content"]) except (binascii.Error, TypeError) as e: - module.fail_json(msg='Error while Base64 decoding content: {0}'.format(e)) + module.fail_json( + msg="Error while Base64 decoding content: {0}".format(e) + ) try: - result = get_crl_info(module, data, list_revoked_certificates=module.params['list_revoked_certificates']) + result = get_crl_info( + module, + data, + list_revoked_certificates=module.params["list_revoked_certificates"], + ) module.exit_json(**result) except OpenSSLObjectError as e: module.fail_json(msg=to_native(e)) diff --git a/plugins/plugin_utils/action_module.py b/plugins/plugin_utils/action_module.py index 94e60e6f..12fed39a 100644 --- a/plugins/plugin_utils/action_module.py +++ b/plugins/plugin_utils/action_module.py @@ -71,6 +71,7 @@ try: ModuleArgumentSpecValidator as dummy, ) from ansible.module_utils.errors import UnsupportedError + HAS_ARGSPEC_VALIDATOR = True except ImportError: # For ansible-base 2.10 and Ansible 2.9, we need to use the 'classical' approach @@ -79,6 +80,7 @@ except ImportError: list_deprecations, list_no_log_values, ) + HAS_ARGSPEC_VALIDATOR = False @@ -89,10 +91,18 @@ class _ModuleExitException(Exception): class AnsibleActionModule(object): - def __init__(self, action_plugin, argument_spec, bypass_checks=False, - mutually_exclusive=None, required_together=None, - required_one_of=None, supports_check_mode=False, - required_if=None, required_by=None): + def __init__( + self, + action_plugin, + argument_spec, + bypass_checks=False, + mutually_exclusive=None, + required_together=None, + required_one_of=None, + supports_check_mode=False, + required_if=None, + required_by=None, + ): # Internal data self.__action_plugin = action_plugin self.__warnings = [] @@ -143,22 +153,38 @@ class AnsibleActionModule(object): # for our use-case: for d in self._validation_result._deprecations: # Before ansible-core 2.14.2, deprecations were always for aliases: - if 'name' in d: + if "name" in d: self.deprecate( - "Alias '{name}' is deprecated. See the module docs for more information".format(name=d['name']), - version=d.get('version'), date=d.get('date'), collection_name=d.get('collection_name')) + "Alias '{name}' is deprecated. See the module docs for more information".format( + name=d["name"] + ), + version=d.get("version"), + date=d.get("date"), + collection_name=d.get("collection_name"), + ) # Since ansible-core 2.14.2, a message is present that can be directly printed: - if 'msg' in d: - self.deprecate(d['msg'], version=d.get('version'), date=d.get('date'), collection_name=d.get('collection_name')) + if "msg" in d: + self.deprecate( + d["msg"], + version=d.get("version"), + date=d.get("date"), + collection_name=d.get("collection_name"), + ) for w in self._validation_result._warnings: - self.warn('Both option {option} and its alias {alias} are set.'.format(option=w['option'], alias=w['alias'])) + self.warn( + "Both option {option} and its alias {alias} are set.".format( + option=w["option"], alias=w["alias"] + ) + ) # Fail for validation errors, even in check mode if error: msg = self._validation_result.errors.msg if isinstance(error, UnsupportedError): - msg = "Unsupported parameters for ({name}) {kind}: {msg}".format(name=self._name, kind='module', msg=msg) + msg = "Unsupported parameters for ({name}) {kind}: {msg}".format( + name=self._name, kind="module", msg=msg + ) self.fail_json(msg=msg) else: @@ -169,7 +195,9 @@ class AnsibleActionModule(object): self.aliases = self._handle_aliases() except (ValueError, TypeError) as e: # Use exceptions here because it is not safe to call fail_json until no_log is processed - raise _ModuleExitException(dict(failed=True, msg="Module alias error: %s" % to_native(e))) + raise _ModuleExitException( + dict(failed=True, msg="Module alias error: %s" % to_native(e)) + ) # Save parameter values that should never be logged self._handle_no_log_values() @@ -183,18 +211,18 @@ class AnsibleActionModule(object): self._set_defaults(pre=True) self._CHECK_ARGUMENT_TYPES_DISPATCHER = { - 'str': self._check_type_str, - 'list': check_type_list, - 'dict': check_type_dict, - 'bool': check_type_bool, - 'int': check_type_int, - 'float': check_type_float, - 'path': check_type_path, - 'raw': check_type_raw, - 'jsonarg': check_type_jsonarg, - 'json': check_type_jsonarg, - 'bytes': check_type_bytes, - 'bits': check_type_bits, + "str": self._check_type_str, + "list": check_type_list, + "dict": check_type_dict, + "bool": check_type_bool, + "int": check_type_int, + "float": check_type_float, + "path": check_type_path, + "raw": check_type_raw, + "jsonarg": check_type_jsonarg, + "json": check_type_jsonarg, + "bytes": check_type_bytes, + "bits": check_type_bits, } if not bypass_checks: self._check_required_arguments() @@ -210,7 +238,7 @@ class AnsibleActionModule(object): # deal with options sub-spec self._handle_options() - def _handle_aliases(self, spec=None, param=None, option_prefix=''): + def _handle_aliases(self, spec=None, param=None, option_prefix=""): if spec is None: spec = self.argument_spec if param is None: @@ -218,21 +246,32 @@ class AnsibleActionModule(object): # this uses exceptions as it happens before we can safely call fail_json alias_warnings = [] - alias_results, self._legal_inputs = handle_aliases(spec, param, alias_warnings=alias_warnings) # pylint: disable=used-before-assignment + alias_results, self._legal_inputs = ( + handle_aliases( # pylint: disable=used-before-assignment + spec, param, alias_warnings=alias_warnings + ) + ) for option, alias in alias_warnings: - self.warn('Both option %s and its alias %s are set.' % (option_prefix + option, option_prefix + alias)) + self.warn( + "Both option %s and its alias %s are set." + % (option_prefix + option, option_prefix + alias) + ) deprecated_aliases = [] for i in spec.keys(): - if 'deprecated_aliases' in spec[i].keys(): - for alias in spec[i]['deprecated_aliases']: + if "deprecated_aliases" in spec[i].keys(): + for alias in spec[i]["deprecated_aliases"]: deprecated_aliases.append(alias) for deprecation in deprecated_aliases: - if deprecation['name'] in param.keys(): - self.deprecate("Alias '%s' is deprecated. See the module docs for more information" % deprecation['name'], - version=deprecation.get('version'), date=deprecation.get('date'), - collection_name=deprecation.get('collection_name')) + if deprecation["name"] in param.keys(): + self.deprecate( + "Alias '%s' is deprecated. See the module docs for more information" + % deprecation["name"], + version=deprecation.get("version"), + date=deprecation.get("date"), + collection_name=deprecation.get("collection_name"), + ) return alias_results def _handle_no_log_values(self, spec=None, param=None): @@ -242,17 +281,30 @@ class AnsibleActionModule(object): param = self.params try: - self.no_log_values.update(list_no_log_values(spec, param)) # pylint: disable=used-before-assignment + self.no_log_values.update( + list_no_log_values( # pylint: disable=used-before-assignment + spec, param + ) + ) except TypeError as te: - self.fail_json(msg="Failure when processing no_log parameters. Module invocation will be hidden. " - "%s" % to_native(te), invocation={'module_args': 'HIDDEN DUE TO FAILURE'}) + self.fail_json( + msg="Failure when processing no_log parameters. Module invocation will be hidden. " + "%s" % to_native(te), + invocation={"module_args": "HIDDEN DUE TO FAILURE"}, + ) - for message in list_deprecations(spec, param): # pylint: disable=used-before-assignment - self.deprecate(message['msg'], version=message.get('version'), date=message.get('date'), - collection_name=message.get('collection_name')) + for message in list_deprecations( # pylint: disable=used-before-assignment + spec, param + ): + self.deprecate( + message["msg"], + version=message.get("version"), + date=message.get("date"), + collection_name=message.get("collection_name"), + ) def _check_arguments(self, spec=None, param=None, legal_inputs=None): - self._syslog_facility = 'LOG_USER' + self._syslog_facility = "LOG_USER" unsupported_parameters = set() if spec is None: spec = self.argument_spec @@ -268,7 +320,7 @@ class AnsibleActionModule(object): for k in PASS_VARS: # handle setting internal properties from internal ansible vars - param_key = '_ansible_%s' % k + param_key = "_ansible_%s" % k if param_key in param: if k in PASS_BOOLS: setattr(self, PASS_VARS[k][0], self.boolean(param[param_key])) @@ -284,19 +336,29 @@ class AnsibleActionModule(object): setattr(self, PASS_VARS[k][0], PASS_VARS[k][1]) if unsupported_parameters: - msg = "Unsupported parameters for (%s) module: %s" % (self._name, ', '.join(sorted(list(unsupported_parameters)))) + msg = "Unsupported parameters for (%s) module: %s" % ( + self._name, + ", ".join(sorted(list(unsupported_parameters))), + ) if self._options_context: msg += " found in %s." % " -> ".join(self._options_context) supported_parameters = list() for key in sorted(spec.keys()): - if 'aliases' in spec[key] and spec[key]['aliases']: - supported_parameters.append("%s (%s)" % (key, ', '.join(sorted(spec[key]['aliases'])))) + if "aliases" in spec[key] and spec[key]["aliases"]: + supported_parameters.append( + "%s (%s)" % (key, ", ".join(sorted(spec[key]["aliases"]))) + ) else: supported_parameters.append(key) - msg += " Supported parameters include: %s" % (', '.join(supported_parameters)) + msg += " Supported parameters include: %s" % ( + ", ".join(supported_parameters) + ) self.fail_json(msg=msg) if self.check_mode and not self.supports_check_mode: - self.exit_json(skipped=True, msg="action module (%s) does not support check mode" % self._name) + self.exit_json( + skipped=True, + msg="action module (%s) does not support check mode" % self._name, + ) def _count_terms(self, check, param=None): if param is None: @@ -370,7 +432,7 @@ class AnsibleActionModule(object): self.fail_json(msg=msg) def _check_required_if(self, spec, param=None): - ''' ensure that parameters which conditionally required are present ''' + """ensure that parameters which conditionally required are present""" if spec is None: return if param is None: @@ -385,38 +447,47 @@ class AnsibleActionModule(object): self.fail_json(msg=msg) def _check_argument_values(self, spec=None, param=None): - ''' ensure all arguments have the requested values, and there are no stray arguments ''' + """ensure all arguments have the requested values, and there are no stray arguments""" if spec is None: spec = self.argument_spec if param is None: param = self.params - for (k, v) in spec.items(): - choices = v.get('choices', None) + for k, v in spec.items(): + choices = v.get("choices", None) if choices is None: continue - if isinstance(choices, SEQUENCETYPE) and not isinstance(choices, (binary_type, text_type)): + if isinstance(choices, SEQUENCETYPE) and not isinstance( + choices, (binary_type, text_type) + ): if k in param: # Allow one or more when type='list' param with choices if isinstance(param[k], list): - diff_list = ", ".join([item for item in param[k] if item not in choices]) + diff_list = ", ".join( + [item for item in param[k] if item not in choices] + ) if diff_list: choices_str = ", ".join([to_native(c) for c in choices]) - msg = "value of %s must be one or more of: %s. Got no match for: %s" % (k, choices_str, diff_list) + msg = ( + "value of %s must be one or more of: %s. Got no match for: %s" + % (k, choices_str, diff_list) + ) if self._options_context: - msg += " found in %s" % " -> ".join(self._options_context) + msg += " found in %s" % " -> ".join( + self._options_context + ) self.fail_json(msg=msg) elif param[k] not in choices: # PyYaml converts certain strings to bools. If we can unambiguously convert back, do so before checking # the value. If we cannot figure this out, module author is responsible. lowered_choices = None - if param[k] == 'False': + if param[k] == "False": lowered_choices = lenient_lowercase(choices) overlap = BOOLEANS_FALSE.intersection(choices) if len(overlap) == 1: # Extract from a set (param[k],) = overlap - if param[k] == 'True': + if param[k] == "True": if lowered_choices is None: lowered_choices = lenient_lowercase(choices) overlap = BOOLEANS_TRUE.intersection(choices) @@ -425,12 +496,21 @@ class AnsibleActionModule(object): if param[k] not in choices: choices_str = ", ".join([to_native(c) for c in choices]) - msg = "value of %s must be one of: %s, got: %s" % (k, choices_str, param[k]) + msg = "value of %s must be one of: %s, got: %s" % ( + k, + choices_str, + param[k], + ) if self._options_context: - msg += " found in %s" % " -> ".join(self._options_context) + msg += " found in %s" % " -> ".join( + self._options_context + ) self.fail_json(msg=msg) else: - msg = "internal error: choices for argument %s are not iterable: %s" % (k, choices) + msg = "internal error: choices for argument %s are not iterable: %s" % ( + k, + choices, + ) if self._options_context: msg += " found in %s" % " -> ".join(self._options_context) self.fail_json(msg=msg) @@ -438,50 +518,50 @@ class AnsibleActionModule(object): def safe_eval(self, value, locals=None, include_exceptions=False): return safe_eval(value, locals, include_exceptions) - def _check_type_str(self, value, param=None, prefix=''): - opts = { - 'error': False, - 'warn': False, - 'ignore': True - } + def _check_type_str(self, value, param=None, prefix=""): + opts = {"error": False, "warn": False, "ignore": True} # Ignore, warn, or error when converting to a string. allow_conversion = opts.get(C.STRING_CONVERSION_ACTION, True) try: return check_type_str(value, allow_conversion) except TypeError: - common_msg = 'quote the entire value to ensure it does not change.' - from_msg = '{0!r}'.format(value) - to_msg = '{0!r}'.format(to_text(value)) + common_msg = "quote the entire value to ensure it does not change." + from_msg = "{0!r}".format(value) + to_msg = "{0!r}".format(to_text(value)) if param is not None: if prefix: - param = '{0}{1}'.format(prefix, param) + param = "{0}{1}".format(prefix, param) - from_msg = '{0}: {1!r}'.format(param, value) - to_msg = '{0}: {1!r}'.format(param, to_text(value)) + from_msg = "{0}: {1!r}".format(param, value) + to_msg = "{0}: {1!r}".format(param, to_text(value)) - if C.STRING_CONVERSION_ACTION == 'error': + if C.STRING_CONVERSION_ACTION == "error": msg = common_msg.capitalize() raise TypeError(to_native(msg)) - elif C.STRING_CONVERSION_ACTION == 'warn': - msg = ('The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). ' - 'If this does not look like what you expect, {3}').format(from_msg, value, to_msg, common_msg) + elif C.STRING_CONVERSION_ACTION == "warn": + msg = ( + 'The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). ' + "If this does not look like what you expect, {3}" + ).format(from_msg, value, to_msg, common_msg) self.warn(to_native(msg)) - return to_native(value, errors='surrogate_or_strict') + return to_native(value, errors="surrogate_or_strict") - def _handle_options(self, argument_spec=None, params=None, prefix=''): - ''' deal with options to create sub spec ''' + def _handle_options(self, argument_spec=None, params=None, prefix=""): + """deal with options to create sub spec""" if argument_spec is None: argument_spec = self.argument_spec if params is None: params = self.params - for (k, v) in argument_spec.items(): - wanted = v.get('type', None) - if wanted == 'dict' or (wanted == 'list' and v.get('elements', '') == 'dict'): - spec = v.get('options', None) - if v.get('apply_defaults', False): + for k, v in argument_spec.items(): + wanted = v.get("type", None) + if wanted == "dict" or ( + wanted == "list" and v.get("elements", "") == "dict" + ): + spec = v.get("options", None) + if v.get("apply_defaults", False): if spec is not None: if params.get(k) is None: params[k] = {} @@ -499,23 +579,31 @@ class AnsibleActionModule(object): for idx, param in enumerate(elements): if not isinstance(param, dict): - self.fail_json(msg="value of %s must be of type dict or list of dict" % k) + self.fail_json( + msg="value of %s must be of type dict or list of dict" % k + ) new_prefix = prefix + k - if wanted == 'list': - new_prefix += '[%d]' % idx - new_prefix += '.' + if wanted == "list": + new_prefix += "[%d]" % idx + new_prefix += "." self._set_fallbacks(spec, param) - options_aliases = self._handle_aliases(spec, param, option_prefix=new_prefix) + options_aliases = self._handle_aliases( + spec, param, option_prefix=new_prefix + ) - options_legal_inputs = list(spec.keys()) + list(options_aliases.keys()) + options_legal_inputs = list(spec.keys()) + list( + options_aliases.keys() + ) self._check_arguments(spec, param, options_legal_inputs) # check exclusive early if not self.bypass_checks: - self._check_mutually_exclusive(v.get('mutually_exclusive', None), param) + self._check_mutually_exclusive( + v.get("mutually_exclusive", None), param + ) self._set_defaults(pre=True, spec=spec, param=param) @@ -524,10 +612,14 @@ class AnsibleActionModule(object): self._check_argument_types(spec, param, new_prefix) self._check_argument_values(spec, param) - self._check_required_together(v.get('required_together', None), param) - self._check_required_one_of(v.get('required_one_of', None), param) - self._check_required_if(v.get('required_if', None), param) - self._check_required_by(v.get('required_by', None), param) + self._check_required_together( + v.get("required_together", None), param + ) + self._check_required_one_of( + v.get("required_one_of", None), param + ) + self._check_required_if(v.get("required_if", None), param) + self._check_required_by(v.get("required_by", None), param) self._set_defaults(pre=False, spec=spec, param=param) @@ -541,15 +633,18 @@ class AnsibleActionModule(object): # Mostly we want to default to str. # For values set to None explicitly, return None instead as # that allows a user to unset a parameter - wanted = 'str' + wanted = "str" try: type_checker = self._CHECK_ARGUMENT_TYPES_DISPATCHER[wanted] except KeyError: - self.fail_json(msg="implementation error: unknown type %s requested for %s" % (wanted, k)) + self.fail_json( + msg="implementation error: unknown type %s requested for %s" + % (wanted, k) + ) else: # set the type_checker to the callable, and reset wanted to the callable's name (or type if it does not have one, ala MagicMock) type_checker = wanted - wanted = getattr(wanted, '__name__', to_native(type(wanted))) + wanted = getattr(wanted, "__name__", to_native(type(wanted))) return type_checker, wanted @@ -559,11 +654,11 @@ class AnsibleActionModule(object): # Get param name for strings so we can later display this value in a useful error message if needed # Only pass 'kwargs' to our checkers and ignore custom callable checkers kwargs = {} - if wanted_name == 'str' and isinstance(wanted, string_types): + if wanted_name == "str" and isinstance(wanted, string_types): if isinstance(param, string_types): - kwargs['param'] = param + kwargs["param"] = param elif isinstance(param, dict): - kwargs['param'] = list(param.keys())[0] + kwargs["param"] = list(param.keys())[0] for value in values: try: validated_params.append(type_checker(value, **kwargs)) @@ -571,20 +666,24 @@ class AnsibleActionModule(object): msg = "Elements value for option %s" % param if self._options_context: msg += " found in '%s'" % " -> ".join(self._options_context) - msg += " is of type %s and we were unable to convert to %s: %s" % (type(value), wanted_name, to_native(e)) + msg += " is of type %s and we were unable to convert to %s: %s" % ( + type(value), + wanted_name, + to_native(e), + ) self.fail_json(msg=msg) return validated_params - def _check_argument_types(self, spec=None, param=None, prefix=''): - ''' ensure all arguments have the requested type ''' + def _check_argument_types(self, spec=None, param=None, prefix=""): + """ensure all arguments have the requested type""" if spec is None: spec = self.argument_spec if param is None: param = self.params - for (k, v) in spec.items(): - wanted = v.get('type', None) + for k, v in spec.items(): + wanted = v.get("type", None) if k not in param: continue @@ -596,22 +695,26 @@ class AnsibleActionModule(object): # Get param name for strings so we can later display this value in a useful error message if needed # Only pass 'kwargs' to our checkers and ignore custom callable checkers kwargs = {} - if wanted_name == 'str' and isinstance(type_checker, string_types): - kwargs['param'] = list(param.keys())[0] + if wanted_name == "str" and isinstance(type_checker, string_types): + kwargs["param"] = list(param.keys())[0] # Get the name of the parent key if this is a nested option if prefix: - kwargs['prefix'] = prefix + kwargs["prefix"] = prefix try: param[k] = type_checker(value, **kwargs) - wanted_elements = v.get('elements', None) + wanted_elements = v.get("elements", None) if wanted_elements: - if wanted != 'list' or not isinstance(param[k], list): + if wanted != "list" or not isinstance(param[k], list): msg = "Invalid type %s for option '%s'" % (wanted_name, param) if self._options_context: - msg += " found in '%s'." % " -> ".join(self._options_context) - msg += ", elements value check is supported only with 'list' type" + msg += " found in '%s'." % " -> ".join( + self._options_context + ) + msg += ( + ", elements value check is supported only with 'list' type" + ) self.fail_json(msg=msg) param[k] = self._handle_elements(wanted_elements, k, param[k]) @@ -619,7 +722,10 @@ class AnsibleActionModule(object): msg = "argument %s is of type %s" % (k, type(value)) if self._options_context: msg += " found in '%s'." % " -> ".join(self._options_context) - msg += " and we were unable to convert to %s: %s" % (wanted_name, to_native(e)) + msg += " and we were unable to convert to %s: %s" % ( + wanted_name, + to_native(e), + ) self.fail_json(msg=msg) def _set_defaults(self, pre=True, spec=None, param=None): @@ -627,8 +733,8 @@ class AnsibleActionModule(object): spec = self.argument_spec if param is None: param = self.params - for (k, v) in spec.items(): - default = v.get('default', None) + for k, v in spec.items(): + default = v.get("default", None) if pre is True: # this prevents setting defaults on required items if default is not None and k not in param: @@ -644,8 +750,8 @@ class AnsibleActionModule(object): if param is None: param = self.params - for (k, v) in spec.items(): - fallback = v.get('fallback', (None,)) + for k, v in spec.items(): + fallback = v.get("fallback", (None,)) fallback_strategy = fallback[0] fallback_args = [] fallback_kwargs = {} @@ -669,62 +775,76 @@ class AnsibleActionModule(object): def deprecate(self, msg, version=None, date=None, collection_name=None): if version is not None and date is not None: - raise AssertionError("implementation error -- version and date must not both be set") + raise AssertionError( + "implementation error -- version and date must not both be set" + ) # Copied from ansible.module_utils.common.warnings: if isinstance(msg, string_types): # For compatibility, we accept that neither version nor date is set, # and treat that the same as if version would haven been set if date is not None: - self.__deprecations.append({'msg': msg, 'date': date, 'collection_name': collection_name}) + self.__deprecations.append( + {"msg": msg, "date": date, "collection_name": collection_name} + ) else: - self.__deprecations.append({'msg': msg, 'version': version, 'collection_name': collection_name}) + self.__deprecations.append( + {"msg": msg, "version": version, "collection_name": collection_name} + ) else: raise TypeError("deprecate requires a string not a %s" % type(msg)) def _return_formatted(self, kwargs): - if 'invocation' not in kwargs: - kwargs['invocation'] = {'module_args': self.params} + if "invocation" not in kwargs: + kwargs["invocation"] = {"module_args": self.params} - if 'warnings' in kwargs: - if isinstance(kwargs['warnings'], list): - for w in kwargs['warnings']: + if "warnings" in kwargs: + if isinstance(kwargs["warnings"], list): + for w in kwargs["warnings"]: self.warn(w) else: - self.warn(kwargs['warnings']) + self.warn(kwargs["warnings"]) if self.__warnings: - kwargs['warnings'] = self.__warnings + kwargs["warnings"] = self.__warnings - if 'deprecations' in kwargs: - if isinstance(kwargs['deprecations'], list): - for d in kwargs['deprecations']: + if "deprecations" in kwargs: + if isinstance(kwargs["deprecations"], list): + for d in kwargs["deprecations"]: if isinstance(d, SEQUENCETYPE) and len(d) == 2: self.deprecate(d[0], version=d[1]) elif isinstance(d, Mapping): - self.deprecate(d['msg'], version=d.get('version'), date=d.get('date'), - collection_name=d.get('collection_name')) + self.deprecate( + d["msg"], + version=d.get("version"), + date=d.get("date"), + collection_name=d.get("collection_name"), + ) else: - self.deprecate(d) # pylint: disable=ansible-deprecated-no-version + self.deprecate( # pylint: disable=ansible-deprecated-no-version + d + ) else: - self.deprecate(kwargs['deprecations']) # pylint: disable=ansible-deprecated-no-version + self.deprecate( # pylint: disable=ansible-deprecated-no-version + kwargs["deprecations"] + ) if self.__deprecations: - kwargs['deprecations'] = self.__deprecations + kwargs["deprecations"] = self.__deprecations kwargs = remove_values(kwargs, self.no_log_values) raise _ModuleExitException(kwargs) def exit_json(self, **kwargs): result = dict(kwargs) - if 'failed' not in result: - result['failed'] = False + if "failed" not in result: + result["failed"] = False self._return_formatted(result) def fail_json(self, msg, **kwargs): result = dict(kwargs) - result['failed'] = True - result['msg'] = msg + result["failed"] = True + result["msg"] = msg self._return_formatted(result) @@ -738,7 +858,7 @@ class ActionModuleBase(ActionBase): @abc.abstractmethod def run_module(self, module): """Run module code""" - module.fail_json(msg='Not implemented.') + module.fail_json(msg="Not implemented.") def run(self, tmp=None, task_vars=None): if task_vars is None: @@ -749,14 +869,18 @@ class ActionModuleBase(ActionBase): try: argument_spec, kwargs = self.setup_module() - module = argument_spec.create_ansible_module_helper(AnsibleActionModule, (self, ), **kwargs) + module = argument_spec.create_ansible_module_helper( + AnsibleActionModule, (self,), **kwargs + ) self.run_module(module) - raise AnsibleError('Internal error: action module did not call module.exit_json()') + raise AnsibleError( + "Internal error: action module did not call module.exit_json()" + ) except _ModuleExitException as mee: result.update(mee.result) return result except Exception: - result['failed'] = True - result['msg'] = 'MODULE FAILURE' - result['exception'] = traceback.format_exc() + result["failed"] = True + result["msg"] = "MODULE FAILURE" + result["exception"] = traceback.format_exc() return result diff --git a/plugins/plugin_utils/gnupg.py b/plugins/plugin_utils/gnupg.py index a544a517..55e059a0 100644 --- a/plugins/plugin_utils/gnupg.py +++ b/plugins/plugin_utils/gnupg.py @@ -22,9 +22,9 @@ class PluginGPGRunner(GPGRunner): def __init__(self, executable=None, cwd=None): if executable is None: try: - executable = get_bin_path('gpg') + executable = get_bin_path("gpg") except ValueError: - raise GPGError('Cannot find the `gpg` executable on the controller') + raise GPGError("Cannot find the `gpg` executable on the controller") self.executable = executable self.cwd = cwd @@ -41,15 +41,19 @@ class PluginGPGRunner(GPGRunner): Raises a ``GPGError`` in case of errors. """ command = [self.executable] + command - p = Popen(command, shell=False, cwd=self.cwd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + p = Popen( + command, shell=False, cwd=self.cwd, stdin=PIPE, stdout=PIPE, stderr=PIPE + ) stdout, stderr = p.communicate(input=data) - stdout = to_native(stdout, errors='surrogate_or_replace') - stderr = to_native(stderr, errors='surrogate_or_replace') + stdout = to_native(stdout, errors="surrogate_or_replace") + stderr = to_native(stderr, errors="surrogate_or_replace") if check_rc and p.returncode != 0: - raise GPGError('Running {cmd} yielded return code {rc} with stdout: "{stdout}" and stderr: "{stderr}")'.format( - cmd=' '.join(command), - rc=p.returncode, - stdout=to_native(stdout, errors='surrogate_or_replace'), - stderr=to_native(stderr, errors='surrogate_or_replace'), - )) + raise GPGError( + 'Running {cmd} yielded return code {rc} with stdout: "{stdout}" and stderr: "{stderr}")'.format( + cmd=" ".join(command), + rc=p.returncode, + stdout=to_native(stdout, errors="surrogate_or_replace"), + stderr=to_native(stderr, errors="surrogate_or_replace"), + ) + ) return p.returncode, stdout, stderr diff --git a/tests/unit/plugins/module_utils/acme/backend_data.py b/tests/unit/plugins/module_utils/acme/backend_data.py index a3532d8b..e530bd8c 100644 --- a/tests/unit/plugins/module_utils/acme/backend_data.py +++ b/tests/unit/plugins/module_utils/acme/backend_data.py @@ -25,60 +25,60 @@ from ..test_time import TIMEZONES, cartesian_product def load_fixture(name): - with open(os.path.join(os.path.dirname(__file__), 'fixtures', name)) as f: + with open(os.path.join(os.path.dirname(__file__), "fixtures", name)) as f: return f.read() TEST_PEM_DERS = [ ( - load_fixture('privatekey_1.pem'), - base64.b64decode('MHcCAQEEIDWajU0PyhYKeulfy/luNtkAve7DkwQ01bXJ97zbxB66oAo' - 'GCCqGSM49AwEHoUQDQgAEAJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3' - 'lWszrS68G8QSzhXR6AmQ3IzZDimnTTXO7XhVylDT8SLzE44/Epmw==') + load_fixture("privatekey_1.pem"), + base64.b64decode( + "MHcCAQEEIDWajU0PyhYKeulfy/luNtkAve7DkwQ01bXJ97zbxB66oAo" + "GCCqGSM49AwEHoUQDQgAEAJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3" + "lWszrS68G8QSzhXR6AmQ3IzZDimnTTXO7XhVylDT8SLzE44/Epmw==" + ), ) ] TEST_KEYS = [ ( - load_fixture('privatekey_1.pem'), + load_fixture("privatekey_1.pem"), { - 'alg': 'ES256', - 'hash': 'sha256', - 'jwk': { - 'crv': 'P-256', - 'kty': 'EC', - 'x': 'AJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3lWszrS68E', - 'y': 'vEEs4V0egJkNyM2Q4pp001zu14VcpQ0_Ei8xOOPxKZs', + "alg": "ES256", + "hash": "sha256", + "jwk": { + "crv": "P-256", + "kty": "EC", + "x": "AJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3lWszrS68E", + "y": "vEEs4V0egJkNyM2Q4pp001zu14VcpQ0_Ei8xOOPxKZs", }, - 'point_size': 32, - 'type': 'ec', + "point_size": 32, + "type": "ec", }, - load_fixture('privatekey_1.txt'), + load_fixture("privatekey_1.txt"), ) ] TEST_CSRS = [ ( - load_fixture('csr_1.pem'), - set([ - ('dns', 'ansible.com'), - ('dns', 'example.com'), - ('dns', 'example.org') - ]), - load_fixture('csr_1.txt'), + load_fixture("csr_1.pem"), + set([("dns", "ansible.com"), ("dns", "example.com"), ("dns", "example.org")]), + load_fixture("csr_1.txt"), ), ( - load_fixture('csr_2.pem'), - set([ - ('dns', 'ansible.com'), - ('ip', '127.0.0.1'), - ('ip', '::1'), - ('ip', '2001:d88:ac10:fe01::'), - ('ip', '2001:1234:5678:abcd:9876:5432:10fe:dcba') - ]), - load_fixture('csr_2.txt'), + load_fixture("csr_2.pem"), + set( + [ + ("dns", "ansible.com"), + ("ip", "127.0.0.1"), + ("ip", "::1"), + ("ip", "2001:d88:ac10:fe01::"), + ("ip", "2001:1234:5678:abcd:9876:5432:10fe:dcba"), + ] + ), + load_fixture("csr_2.txt"), ), ] @@ -92,18 +92,21 @@ TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt") # OpenSSL 3.3.0 output TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt") # OpenSSL 1.1.1f output -TEST_CERT_DAYS = cartesian_product(TIMEZONES, [ - (datetime.datetime(2018, 11, 15, 1, 2, 3), 11), - (datetime.datetime(2018, 11, 25, 15, 20, 0), 1), - (datetime.datetime(2018, 11, 25, 15, 30, 0), 0), -]) +TEST_CERT_DAYS = cartesian_product( + TIMEZONES, + [ + (datetime.datetime(2018, 11, 15, 1, 2, 3), 11), + (datetime.datetime(2018, 11, 25, 15, 20, 0), 1), + (datetime.datetime(2018, 11, 25, 15, 30, 0), 0), + ], +) TEST_CERT_INFO = CertificateInformation( not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), serial_number=1, - subject_key_identifier=b'\x98\xD2\xFD\x3C\xCC\xCD\x69\x45\xFB\xE2\x8C\x30\x2C\x54\x62\x18\x34\xB7\x07\x73', + subject_key_identifier=b"\x98\xd2\xfd\x3c\xcc\xcd\x69\x45\xfb\xe2\x8c\x30\x2c\x54\x62\x18\x34\xb7\x07\x73", authority_key_identifier=None, ) @@ -112,8 +115,8 @@ TEST_CERT_INFO_2 = CertificateInformation( not_valid_before=datetime.datetime(2024, 5, 4, 20, 42, 21), not_valid_after=datetime.datetime(2029, 5, 4, 20, 42, 20), serial_number=4218235397573492796, - subject_key_identifier=b'\x17\xE5\x83\x22\x14\xEF\x74\xD3\xBE\x7E\x30\x76\x56\x1F\x51\x74\x65\x1F\xE9\xF0', - authority_key_identifier=b'\x13\xC3\x4C\x3E\x59\x45\xDD\xE3\x63\x51\xA3\x46\x80\xC4\x08\xC7\x14\xC0\x64\x4E', + subject_key_identifier=b"\x17\xe5\x83\x22\x14\xef\x74\xd3\xbe\x7e\x30\x76\x56\x1f\x51\x74\x65\x1f\xe9\xf0", + authority_key_identifier=b"\x13\xc3\x4c\x3e\x59\x45\xdd\xe3\x63\x51\xa3\x46\x80\xc4\x08\xc7\x14\xc0\x64\x4e", ) @@ -124,77 +127,112 @@ TEST_CERT_INFO = [ ] -TEST_PARSE_ACME_TIMESTAMP = cartesian_product(TIMEZONES, [ - ( - '2024-01-01T00:11:22Z', - dict(year=2024, month=1, day=1, hour=0, minute=11, second=22), - ), - ( - '2024-01-01T00:11:22.123Z', - dict(year=2024, month=1, day=1, hour=0, minute=11, second=22, microsecond=123000), - ), - ( - '2024-04-17T06:54:13.333333334Z', - dict(year=2024, month=4, day=17, hour=6, minute=54, second=13, microsecond=333333), - ), -]) +TEST_PARSE_ACME_TIMESTAMP = cartesian_product( + TIMEZONES, + [ + ( + "2024-01-01T00:11:22Z", + dict(year=2024, month=1, day=1, hour=0, minute=11, second=22), + ), + ( + "2024-01-01T00:11:22.123Z", + dict( + year=2024, + month=1, + day=1, + hour=0, + minute=11, + second=22, + microsecond=123000, + ), + ), + ( + "2024-04-17T06:54:13.333333334Z", + dict( + year=2024, + month=4, + day=17, + hour=6, + minute=54, + second=13, + microsecond=333333, + ), + ), + ], +) if sys.version_info >= (3, 5): - TEST_PARSE_ACME_TIMESTAMP.extend(cartesian_product(TIMEZONES, [ - ( - '2024-01-01T00:11:22+0100', - dict(year=2023, month=12, day=31, hour=23, minute=11, second=22), - ), - ( - '2024-01-01T00:11:22.123+0100', - dict(year=2023, month=12, day=31, hour=23, minute=11, second=22, microsecond=123000), - ), - ])) + TEST_PARSE_ACME_TIMESTAMP.extend( + cartesian_product( + TIMEZONES, + [ + ( + "2024-01-01T00:11:22+0100", + dict(year=2023, month=12, day=31, hour=23, minute=11, second=22), + ), + ( + "2024-01-01T00:11:22.123+0100", + dict( + year=2023, + month=12, + day=31, + hour=23, + minute=11, + second=22, + microsecond=123000, + ), + ), + ], + ) + ) -TEST_INTERPOLATE_TIMESTAMP = cartesian_product(TIMEZONES, [ - ( - dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), - dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), - 0.0, - dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), - ), - ( - dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), - dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), - 0.5, - dict(year=2024, month=1, day=1, hour=0, minute=30, second=0), - ), - ( - dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), - dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), - 1.0, - dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), - ), -]) +TEST_INTERPOLATE_TIMESTAMP = cartesian_product( + TIMEZONES, + [ + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 0.0, + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + ), + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 0.5, + dict(year=2024, month=1, day=1, hour=0, minute=30, second=0), + ), + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 1.0, + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + ), + ], +) class FakeBackend(CryptoBackend): def parse_key(self, key_file=None, key_content=None, passphrase=None): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def sign(self, payload64, protected64, key_data): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def create_mac_key(self, alg, key): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def get_csr_identifiers(self, csr_filename=None, csr_content=None): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def get_cert_days(self, cert_filename=None, cert_content=None, now=None): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def create_chain_matcher(self, criterium): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") def get_cert_information(self, cert_filename=None, cert_content=None): - raise BackendException('Not implemented in fake backend') + raise BackendException("Not implemented in fake backend") diff --git a/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py b/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py index 2cf9a765..1efea750 100644 --- a/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py +++ b/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py @@ -39,26 +39,26 @@ from .backend_data import ( if not HAS_CURRENT_CRYPTOGRAPHY: - pytest.skip('cryptography not found') + pytest.skip("cryptography not found") @pytest.mark.parametrize("pem, result, dummy", TEST_KEYS) def test_eckeyparse_cryptography(pem, result, dummy, tmpdir): - fn = tmpdir / 'test.pem' + fn = tmpdir / "test.pem" fn.write(pem) module = MagicMock() backend = CryptographyBackend(module) key = backend.parse_key(key_file=str(fn)) - key.pop('key_obj') + key.pop("key_obj") assert key == result key = backend.parse_key(key_content=pem) - key.pop('key_obj') + key.pop("key_obj") assert key == result @pytest.mark.parametrize("csr, result, openssl_output", TEST_CSRS) def test_csridentifiers_cryptography(csr, result, openssl_output, tmpdir): - fn = tmpdir / 'test.csr' + fn = tmpdir / "test.csr" fn.write(csr) module = MagicMock() backend = CryptographyBackend(module) @@ -71,7 +71,7 @@ def test_csridentifiers_cryptography(csr, result, openssl_output, tmpdir): @pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS) def test_certdays_cryptography(timezone, now, expected_days, tmpdir): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): - fn = tmpdir / 'test-cert.pem' + fn = tmpdir / "test-cert.pem" fn.write(TEST_CERT) module = MagicMock() backend = CryptographyBackend(module) @@ -81,9 +81,11 @@ def test_certdays_cryptography(timezone, now, expected_days, tmpdir): assert days == expected_days -@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) +@pytest.mark.parametrize( + "cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO +) def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir): - fn = tmpdir / 'test-cert.pem' + fn = tmpdir / "test-cert.pem" fn.write(cert_content) module = MagicMock() backend = CryptographyBackend(module) @@ -103,7 +105,9 @@ def test_get_cert_information(cert_content, expected_cert_info, openssl_output, # @pytest.mark.parametrize("timezone", TIMEZONES) # Due to a bug in freezegun (https://github.com/spulec/freezegun/issues/348, https://github.com/spulec/freezegun/issues/553) # this only works with timezone = UTC if CRYPTOGRAPHY_TIMEZONE is truish -@pytest.mark.parametrize("timezone", [datetime.timedelta(hours=0)] if CRYPTOGRAPHY_TIMEZONE else TIMEZONES) +@pytest.mark.parametrize( + "timezone", [datetime.timedelta(hours=0)] if CRYPTOGRAPHY_TIMEZONE else TIMEZONES +) def test_now(timezone): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): module = MagicMock() @@ -127,7 +131,9 @@ def test_parse_acme_timestamp(timezone, input, expected): assert ts_expected == timestamp -@pytest.mark.parametrize("timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +@pytest.mark.parametrize( + "timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP +) def test_interpolate_timestamp(timezone, start, end, percentage, expected): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): module = MagicMock() diff --git a/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py b/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py index ed6c8b25..67a9aae7 100644 --- a/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py +++ b/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py @@ -51,23 +51,23 @@ TEST_IPS = [ @pytest.mark.parametrize("pem, result, openssl_output", TEST_KEYS) def test_eckeyparse_openssl(pem, result, openssl_output, tmpdir): - fn = tmpdir / 'test.key' + fn = tmpdir / "test.key" fn.write(pem) module = MagicMock() module.run_command = MagicMock(return_value=(0, openssl_output, 0)) - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") key = backend.parse_key(key_file=str(fn)) - key.pop('key_file') + key.pop("key_file") assert key == result @pytest.mark.parametrize("csr, result, openssl_output", TEST_CSRS) def test_csridentifiers_openssl(csr, result, openssl_output, tmpdir): - fn = tmpdir / 'test.csr' + fn = tmpdir / "test.csr" fn.write(csr) module = MagicMock() module.run_command = MagicMock(return_value=(0, openssl_output, 0)) - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") identifiers = backend.get_csr_identifiers(str(fn)) assert identifiers == result @@ -75,31 +75,33 @@ def test_csridentifiers_openssl(csr, result, openssl_output, tmpdir): @pytest.mark.parametrize("ip, result", TEST_IPS) def test_normalize_ip(ip, result): module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") assert backend._normalize_ip(ip) == result @pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS) def test_certdays_cryptography(timezone, now, expected_days, tmpdir): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): - fn = tmpdir / 'test-cert.pem' + fn = tmpdir / "test-cert.pem" fn.write(TEST_CERT) module = MagicMock() module.run_command = MagicMock(return_value=(0, TEST_CERT_OPENSSL_OUTPUT, 0)) - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") days = backend.get_cert_days(cert_filename=str(fn), now=now) assert days == expected_days days = backend.get_cert_days(cert_content=TEST_CERT, now=now) assert days == expected_days -@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) +@pytest.mark.parametrize( + "cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO +) def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir): - fn = tmpdir / 'test-cert.pem' + fn = tmpdir / "test-cert.pem" fn.write(cert_content) module = MagicMock() module.run_command = MagicMock(return_value=(0, openssl_output, 0)) - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") expected_cert_info = expected_cert_info._replace( not_valid_after=ensure_utc_timezone(expected_cert_info.not_valid_after), @@ -119,7 +121,7 @@ def test_get_cert_information(cert_content, expected_cert_info, openssl_output, def test_now(timezone): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") now = backend.get_now() assert now.tzinfo is not None assert now == datetime.datetime(2024, 2, 3, 4, 5, 6, tzinfo=UTC) @@ -129,17 +131,19 @@ def test_now(timezone): def test_parse_acme_timestamp(timezone, input, expected): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") ts_expected = backend.get_utc_datetime(**expected) timestamp = backend.parse_acme_timestamp(input) assert ts_expected == timestamp -@pytest.mark.parametrize("timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +@pytest.mark.parametrize( + "timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP +) def test_interpolate_timestamp(timezone, start, end, percentage, expected): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + backend = OpenSSLCLIBackend(module, openssl_binary="openssl") ts_start = backend.get_utc_datetime(**start) ts_end = backend.get_utc_datetime(**end) ts_expected = backend.get_utc_datetime(**expected) diff --git a/tests/unit/plugins/module_utils/acme/test_challenges.py b/tests/unit/plugins/module_utils/acme/test_challenges.py index ddae4884..224e96a2 100644 --- a/tests/unit/plugins/module_utils/acme/test_challenges.py +++ b/tests/unit/plugins/module_utils/acme/test_challenges.py @@ -25,16 +25,16 @@ from ansible_collections.community.internal_test_tools.tests.unit.compat.mock im def test_combine_identifier(): - assert combine_identifier('', '') == ':' - assert combine_identifier('a', 'b') == 'a:b' + assert combine_identifier("", "") == ":" + assert combine_identifier("a", "b") == "a:b" def test_split_identifier(): - assert split_identifier(':') == ['', ''] - assert split_identifier('a:b') == ['a', 'b'] - assert split_identifier('a:b:c') == ['a', 'b:c'] + assert split_identifier(":") == ["", ""] + assert split_identifier("a:b") == ["a", "b"] + assert split_identifier("a:b:c") == ["a", "b:c"] with pytest.raises(ModuleFailException) as exc: - split_identifier('a') + split_identifier("a") assert exc.value.msg == 'Identifier "a" is not of the form :' @@ -42,43 +42,43 @@ def test_challenge_from_to_json(): client = MagicMock() data = { - 'url': 'xxx', - 'type': 'type', - 'status': 'valid', + "url": "xxx", + "type": "type", + "status": "valid", } client.version = 2 challenge = Challenge.from_json(client, data) assert challenge.data == data - assert challenge.type == 'type' - assert challenge.url == 'xxx' - assert challenge.status == 'valid' + assert challenge.type == "type" + assert challenge.url == "xxx" + assert challenge.status == "valid" assert challenge.token is None assert challenge.to_json() == data data = { - 'type': 'type', - 'status': 'valid', - 'token': 'foo', + "type": "type", + "status": "valid", + "token": "foo", } - challenge = Challenge.from_json(None, data, url='xxx') + challenge = Challenge.from_json(None, data, url="xxx") assert challenge.data == data - assert challenge.type == 'type' - assert challenge.url == 'xxx' - assert challenge.status == 'valid' - assert challenge.token == 'foo' + assert challenge.type == "type" + assert challenge.url == "xxx" + assert challenge.status == "valid" + assert challenge.token == "foo" assert challenge.to_json() == data data = { - 'uri': 'xxx', - 'type': 'type', - 'status': 'valid', + "uri": "xxx", + "type": "type", + "status": "valid", } client.version = 1 challenge = Challenge.from_json(client, data) assert challenge.data == data - assert challenge.type == 'type' - assert challenge.url == 'xxx' - assert challenge.status == 'valid' + assert challenge.type == "type" + assert challenge.url == "xxx" + assert challenge.status == "valid" assert challenge.token is None assert challenge.to_json() == data @@ -88,93 +88,93 @@ def test_authorization_from_to_json(): client.version = 2 data = { - 'challenges': [], - 'status': 'valid', - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "challenges": [], + "status": "valid", + "identifier": { + "type": "dns", + "value": "example.com", }, } - authz = Authorization.from_json(client, data, 'xxx') - assert authz.url == 'xxx' - assert authz.status == 'valid' - assert authz.identifier == 'example.com' - assert authz.identifier_type == 'dns' + authz = Authorization.from_json(client, data, "xxx") + assert authz.url == "xxx" + assert authz.status == "valid" + assert authz.identifier == "example.com" + assert authz.identifier_type == "dns" assert authz.challenges == [] assert authz.to_json() == { - 'uri': 'xxx', - 'challenges': [], - 'status': 'valid', - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "uri": "xxx", + "challenges": [], + "status": "valid", + "identifier": { + "type": "dns", + "value": "example.com", }, } data = { - 'challenges': [ + "challenges": [ { - 'url': 'xxxyyy', - 'type': 'type', - 'status': 'valid', + "url": "xxxyyy", + "type": "type", + "status": "valid", } ], - 'status': 'valid', - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "status": "valid", + "identifier": { + "type": "dns", + "value": "example.com", }, - 'wildcard': True, + "wildcard": True, } - authz = Authorization.from_json(client, data, 'xxx') - assert authz.url == 'xxx' - assert authz.status == 'valid' - assert authz.identifier == '*.example.com' - assert authz.identifier_type == 'dns' + authz = Authorization.from_json(client, data, "xxx") + assert authz.url == "xxx" + assert authz.status == "valid" + assert authz.identifier == "*.example.com" + assert authz.identifier_type == "dns" assert len(authz.challenges) == 1 assert authz.challenges[0].data == { - 'url': 'xxxyyy', - 'type': 'type', - 'status': 'valid', + "url": "xxxyyy", + "type": "type", + "status": "valid", } assert authz.to_json() == { - 'uri': 'xxx', - 'challenges': [ + "uri": "xxx", + "challenges": [ { - 'url': 'xxxyyy', - 'type': 'type', - 'status': 'valid', + "url": "xxxyyy", + "type": "type", + "status": "valid", } ], - 'status': 'valid', - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "status": "valid", + "identifier": { + "type": "dns", + "value": "example.com", }, - 'wildcard': True, + "wildcard": True, } client.version = 1 data = { - 'challenges': [], - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "challenges": [], + "identifier": { + "type": "dns", + "value": "example.com", }, } - authz = Authorization.from_json(client, data, 'xxx') - assert authz.url == 'xxx' - assert authz.status == 'pending' - assert authz.identifier == 'example.com' - assert authz.identifier_type == 'dns' + authz = Authorization.from_json(client, data, "xxx") + assert authz.url == "xxx" + assert authz.status == "pending" + assert authz.identifier == "example.com" + assert authz.identifier_type == "dns" assert authz.challenges == [] assert authz.to_json() == { - 'uri': 'xxx', - 'challenges': [], - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "uri": "xxx", + "challenges": [], + "identifier": { + "type": "dns", + "value": "example.com", }, } @@ -184,60 +184,60 @@ def test_authorization_create_error(): client.version = 2 client.directory.directory = {} with pytest.raises(ACMEProtocolException) as exc: - Authorization.create(client, 'dns', 'example.com') + Authorization.create(client, "dns", "example.com") - assert exc.value.msg == 'ACME endpoint does not support pre-authorization.' + assert exc.value.msg == "ACME endpoint does not support pre-authorization." def test_wait_for_validation_error(): client = MagicMock() client.version = 2 data = { - 'challenges': [ + "challenges": [ { - 'url': 'xxxyyy1', - 'type': 'dns-01', - 'status': 'invalid', - 'error': { - 'type': 'dns-failed', - 'subproblems': [ + "url": "xxxyyy1", + "type": "dns-01", + "status": "invalid", + "error": { + "type": "dns-failed", + "subproblems": [ { - 'type': 'subproblem', - 'detail': 'example.com DNS-01 validation failed', + "type": "subproblem", + "detail": "example.com DNS-01 validation failed", }, - ] + ], }, }, { - 'url': 'xxxyyy2', - 'type': 'http-01', - 'status': 'invalid', - 'error': { - 'type': 'http-failed', - 'subproblems': [ + "url": "xxxyyy2", + "type": "http-01", + "status": "invalid", + "error": { + "type": "http-failed", + "subproblems": [ { - 'type': 'subproblem', - 'detail': 'example.com HTTP-01 validation failed', + "type": "subproblem", + "detail": "example.com HTTP-01 validation failed", }, - ] + ], }, }, { - 'url': 'xxxyyy3', - 'type': 'something-else', - 'status': 'valid', + "url": "xxxyyy3", + "type": "something-else", + "status": "valid", }, ], - 'status': 'invalid', - 'identifier': { - 'type': 'dns', - 'value': 'example.com', + "status": "invalid", + "identifier": { + "type": "dns", + "value": "example.com", }, } client.get_request = MagicMock(return_value=(data, {})) - authz = Authorization.from_json(client, data, 'xxx') + authz = Authorization.from_json(client, data, "xxx") with pytest.raises(ACMEProtocolException) as exc: - authz.wait_for_validation(client, 'dns') + authz.wait_for_validation(client, "dns") assert exc.value.msg == ( 'Failed to validate challenge for dns:example.com: Status is "invalid". Challenge dns-01: Error dns-failed Subproblems:\n' @@ -245,8 +245,8 @@ def test_wait_for_validation_error(): '(http-01.0) Error subproblem: "example.com HTTP-01 validation failed".' ) data = data.copy() - data['uri'] = 'xxx' + data["uri"] = "xxx" assert exc.value.module_fail_args == { - 'identifier': 'dns:example.com', - 'authorization': data, + "identifier": "dns:example.com", + "authorization": data, } diff --git a/tests/unit/plugins/module_utils/acme/test_errors.py b/tests/unit/plugins/module_utils/acme/test_errors.py index adfe1e77..f54ebbfc 100644 --- a/tests/unit/plugins/module_utils/acme/test_errors.py +++ b/tests/unit/plugins/module_utils/acme/test_errors.py @@ -21,97 +21,78 @@ from ansible_collections.community.internal_test_tools.tests.unit.compat.mock im TEST_FORMAT_ERROR_PROBLEM = [ ( { - 'type': 'foo', + "type": "foo", }, - '', - 'Error foo' + "", + "Error foo", ), + ({"type": "foo", "title": "bar"}, "", 'Error "bar" (foo)'), + ({"type": "foo", "detail": "bar baz"}, "", 'Error foo: "bar baz"'), + ({"type": "foo", "subproblems": []}, "", "Error foo Subproblems:"), ( { - 'type': 'foo', - 'title': 'bar' - }, - '', - 'Error "bar" (foo)' - ), - ( - { - 'type': 'foo', - 'detail': 'bar baz' - }, - '', - 'Error foo: "bar baz"' - ), - ( - { - 'type': 'foo', - 'subproblems': [] - }, - '', - 'Error foo Subproblems:' - ), - ( - { - 'type': 'foo', - 'subproblems': [ + "type": "foo", + "subproblems": [ { - 'type': 'bar', + "type": "bar", }, - ] + ], }, - '', - 'Error foo Subproblems:\n(0) Error bar' + "", + "Error foo Subproblems:\n(0) Error bar", ), ( { - 'type': 'foo', - 'subproblems': [ + "type": "foo", + "subproblems": [ { - 'type': 'bar', - 'subproblems': [ + "type": "bar", + "subproblems": [ { - 'type': 'baz', + "type": "baz", }, - ] + ], }, - ] + ], }, - '', - 'Error foo Subproblems:\n(0) Error bar Subproblems:\n(0.0) Error baz' + "", + "Error foo Subproblems:\n(0) Error bar Subproblems:\n(0.0) Error baz", ), ( { - 'type': 'foo', - 'title': 'Foo Error', - 'detail': 'Foo went wrong', - 'subproblems': [ + "type": "foo", + "title": "Foo Error", + "detail": "Foo went wrong", + "subproblems": [ { - 'type': 'bar', - 'detail': 'Bar went wrong', - 'subproblems': [ + "type": "bar", + "detail": "Bar went wrong", + "subproblems": [ { - 'type': 'baz', - 'title': 'Baz Error', + "type": "baz", + "title": "Baz Error", }, - ] + ], }, { - 'type': 'bar2', - 'title': 'Bar 2 Error', - 'detail': 'Bar really went wrong' + "type": "bar2", + "title": "Bar 2 Error", + "detail": "Bar really went wrong", }, - ] + ], }, - 'X.', + "X.", 'Error "Foo Error" (foo): "Foo went wrong" Subproblems:\n' '(X.0) Error bar: "Bar went wrong" Subproblems:\n' '(X.0.0) Error "Baz Error" (baz)\n' - '(X.1) Error "Bar 2 Error" (bar2): "Bar really went wrong"' + '(X.1) Error "Bar 2 Error" (bar2): "Bar really went wrong"', ), ] -@pytest.mark.parametrize("problem, subproblem_prefix, result", TEST_FORMAT_ERROR_PROBLEM) +@pytest.mark.parametrize( + "problem, subproblem_prefix, result", TEST_FORMAT_ERROR_PROBLEM +) def test_format_error_problem(problem, subproblem_prefix, result): res = format_error_problem(problem, subproblem_prefix) assert res == result @@ -119,14 +100,14 @@ def test_format_error_problem(problem, subproblem_prefix, result): def create_regular_response(response_text): response = MagicMock() - response.read = MagicMock(return_value=response_text.encode('utf-8')) + response.read = MagicMock(return_value=response_text.encode("utf-8")) response.closed = False return response def create_error_response(): response = MagicMock() - response.read = MagicMock(side_effect=AttributeError('read')) + response.read = MagicMock(side_effect=AttributeError("read")) response.closed = True return response @@ -142,220 +123,219 @@ TEST_ACME_PROTOCOL_EXCEPTION = [ ( {}, None, - 'ACME request failed.', - { - }, + "ACME request failed.", + {}, ), ( { - 'msg': 'Foo', - 'extras': { - 'foo': 'bar', + "msg": "Foo", + "extras": { + "foo": "bar", }, }, None, - 'Foo.', + "Foo.", { - 'foo': 'bar', + "foo": "bar", }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, + "info": { + "url": "https://ca.example.com/foo", + "status": 201, }, }, None, - 'ACME request failed for https://ca.example.com/foo with HTTP status 201 Created.', + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created.", { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, + "http_url": "https://ca.example.com/foo", + "http_status": 201, }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, + "info": { + "url": "https://ca.example.com/foo", + "status": 201, }, - 'response': create_regular_response('xxx'), - }, - None, - 'ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The raw error result: xxx', - { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, - }, - ), - ( - { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, - }, - 'response': create_regular_response('xxx'), - }, - create_decode_error('yyy'), - 'ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The raw error result: xxx', - { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, - }, - ), - ( - { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, - }, - 'response': create_regular_response('xxx'), - }, - lambda content: dict(foo='bar'), - "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}", - { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, - }, - ), - ( - { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, - }, - 'response': create_error_response(), - }, - None, - 'ACME request failed for https://ca.example.com/foo with HTTP status 201 Created.', - { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, - }, - ), - ( - { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, - 'body': 'xxx', - }, - 'response': create_error_response(), - }, - lambda content: dict(foo='bar'), - "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}", - { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, - }, - ), - ( - { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, - }, - 'content': 'xxx', + "response": create_regular_response("xxx"), }, None, "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The raw error result: xxx", { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, + "http_url": "https://ca.example.com/foo", + "http_status": 201, }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 400, + "info": { + "url": "https://ca.example.com/foo", + "status": 201, }, - 'content_json': { - 'foo': 'bar', + "response": create_regular_response("xxx"), + }, + create_decode_error("yyy"), + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The raw error result: xxx", + { + "http_url": "https://ca.example.com/foo", + "http_status": 201, + }, + ), + ( + { + "info": { + "url": "https://ca.example.com/foo", + "status": 201, + }, + "response": create_regular_response("xxx"), + }, + lambda content: dict(foo="bar"), + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}", + { + "http_url": "https://ca.example.com/foo", + "http_status": 201, + }, + ), + ( + { + "info": { + "url": "https://ca.example.com/foo", + "status": 201, + }, + "response": create_error_response(), + }, + None, + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created.", + { + "http_url": "https://ca.example.com/foo", + "http_status": 201, + }, + ), + ( + { + "info": { + "url": "https://ca.example.com/foo", + "status": 201, + "body": "xxx", + }, + "response": create_error_response(), + }, + lambda content: dict(foo="bar"), + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}", + { + "http_url": "https://ca.example.com/foo", + "http_status": 201, + }, + ), + ( + { + "info": { + "url": "https://ca.example.com/foo", + "status": 201, + }, + "content": "xxx", + }, + None, + "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The raw error result: xxx", + { + "http_url": "https://ca.example.com/foo", + "http_status": 201, + }, + ), + ( + { + "info": { + "url": "https://ca.example.com/foo", + "status": 400, + }, + "content_json": { + "foo": "bar", + }, + "extras": { + "bar": "baz", }, - 'extras': { - 'bar': 'baz', - } }, None, "ACME request failed for https://ca.example.com/foo with HTTP status 400 Bad Request. The JSON error result: {'foo': 'bar'}", { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 400, - 'bar': 'baz', + "http_url": "https://ca.example.com/foo", + "http_status": 400, + "bar": "baz", }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 201, + "info": { + "url": "https://ca.example.com/foo", + "status": 201, }, - 'content_json': { - 'type': 'foo', + "content_json": { + "type": "foo", }, }, None, "ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'type': 'foo'}", { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 201, + "http_url": "https://ca.example.com/foo", + "http_status": 201, }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 400, + "info": { + "url": "https://ca.example.com/foo", + "status": 400, }, - 'content_json': { - 'type': 'foo', + "content_json": { + "type": "foo", }, }, None, "ACME request failed for https://ca.example.com/foo with status 400 Bad Request. Error foo.", { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 400, - 'problem': { - 'type': 'foo', + "http_url": "https://ca.example.com/foo", + "http_status": 400, + "problem": { + "type": "foo", }, - 'subproblems': [], + "subproblems": [], }, ), ( { - 'info': { - 'url': 'https://ca.example.com/foo', - 'status': 400, + "info": { + "url": "https://ca.example.com/foo", + "status": 400, }, - 'content_json': { - 'type': 'foo', - 'title': 'Foo Error', - 'subproblems': [ + "content_json": { + "type": "foo", + "title": "Foo Error", + "subproblems": [ { - 'type': 'bar', - 'detail': 'This is a bar error', - 'details': 'Details.', + "type": "bar", + "detail": "This is a bar error", + "details": "Details.", }, ], }, }, None, - "ACME request failed for https://ca.example.com/foo with status 400 Bad Request. Error \"Foo Error\" (foo). Subproblems:\n" - "(0) Error bar: \"This is a bar error\".", + 'ACME request failed for https://ca.example.com/foo with status 400 Bad Request. Error "Foo Error" (foo). Subproblems:\n' + '(0) Error bar: "This is a bar error".', { - 'http_url': 'https://ca.example.com/foo', - 'http_status': 400, - 'problem': { - 'type': 'foo', - 'title': 'Foo Error', + "http_url": "https://ca.example.com/foo", + "http_status": 400, + "problem": { + "type": "foo", + "title": "Foo Error", }, - 'subproblems': [ + "subproblems": [ { - 'type': 'bar', - 'detail': 'This is a bar error', - 'details': 'Details.', + "type": "bar", + "detail": "This is a bar error", + "details": "Details.", }, ], }, diff --git a/tests/unit/plugins/module_utils/acme/test_io.py b/tests/unit/plugins/module_utils/acme/test_io.py index e8bf65d8..46c8a317 100644 --- a/tests/unit/plugins/module_utils/acme/test_io.py +++ b/tests/unit/plugins/module_utils/acme/test_io.py @@ -22,14 +22,14 @@ TEST_TEXT = r"""1234 def test_read_file(tmpdir): - fn = tmpdir / 'test.txt' + fn = tmpdir / "test.txt" fn.write(TEST_TEXT) - assert read_file(str(fn), 't') == TEST_TEXT - assert read_file(str(fn), 'b') == TEST_TEXT.encode('utf-8') + assert read_file(str(fn), "t") == TEST_TEXT + assert read_file(str(fn), "b") == TEST_TEXT.encode("utf-8") def test_write_file(tmpdir): - fn = tmpdir / 'test.txt' + fn = tmpdir / "test.txt" module = MagicMock() - write_file(module, str(fn), TEST_TEXT.encode('utf-8')) + write_file(module, str(fn), TEST_TEXT.encode("utf-8")) assert fn.read() == TEST_TEXT diff --git a/tests/unit/plugins/module_utils/acme/test_orders.py b/tests/unit/plugins/module_utils/acme/test_orders.py index 703ae670..733e7c92 100644 --- a/tests/unit/plugins/module_utils/acme/test_orders.py +++ b/tests/unit/plugins/module_utils/acme/test_orders.py @@ -22,15 +22,15 @@ def test_order_from_json(): client = MagicMock() data = { - 'status': 'valid', - 'identifiers': [], - 'authorizations': [], + "status": "valid", + "identifiers": [], + "authorizations": [], } client.version = 2 - order = Order.from_json(client, data, 'xxx') + order = Order.from_json(client, data, "xxx") assert order.data == data - assert order.url == 'xxx' - assert order.status == 'valid' + assert order.url == "xxx" + assert order.status == "valid" assert order.identifiers == [] assert order.finalize_uri is None assert order.certificate_uri is None @@ -43,15 +43,17 @@ def test_wait_for_finalization_error(): client.version = 2 data = { - 'status': 'invalid', - 'identifiers': [], - 'authorizations': [], + "status": "invalid", + "identifiers": [], + "authorizations": [], } - order = Order.from_json(client, data, 'xxx') + order = Order.from_json(client, data, "xxx") client.get_request = MagicMock(return_value=(data, {})) with pytest.raises(ACMEProtocolException) as exc: order.wait_for_finalization(client) - assert exc.value.msg.startswith('Failed to wait for order to complete; got status "invalid". The JSON result: ') + assert exc.value.msg.startswith( + 'Failed to wait for order to complete; got status "invalid". The JSON result: ' + ) assert exc.value.module_fail_args == {} diff --git a/tests/unit/plugins/module_utils/acme/test_utils.py b/tests/unit/plugins/module_utils/acme/test_utils.py index 276171ad..dc29bdbd 100644 --- a/tests/unit/plugins/module_utils/acme/test_utils.py +++ b/tests/unit/plugins/module_utils/acme/test_utils.py @@ -39,38 +39,34 @@ TEST_LINKS_HEADER = [ [], ), ( - { - 'link': '; rel="bar"' - }, + {"link": '; rel="bar"'}, [ - ('foo', 'bar'), + ("foo", "bar"), + ], + ), + ( + {"link": '; rel="bar", ; rel="bam"'}, + [ + ("foo", "bar"), + ("baz", "bam"), ], ), ( { - 'link': '; rel="bar", ; rel="bam"' + "link": '; rel="preconnect", ; rel="preconnect", ; rel="preconnect"' }, [ - ('foo', 'bar'), - ('baz', 'bam'), - ], - ), - ( - { - 'link': '; rel="preconnect", ; rel="preconnect", ; rel="preconnect"' - }, - [ - ('https://one.example.com', 'preconnect'), - ('https://two.example.com', 'preconnect'), - ('https://three.example.com', 'preconnect'), + ("https://one.example.com", "preconnect"), + ("https://two.example.com", "preconnect"), + ("https://three.example.com", "preconnect"), ], ), ] TEST_RETRY_AFTER_HEADER = [ - ('120', datetime.datetime(2024, 4, 29, 0, 2, 0)), - ('Wed, 21 Oct 2015 07:28:00 GMT', datetime.datetime(2015, 10, 21, 7, 28, 0)), + ("120", datetime.datetime(2024, 4, 29, 0, 2, 0)), + ("Wed, 21 Oct 2015 07:28:00 GMT", datetime.datetime(2015, 10, 21, 7, 28, 0)), ] @@ -81,9 +77,9 @@ TEST_COMPUTE_CERT_ID = [ not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), serial_number=1, subject_key_identifier=None, - authority_key_identifier=b'\x00\xff', + authority_key_identifier=b"\x00\xff", ), - 'AP8.AQ', + "AP8.AQ", ), ( # AKI, serial number, and expected result taken from @@ -93,21 +89,21 @@ TEST_COMPUTE_CERT_ID = [ not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), serial_number=0x87654321, subject_key_identifier=None, - authority_key_identifier=b'\x69\x88\x5B\x6B\x87\x46\x40\x41\xE1\xB3\x7B\x84\x7B\xA0\xAE\x2C\xDE\x01\xC8\xD4', + authority_key_identifier=b"\x69\x88\x5b\x6b\x87\x46\x40\x41\xe1\xb3\x7b\x84\x7b\xa0\xae\x2c\xde\x01\xc8\xd4", ), - 'aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE', + "aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE", ), ] @pytest.mark.parametrize("value, result", NOPAD_B64) def test_nopad_b64(value, result): - assert nopad_b64(value.encode('utf-8')) == result + assert nopad_b64(value.encode("utf-8")) == result @pytest.mark.parametrize("pem, der", TEST_PEM_DERS) def test_pem_to_der(pem, der, tmpdir): - fn = tmpdir / 'test.pem' + fn = tmpdir / "test.pem" fn.write(pem) assert pem_to_der(str(fn)) == der @@ -126,7 +122,9 @@ def test_process_links(value, expected_result): @pytest.mark.parametrize("value, expected_result", TEST_RETRY_AFTER_HEADER) def test_parse_retry_after(value, expected_result): - assert expected_result == parse_retry_after(value, now=datetime.datetime(2024, 4, 29, 0, 0, 0)) + assert expected_result == parse_retry_after( + value, now=datetime.datetime(2024, 4, 29, 0, 0, 0) + ) @pytest.mark.parametrize("cert_info, expected_result", TEST_COMPUTE_CERT_ID) diff --git a/tests/unit/plugins/module_utils/crypto/test_asn1.py b/tests/unit/plugins/module_utils/crypto/test_asn1.py index f608468f..064c41b1 100644 --- a/tests/unit/plugins/module_utils/crypto/test_asn1.py +++ b/tests/unit/plugins/module_utils/crypto/test_asn1.py @@ -21,52 +21,89 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto._asn1 impo TEST_CASES = [ - ('UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - - ('EXPLICIT:10,UTF8:Hello World', b'\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('EXPLICIT:12U,UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('EXPLICIT:10A,UTF8:Hello World', b'\x6a\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('EXPLICIT:10P,UTF8:Hello World', b'\xea\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('EXPLICIT:10C,UTF8:Hello World', b'\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('EXPLICIT:1024P,UTF8:Hello World', b'\xff\x88\x00\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - - ('IMPLICIT:10,UTF8:Hello World', b'\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('IMPLICIT:12U,UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('IMPLICIT:10A,UTF8:Hello World', b'\x4a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('IMPLICIT:10P,UTF8:Hello World', b'\xca\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('IMPLICIT:10C,UTF8:Hello World', b'\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - ('IMPLICIT:1024P,UTF8:Hello World', b'\xdf\x88\x00\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), - + ("UTF8:Hello World", b"\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64"), + ( + "EXPLICIT:10,UTF8:Hello World", + b"\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "EXPLICIT:12U,UTF8:Hello World", + b"\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "EXPLICIT:10A,UTF8:Hello World", + b"\x6a\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "EXPLICIT:10P,UTF8:Hello World", + b"\xea\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "EXPLICIT:10C,UTF8:Hello World", + b"\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "EXPLICIT:1024P,UTF8:Hello World", + b"\xff\x88\x00\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:10,UTF8:Hello World", + b"\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:12U,UTF8:Hello World", + b"\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:10A,UTF8:Hello World", + b"\x4a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:10P,UTF8:Hello World", + b"\xca\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:10C,UTF8:Hello World", + b"\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), + ( + "IMPLICIT:1024P,UTF8:Hello World", + b"\xdf\x88\x00\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64", + ), # Tests large data lengths, special logic for the length octet encoding. - ('UTF8:' + ('A' * 600), b'\x0c\x82\x02\x58' + (b'\x41' * 600)), - + ("UTF8:" + ("A" * 600), b"\x0c\x82\x02\x58" + (b"\x41" * 600)), # This isn't valid with openssl asn1parse but has been validated against an ASN.1 parser. OpenSSL seems to read the # data u"café" encoded as UTF-8 bytes b"caf\xc3\xa9", decodes that internally with latin-1 (or similar variant) as # u"café" then encodes that to UTF-8 b"caf\xc3\x83\xc2\xa9" for the UTF8String. Ultimately openssl is wrong here # so we keep our assertion happening. - (u'UTF8:café', b'\x0c\x05\x63\x61\x66\xc3\xa9'), + (u"UTF8:café", b"\x0c\x05\x63\x61\x66\xc3\xa9"), ] -@pytest.mark.parametrize('value, expected', TEST_CASES) +@pytest.mark.parametrize("value, expected", TEST_CASES) def test_serialize_asn1_string_as_der(value, expected): actual = serialize_asn1_string_as_der(value) print("%s | %s" % (value, base64.b16encode(actual).decode())) assert actual == expected -@pytest.mark.parametrize('value', [ - 'invalid', - 'EXPLICIT,UTF:value', -]) +@pytest.mark.parametrize( + "value", + [ + "invalid", + "EXPLICIT,UTF:value", + ], +) def test_serialize_asn1_string_as_der_invalid_format(value): - expected = "The ASN.1 serialized string must be in the format [modifier,]type[:value]" + expected = ( + "The ASN.1 serialized string must be in the format [modifier,]type[:value]" + ) with pytest.raises(ValueError, match=re.escape(expected)): serialize_asn1_string_as_der(value) def test_serialize_asn1_string_as_der_invalid_type(): - expected = "The ASN.1 serialized string is not a known type \"OID\", only UTF8 types are supported" + expected = 'The ASN.1 serialized string is not a known type "OID", only UTF8 types are supported' with pytest.raises(ValueError, match=re.escape(expected)): serialize_asn1_string_as_der("OID:1.2.3.4") @@ -77,17 +114,23 @@ def test_pack_asn_invalid_class(): @pytest.mark.skip() # This is to just to build the test case assertions and shouldn't run normally. -@pytest.mark.parametrize('value, expected', TEST_CASES) +@pytest.mark.parametrize("value, expected", TEST_CASES) def test_test_cases(value, expected, tmp_path): - test_file = tmp_path / 'test.der' - subprocess.run(['openssl', 'asn1parse', '-genstr', value, '-noout', '-out', test_file], check=True) + test_file = tmp_path / "test.der" + subprocess.run( + ["openssl", "asn1parse", "-genstr", value, "-noout", "-out", test_file], + check=True, + ) - with open(test_file, mode='rb') as fd: + with open(test_file, mode="rb") as fd: b_data = fd.read() hex_str = base64.b16encode(b_data).decode().lower() - print("%s | \\x%s" % (value, "\\x".join([hex_str[i:i + 2] for i in range(0, len(hex_str), 2)]))) + print( + "%s | \\x%s" + % (value, "\\x".join([hex_str[i : i + 2] for i in range(0, len(hex_str), 2)])) + ) # This is a know edge case where openssl asn1parse does not work properly. - if value != u'UTF8:café': + if value != u"UTF8:café": assert b_data == expected diff --git a/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py b/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py index 9dbdc75c..e33752b4 100644 --- a/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py +++ b/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py @@ -28,100 +28,147 @@ from ansible_collections.community.crypto.plugins.module_utils.version import ( from cryptography.x509 import NameAttribute, oid -@pytest.mark.parametrize('unicode, idna, cycled_unicode', [ - (u'..', u'..', None), - (u'foo.com', u'foo.com', None), - (u'.foo.com.', u'.foo.com.', None), - (u'*.foo.com', u'*.foo.com', None), - (u'straße', u'xn--strae-oqa', None), - (u'ffóò.ḃâŗ.çøṁ', u'xn--ff-3jad.xn--2ca8uh37e.xn--7ca8a981n', u'ffóò.ḃâŗ.çøṁ'), - (u'*.☺.', u'*.xn--74h.', None), -]) +@pytest.mark.parametrize( + "unicode, idna, cycled_unicode", + [ + (u"..", u"..", None), + (u"foo.com", u"foo.com", None), + (u".foo.com.", u".foo.com.", None), + (u"*.foo.com", u"*.foo.com", None), + (u"straße", u"xn--strae-oqa", None), + (u"ffóò.ḃâŗ.çøṁ", u"xn--ff-3jad.xn--2ca8uh37e.xn--7ca8a981n", u"ffóò.ḃâŗ.çøṁ"), + (u"*.☺.", u"*.xn--74h.", None), + ], +) def test_adjust_idn(unicode, idna, cycled_unicode): if cycled_unicode is None: cycled_unicode = unicode - result = _adjust_idn(unicode, 'ignore') + result = _adjust_idn(unicode, "ignore") print(result, unicode) assert result == unicode - result = _adjust_idn(idna, 'ignore') + result = _adjust_idn(idna, "ignore") print(result, idna) assert result == idna - result = _adjust_idn(unicode, 'unicode') + result = _adjust_idn(unicode, "unicode") print(result, unicode) assert result == unicode - result = _adjust_idn(idna, 'unicode') + result = _adjust_idn(idna, "unicode") print(result, cycled_unicode) assert result == cycled_unicode - result = _adjust_idn(unicode, 'idna') + result = _adjust_idn(unicode, "idna") print(result, idna) assert result == idna - result = _adjust_idn(idna, 'idna') + result = _adjust_idn(idna, "idna") print(result, idna) assert result == idna -@pytest.mark.parametrize('value, idn_rewrite, message', [ - (u'bar', 'foo', re.escape(u'Invalid value for idn_rewrite: "foo"')), -]) +@pytest.mark.parametrize( + "value, idn_rewrite, message", + [ + (u"bar", "foo", re.escape(u'Invalid value for idn_rewrite: "foo"')), + ], +) def test_adjust_idn_fail_valueerror(value, idn_rewrite, message): with pytest.raises(ValueError, match=message): _adjust_idn(value, idn_rewrite) -@pytest.mark.parametrize('value, idn_rewrite, message', [ - ( - u'xn--a', - 'unicode', - u'''^Error while transforming part u?"xn\\-\\-a" of IDNA DNS name u?"xn\\-\\-a" to Unicode\\.''' - u''' IDNA2008 transformation resulted in "Codepoint U\\+0080 at position 1 of u?'\\\\x80' not allowed",''' - u''' IDNA2003 transformation resulted in "(decoding with 'idna' codec failed''' - u''' \\(UnicodeError: |'idna' codec can't decode byte 0x78 in position 0: )?Invalid character u?'\\\\x80'\\)?"\\.$''' - ), -]) +@pytest.mark.parametrize( + "value, idn_rewrite, message", + [ + ( + u"xn--a", + "unicode", + u"""^Error while transforming part u?"xn\\-\\-a" of IDNA DNS name u?"xn\\-\\-a" to Unicode\\.""" + u""" IDNA2008 transformation resulted in "Codepoint U\\+0080 at position 1 of u?'\\\\x80' not allowed",""" + u""" IDNA2003 transformation resulted in "(decoding with 'idna' codec failed""" + u""" \\(UnicodeError: |'idna' codec can't decode byte 0x78 in position 0: )?Invalid character u?'\\\\x80'\\)?"\\.$""", + ), + ], +) def test_adjust_idn_fail_user_error(value, idn_rewrite, message): with pytest.raises(OpenSSLObjectError, match=message): _adjust_idn(value, idn_rewrite) def test_cryptography_get_name_invalid_prefix(): - with pytest.raises(OpenSSLObjectError, match="^Cannot parse Subject Alternative Name"): - cryptography_get_name('fake:value') + with pytest.raises( + OpenSSLObjectError, match="^Cannot parse Subject Alternative Name" + ): + cryptography_get_name("fake:value") def test_cryptography_get_name_other_name_no_oid(): - with pytest.raises(OpenSSLObjectError, match="Cannot parse Subject Alternative Name otherName"): - cryptography_get_name('otherName:value') + with pytest.raises( + OpenSSLObjectError, match="Cannot parse Subject Alternative Name otherName" + ): + cryptography_get_name("otherName:value") def test_cryptography_get_name_other_name_utfstring(): - actual = cryptography_get_name('otherName:1.3.6.1.4.1.311.20.2.3;UTF8:Hello World') - assert actual.type_id.dotted_string == '1.3.6.1.4.1.311.20.2.3' - assert actual.value == b'\x0c\x0bHello World' + actual = cryptography_get_name("otherName:1.3.6.1.4.1.311.20.2.3;UTF8:Hello World") + assert actual.type_id.dotted_string == "1.3.6.1.4.1.311.20.2.3" + assert actual.value == b"\x0c\x0bHello World" -@pytest.mark.parametrize('name, options, expected', [ - (b'CN=x ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'x '), b'')), - (b'CN=\\ ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u' '), b'')), - (b'CN=\\#', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'#'), b'')), - (b'CN=#402032', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'@ 2'), b'')), - (b'CN = x ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'x '), b'')), - (b'CN = x\\, ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'x, '), b'')), - (b'CN = x\\40 ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'x@ '), b'')), - (b'CN = \\ , / ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u' '), b', / ')), - (b'CN = \\ , / ', {'sep': b'/'}, (NameAttribute(oid.NameOID.COMMON_NAME, u' , '), b'/ ')), - (b'CN = \\ , / ', {'decode_remainder': False}, (NameAttribute(oid.NameOID.COMMON_NAME, u'\\ , / '), b'')), - # Some examples from https://datatracker.ietf.org/doc/html/rfc4514#section-4: - (b'CN=James \\"Jim\\" Smith\\, III', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'James "Jim" Smith, III'), b'')), - (b'CN=Before\\0dAfter', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'Before\x0dAfter'), b'')), - (b'1.3.6.1.4.1.1466.0=#04024869', {}, (NameAttribute(oid.ObjectIdentifier(u'1.3.6.1.4.1.1466.0'), u'\x04\x02Hi'), b'')), - (b'CN=Lu\\C4\\8Di\\C4\\87', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u'Lučić'), b'')), -]) +@pytest.mark.parametrize( + "name, options, expected", + [ + (b"CN=x ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"x "), b"")), + (b"CN=\\ ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u" "), b"")), + (b"CN=\\#", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"#"), b"")), + (b"CN=#402032", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"@ 2"), b"")), + (b"CN = x ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"x "), b"")), + (b"CN = x\\, ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"x, "), b"")), + (b"CN = x\\40 ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u"x@ "), b"")), + ( + b"CN = \\ , / ", + {}, + (NameAttribute(oid.NameOID.COMMON_NAME, u" "), b", / "), + ), + ( + b"CN = \\ , / ", + {"sep": b"/"}, + (NameAttribute(oid.NameOID.COMMON_NAME, u" , "), b"/ "), + ), + ( + b"CN = \\ , / ", + {"decode_remainder": False}, + (NameAttribute(oid.NameOID.COMMON_NAME, u"\\ , / "), b""), + ), + # Some examples from https://datatracker.ietf.org/doc/html/rfc4514#section-4: + ( + b'CN=James \\"Jim\\" Smith\\, III', + {}, + (NameAttribute(oid.NameOID.COMMON_NAME, u'James "Jim" Smith, III'), b""), + ), + ( + b"CN=Before\\0dAfter", + {}, + (NameAttribute(oid.NameOID.COMMON_NAME, u"Before\x0dAfter"), b""), + ), + ( + b"1.3.6.1.4.1.1466.0=#04024869", + {}, + ( + NameAttribute(oid.ObjectIdentifier("1.3.6.1.4.1.1466.0"), u"\x04\x02Hi"), + b"", + ), + ), + ( + b"CN=Lu\\C4\\8Di\\C4\\87", + {}, + (NameAttribute(oid.NameOID.COMMON_NAME, u"Lučić"), b""), + ), + ], +) def test_parse_dn_component(name, options, expected): result = _parse_dn_component(name, **options) print(result, expected) @@ -131,42 +178,77 @@ def test_parse_dn_component(name, options, expected): # Cryptography < 2.9 does not allow empty strings # (https://github.com/pyca/cryptography/commit/87b2749c52e688c809f1861e55d958c64147493c) # Cryptoraphy 43.0.0+ also doesn't allow this anymore -if LooseVersion('2.9') <= LooseVersion(cryptography.__version__) < LooseVersion('43.0.0'): - @pytest.mark.parametrize('name, options, expected', [ - (b'CN=', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u''), b'')), - (b'CN= ', {}, (NameAttribute(oid.NameOID.COMMON_NAME, u''), b'')), - ]) +if ( + LooseVersion("2.9") + <= LooseVersion(cryptography.__version__) + < LooseVersion("43.0.0") +): + + @pytest.mark.parametrize( + "name, options, expected", + [ + (b"CN=", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u""), b"")), + (b"CN= ", {}, (NameAttribute(oid.NameOID.COMMON_NAME, u""), b"")), + ], + ) def test_parse_dn_component_not_py26(name, options, expected): result = _parse_dn_component(name, **options) print(result, expected) assert result == expected -@pytest.mark.parametrize('name, options, message', [ - (b'CN=\\0', {}, u'Hex escape sequence "\\0" incomplete at end of string'), - (b'CN=\\0,', {}, u'Hex escape sequence "\\0," has invalid second letter'), - (b'CN=#0,', {}, u'Invalid hex sequence entry "0,"'), -]) +@pytest.mark.parametrize( + "name, options, message", + [ + (b"CN=\\0", {}, u'Hex escape sequence "\\0" incomplete at end of string'), + (b"CN=\\0,", {}, u'Hex escape sequence "\\0," has invalid second letter'), + (b"CN=#0,", {}, u'Invalid hex sequence entry "0,"'), + ], +) def test_parse_dn_component_failure(name, options, message): - with pytest.raises(OpenSSLObjectError, match=u'^%s$' % re.escape(message)): + with pytest.raises(OpenSSLObjectError, match=u"^%s$" % re.escape(message)): _parse_dn_component(name, **options) -@pytest.mark.parametrize('name, expected', [ - (b'CN=foo', [NameAttribute(oid.NameOID.COMMON_NAME, u'foo')]), - (b'CN=foo,CN=bar', [NameAttribute(oid.NameOID.COMMON_NAME, u'foo'), NameAttribute(oid.NameOID.COMMON_NAME, u'bar')]), - (b'CN = foo , CN = bar', [NameAttribute(oid.NameOID.COMMON_NAME, u'foo '), NameAttribute(oid.NameOID.COMMON_NAME, u'bar')]), -]) +@pytest.mark.parametrize( + "name, expected", + [ + (b"CN=foo", [NameAttribute(oid.NameOID.COMMON_NAME, u"foo")]), + ( + b"CN=foo,CN=bar", + [ + NameAttribute(oid.NameOID.COMMON_NAME, u"foo"), + NameAttribute(oid.NameOID.COMMON_NAME, u"bar"), + ], + ), + ( + b"CN = foo , CN = bar", + [ + NameAttribute(oid.NameOID.COMMON_NAME, u"foo "), + NameAttribute(oid.NameOID.COMMON_NAME, u"bar"), + ], + ), + ], +) def test_parse_dn(name, expected): result = _parse_dn(name) print(result, expected) assert result == expected -@pytest.mark.parametrize('name, message', [ - (b'CN=\\0', u'Error while parsing distinguished name "CN=\\0": Hex escape sequence "\\0" incomplete at end of string'), - (b'CN=x,', u'Error while parsing distinguished name "CN=x,": unexpected end of string'), -]) +@pytest.mark.parametrize( + "name, message", + [ + ( + b"CN=\\0", + u'Error while parsing distinguished name "CN=\\0": Hex escape sequence "\\0" incomplete at end of string', + ), + ( + b"CN=x,", + u'Error while parsing distinguished name "CN=x,": unexpected end of string', + ), + ], +) def test_parse_dn_failure(name, message): - with pytest.raises(OpenSSLObjectError, match=u'^%s$' % re.escape(message)): + with pytest.raises(OpenSSLObjectError, match=u"^%s$" % re.escape(message)): _parse_dn(name) diff --git a/tests/unit/plugins/module_utils/crypto/test_math.py b/tests/unit/plugins/module_utils/crypto/test_math.py index df5424f2..d5200c59 100644 --- a/tests/unit/plugins/module_utils/crypto/test_math.py +++ b/tests/unit/plugins/module_utils/crypto/test_math.py @@ -20,98 +20,116 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.math impor ) -@pytest.mark.parametrize('f, e, m, result', [ - (0, 0, 5, 1), - (0, 1, 5, 0), - (2, 1, 5, 2), - (2, 2, 5, 4), - (2, 3, 5, 3), - (2, 10, 5, 4), -]) +@pytest.mark.parametrize( + "f, e, m, result", + [ + (0, 0, 5, 1), + (0, 1, 5, 0), + (2, 1, 5, 2), + (2, 2, 5, 4), + (2, 3, 5, 3), + (2, 10, 5, 4), + ], +) def test_binary_exp_mod(f, e, m, result): value = binary_exp_mod(f, e, m) print(value) assert value == result -@pytest.mark.parametrize('a, b, result', [ - (0, -123, -123), - (0, 123, 123), - (-123, 0, -123), - (123, 0, 123), - (-123, 1, 1), - (123, 1, 1), - (1, -123, -1), - (1, 123, 1), - (1024, 10, 2), -]) +@pytest.mark.parametrize( + "a, b, result", + [ + (0, -123, -123), + (0, 123, 123), + (-123, 0, -123), + (123, 0, 123), + (-123, 1, 1), + (123, 1, 1), + (1, -123, -1), + (1, 123, 1), + (1024, 10, 2), + ], +) def test_simple_gcd(a, b, result): value = simple_gcd(a, b) print(value) assert value == result -@pytest.mark.parametrize('n, result', [ - (-2, True), - (0, True), - (1, True), - (2, False), - (3, False), - (4, True), - (5, False), - (6, True), - (7, False), - (8, True), - (9, True), - (10, True), - (211, False), # the smallest prime number >= 200 -]) +@pytest.mark.parametrize( + "n, result", + [ + (-2, True), + (0, True), + (1, True), + (2, False), + (3, False), + (4, True), + (5, False), + (6, True), + (7, False), + (8, True), + (9, True), + (10, True), + (211, False), # the smallest prime number >= 200 + ], +) def test_quick_is_not_prime(n, result): value = quick_is_not_prime(n) print(value) assert value == result -@pytest.mark.parametrize('no, count, result', [ - (0, None, b''), - (0, 1, b'\x00'), - (0, 2, b'\x00\x00'), - (1, None, b'\x01'), - (1, 2, b'\x00\x01'), - (255, None, b'\xff'), - (256, None, b'\x01\x00'), -]) +@pytest.mark.parametrize( + "no, count, result", + [ + (0, None, b""), + (0, 1, b"\x00"), + (0, 2, b"\x00\x00"), + (1, None, b"\x01"), + (1, 2, b"\x00\x01"), + (255, None, b"\xff"), + (256, None, b"\x01\x00"), + ], +) def test_convert_int_to_bytes(no, count, result): value = convert_int_to_bytes(no, count=count) print(value) assert value == result -@pytest.mark.parametrize('no, digits, result', [ - (0, None, '0'), - (1, None, '1'), - (16, None, '10'), - (1, 3, '001'), - (255, None, 'ff'), - (256, None, '100'), - (256, 2, '100'), - (256, 3, '100'), - (256, 4, '0100'), -]) +@pytest.mark.parametrize( + "no, digits, result", + [ + (0, None, "0"), + (1, None, "1"), + (16, None, "10"), + (1, 3, "001"), + (255, None, "ff"), + (256, None, "100"), + (256, 2, "100"), + (256, 3, "100"), + (256, 4, "0100"), + ], +) def test_convert_int_to_hex(no, digits, result): value = convert_int_to_hex(no, digits=digits) print(value) assert value == result -@pytest.mark.parametrize('data, result', [ - (b'', 0), - (b'\x00', 0), - (b'\x00\x01', 1), - (b'\x01', 1), - (b'\xff', 255), - (b'\x01\x00', 256), -]) +@pytest.mark.parametrize( + "data, result", + [ + (b"", 0), + (b"\x00", 0), + (b"\x00\x01", 1), + (b"\x01", 1), + (b"\xff", 255), + (b"\x01\x00", 256), + ], +) def test_convert_bytes_to_int(data, result): value = convert_bytes_to_int(data) print(value) diff --git a/tests/unit/plugins/module_utils/crypto/test_pem.py b/tests/unit/plugins/module_utils/crypto/test_pem.py index d825bda5..8f7f29b8 100644 --- a/tests/unit/plugins/module_utils/crypto/test_pem.py +++ b/tests/unit/plugins/module_utils/crypto/test_pem.py @@ -19,48 +19,48 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import PEM_TEST_CASES = [ - (b'', [], False, 'raw'), - (b'random stuff\nblabla', [], False, 'raw'), - (b'-----BEGIN PRIVATE KEY-----', [], False, 'raw'), + (b"", [], False, "raw"), + (b"random stuff\nblabla", [], False, "raw"), + (b"-----BEGIN PRIVATE KEY-----", [], False, "raw"), ( - b'-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----', - ['-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----'], + b"-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", + ["-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"], True, - 'pkcs8', + "pkcs8", ), ( - b'foo=bar\n# random stuff\n-----BEGIN RSA PRIVATE KEY-----\nblabla\n-----END RSA PRIVATE KEY-----\nmore stuff\n', - ['-----BEGIN RSA PRIVATE KEY-----\nblabla\n-----END RSA PRIVATE KEY-----\n'], + b"foo=bar\n# random stuff\n-----BEGIN RSA PRIVATE KEY-----\nblabla\n-----END RSA PRIVATE KEY-----\nmore stuff\n", + ["-----BEGIN RSA PRIVATE KEY-----\nblabla\n-----END RSA PRIVATE KEY-----\n"], True, - 'pkcs1', + "pkcs1", ), ( - b'foo=bar\n# random stuff\n-----BEGIN CERTIFICATE-----\nblabla\n-----END CERTIFICATE-----\nmore stuff\n' - b'\n-----BEGIN CERTIFICATE-----\nfoobar\n-----END CERTIFICATE-----', + b"foo=bar\n# random stuff\n-----BEGIN CERTIFICATE-----\nblabla\n-----END CERTIFICATE-----\nmore stuff\n" + b"\n-----BEGIN CERTIFICATE-----\nfoobar\n-----END CERTIFICATE-----", [ - '-----BEGIN CERTIFICATE-----\nblabla\n-----END CERTIFICATE-----\n', - '-----BEGIN CERTIFICATE-----\nfoobar\n-----END CERTIFICATE-----', + "-----BEGIN CERTIFICATE-----\nblabla\n-----END CERTIFICATE-----\n", + "-----BEGIN CERTIFICATE-----\nfoobar\n-----END CERTIFICATE-----", ], True, - 'unknown-pem', + "unknown-pem", ), ( - b'-----BEGINCERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n', + b"-----BEGINCERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n-----END CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n", [ - '-----BEGIN CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n-----END CERTIFICATE-----\n', + "-----BEGIN CERTIFICATE-----\n-----BEGINCERTIFICATE-----\n-----END CERTIFICATE-----\n", ], True, - 'unknown-pem', + "unknown-pem", ), ] -@pytest.mark.parametrize('data, pems, is_pem, private_key_type', PEM_TEST_CASES) +@pytest.mark.parametrize("data, pems, is_pem, private_key_type", PEM_TEST_CASES) def test_pem_handling(data, pems, is_pem, private_key_type): assert identify_pem_format(data) == is_pem assert identify_private_key_format(data) == private_key_type try: - text = data.decode('utf-8') + text = data.decode("utf-8") assert split_pem_list(text) == pems first_pem = pems[0] if pems else None assert extract_first_pem(text) == first_pem diff --git a/tests/unit/plugins/module_utils/openssh/test_certificate.py b/tests/unit/plugins/module_utils/openssh/test_certificate.py index 9cca0cd8..ef0f9591 100644 --- a/tests/unit/plugins/module_utils/openssh/test_certificate.py +++ b/tests/unit/plugins/module_utils/openssh/test_certificate.py @@ -33,22 +33,22 @@ from ansible_collections.community.crypto.plugins.module_utils.openssh.certifica # permit-pty # permit-user-rc RSA_CERT_SIGNED_BY_DSA = ( - b'ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgY9CvhGpyvBB611Lmx6hHPD+CmeJ0oW' + - b'SSK1q6K3h5CS4AAAADAQABAAABAQDKYIJtpFaWpTNNifmuV3DM9BBdngMG28jWPy4C/SoZg4EP7mkYUsG6hN+LgjOL17YEF7bKDEWPl9sQS' + - b'92iD+AuAPrjnHVQ9VG5hbTYiQAaicj6hxqBoNqGQWxDzhZL4B35MgqmoUOBGnzYA/fKgqhRVzOXbWFxKLtzSJzB+Z+kmeoBzq+4MazL4Bko' + - b'yPZMrIMnvxiluv+kqE9SWeJ/5e7WXdtbYTnSR4WN3gW/BMKEoKQk/UGwuPvCiRq+y8LorJP4B1Wfwlm/meqtbTidXyCcQPR9xWpce3rRjLL' + - b'T6cimUjWrbx7Q1SlsypdkclgPSTu9Jg457am8tnQUgnL7VdetAAAAAAAAAAAAAAABAAAABHRlc3QAAAAAAAAAAAAAAAD//////////wAAAA' + - b'AAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0L' + - b'WZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAGxAAAAB3NzaC1kc3MAAACBAPV/' + - b'b5FknU8e56TWAGLRQ0v3c3f5jAS0txcwqtYLHLulTqyMcLL0MyzWxXv77MpjTMwEjWXLbfNWdk/qmsjfBynzs2nSZ7clVsqt/ZOadcBFEhq' + - b'ZM0l+1ZCPkhQiqsD2aodGbkVcJgqL5Z5krzB5MTey7c8rlAAxKOjfs70Bg8MPAAAAFQCW466dSEu2Pf0u8AA5SHgH0i/xuwAAAIBc23gfmv' + - b'GC+oaUAXiak17kH6NvOSJXZBdk/8CyGK6yL+CHKrKyffe6BbiVXwC6sUIa9j4YsFeyYwPFGBtfLuNUmgyKYTJcCM2zJLBykmTIvjSdRaYGN' + - b'Rkyi8GnzVV2lWxQ+4m4UGeTPbPN/OG4B0NwDbBJGbVJv0xJPq2EBKoUdgAAAIAyrFxGDLtOZFZ2fgONVaKaapEpJ5f3qPhLDXxVQ/BKVUkU' + - b'RA4AHHyXF2AMiiOOiHLrO5xsEGUyW+OISFm+6m17cEPNixA7G1fBniLvyVv2woyYW3kaY4J9z266kAFzFWVNgwr+T7MY0hEvct8VFA97JMR' + - b'Q7c8c/tNDaL7uqV46QQAAADcAAAAHc3NoLWRzcwAAAChaQ94wqca+KhkHtbkLpjvGsfu0Gy03SAb0+o11Shk/BXnK7N/cwEVD ' + - b'ansible@ansible-host' + b"ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgY9CvhGpyvBB611Lmx6hHPD+CmeJ0oW" + + b"SSK1q6K3h5CS4AAAADAQABAAABAQDKYIJtpFaWpTNNifmuV3DM9BBdngMG28jWPy4C/SoZg4EP7mkYUsG6hN+LgjOL17YEF7bKDEWPl9sQS" + + b"92iD+AuAPrjnHVQ9VG5hbTYiQAaicj6hxqBoNqGQWxDzhZL4B35MgqmoUOBGnzYA/fKgqhRVzOXbWFxKLtzSJzB+Z+kmeoBzq+4MazL4Bko" + + b"yPZMrIMnvxiluv+kqE9SWeJ/5e7WXdtbYTnSR4WN3gW/BMKEoKQk/UGwuPvCiRq+y8LorJP4B1Wfwlm/meqtbTidXyCcQPR9xWpce3rRjLL" + + b"T6cimUjWrbx7Q1SlsypdkclgPSTu9Jg457am8tnQUgnL7VdetAAAAAAAAAAAAAAABAAAABHRlc3QAAAAAAAAAAAAAAAD//////////wAAAA" + + b"AAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0L" + + b"WZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAGxAAAAB3NzaC1kc3MAAACBAPV/" + + b"b5FknU8e56TWAGLRQ0v3c3f5jAS0txcwqtYLHLulTqyMcLL0MyzWxXv77MpjTMwEjWXLbfNWdk/qmsjfBynzs2nSZ7clVsqt/ZOadcBFEhq" + + b"ZM0l+1ZCPkhQiqsD2aodGbkVcJgqL5Z5krzB5MTey7c8rlAAxKOjfs70Bg8MPAAAAFQCW466dSEu2Pf0u8AA5SHgH0i/xuwAAAIBc23gfmv" + + b"GC+oaUAXiak17kH6NvOSJXZBdk/8CyGK6yL+CHKrKyffe6BbiVXwC6sUIa9j4YsFeyYwPFGBtfLuNUmgyKYTJcCM2zJLBykmTIvjSdRaYGN" + + b"Rkyi8GnzVV2lWxQ+4m4UGeTPbPN/OG4B0NwDbBJGbVJv0xJPq2EBKoUdgAAAIAyrFxGDLtOZFZ2fgONVaKaapEpJ5f3qPhLDXxVQ/BKVUkU" + + b"RA4AHHyXF2AMiiOOiHLrO5xsEGUyW+OISFm+6m17cEPNixA7G1fBniLvyVv2woyYW3kaY4J9z266kAFzFWVNgwr+T7MY0hEvct8VFA97JMR" + + b"Q7c8c/tNDaL7uqV46QQAAADcAAAAHc3NoLWRzcwAAAChaQ94wqca+KhkHtbkLpjvGsfu0Gy03SAb0+o11Shk/BXnK7N/cwEVD " + + b"ansible@ansible-host" ) -RSA_FINGERPRINT = 'SHA256:SvUwwUer4AwsdePYseJR3LcZS8lnKi6BqiL51Dop030' +RSA_FINGERPRINT = "SHA256:SvUwwUer4AwsdePYseJR3LcZS8lnKi6BqiL51Dop030" # Type: ssh-dss-cert-v01@openssh.com user certificate # Public key: DSA-CERT SHA256:YCdJ2lYU+FSkWUud7zg1SJszprXoRGNU/GVcqXUjgC8 # Signing CA: ECDSA SHA256:w9lp4zGRJShhm4DzO3ulVm0BEcR0PMjrM6VanQo4C0w @@ -59,18 +59,18 @@ RSA_FINGERPRINT = 'SHA256:SvUwwUer4AwsdePYseJR3LcZS8lnKi6BqiL51Dop030' # Critical Options: (none) # Extensions: (none) DSA_CERT_SIGNED_BY_ECDSA_NO_OPTS = ( - b'ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgsKvMxIv4viCNQX7z8K4/R5jronpZGf' + - b'ydpoBoh2Cx5dgAAACBAPV/b5FknU8e56TWAGLRQ0v3c3f5jAS0txcwqtYLHLulTqyMcLL0MyzWxXv77MpjTMwEjWXLbfNWdk/qmsjfBynzs' + - b'2nSZ7clVsqt/ZOadcBFEhqZM0l+1ZCPkhQiqsD2aodGbkVcJgqL5Z5krzB5MTey7c8rlAAxKOjfs70Bg8MPAAAAFQCW466dSEu2Pf0u8AA5' + - b'SHgH0i/xuwAAAIBc23gfmvGC+oaUAXiak17kH6NvOSJXZBdk/8CyGK6yL+CHKrKyffe6BbiVXwC6sUIa9j4YsFeyYwPFGBtfLuNUmgyKYTJ' + - b'cCM2zJLBykmTIvjSdRaYGNRkyi8GnzVV2lWxQ+4m4UGeTPbPN/OG4B0NwDbBJGbVJv0xJPq2EBKoUdgAAAIAyrFxGDLtOZFZ2fgONVaKaap' + - b'EpJ5f3qPhLDXxVQ/BKVUkURA4AHHyXF2AMiiOOiHLrO5xsEGUyW+OISFm+6m17cEPNixA7G1fBniLvyVv2woyYW3kaY4J9z266kAFzFWVNg' + - b'wr+T7MY0hEvct8VFA97JMRQ7c8c/tNDaL7uqV46QQAAAAAAAAAAAAAAAQAAAAR0ZXN0AAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAA' + - b'AAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOf55Wc0yzaJPtxXxBGZKmAUozbYXwxZGFS1c/FaJbwLpq/' + - b'wvanQKM01uU73swNIt+ZFra9kRSi21xjzgMPn7U0AAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIGmlKa/riG7+EpoW6dTJY6' + - b'0N8BrEcniKgOxdRM1EPJ2DAAAAIQDnK4stvbvS+Bn0/42Was7uOfJtnLYXs5EuB2L3uejPcQ== ansible@ansible-host' + b"ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgsKvMxIv4viCNQX7z8K4/R5jronpZGf" + + b"ydpoBoh2Cx5dgAAACBAPV/b5FknU8e56TWAGLRQ0v3c3f5jAS0txcwqtYLHLulTqyMcLL0MyzWxXv77MpjTMwEjWXLbfNWdk/qmsjfBynzs" + + b"2nSZ7clVsqt/ZOadcBFEhqZM0l+1ZCPkhQiqsD2aodGbkVcJgqL5Z5krzB5MTey7c8rlAAxKOjfs70Bg8MPAAAAFQCW466dSEu2Pf0u8AA5" + + b"SHgH0i/xuwAAAIBc23gfmvGC+oaUAXiak17kH6NvOSJXZBdk/8CyGK6yL+CHKrKyffe6BbiVXwC6sUIa9j4YsFeyYwPFGBtfLuNUmgyKYTJ" + + b"cCM2zJLBykmTIvjSdRaYGNRkyi8GnzVV2lWxQ+4m4UGeTPbPN/OG4B0NwDbBJGbVJv0xJPq2EBKoUdgAAAIAyrFxGDLtOZFZ2fgONVaKaap" + + b"EpJ5f3qPhLDXxVQ/BKVUkURA4AHHyXF2AMiiOOiHLrO5xsEGUyW+OISFm+6m17cEPNixA7G1fBniLvyVv2woyYW3kaY4J9z266kAFzFWVNg" + + b"wr+T7MY0hEvct8VFA97JMRQ7c8c/tNDaL7uqV46QQAAAAAAAAAAAAAAAQAAAAR0ZXN0AAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAA" + + b"AAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOf55Wc0yzaJPtxXxBGZKmAUozbYXwxZGFS1c/FaJbwLpq/" + + b"wvanQKM01uU73swNIt+ZFra9kRSi21xjzgMPn7U0AAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIGmlKa/riG7+EpoW6dTJY6" + + b"0N8BrEcniKgOxdRM1EPJ2DAAAAIQDnK4stvbvS+Bn0/42Was7uOfJtnLYXs5EuB2L3uejPcQ== ansible@ansible-host" ) -DSA_FINGERPRINT = 'SHA256:YCdJ2lYU+FSkWUud7zg1SJszprXoRGNU/GVcqXUjgC8' +DSA_FINGERPRINT = "SHA256:YCdJ2lYU+FSkWUud7zg1SJszprXoRGNU/GVcqXUjgC8" # Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate # Public key: ECDSA-CERT SHA256:w9lp4zGRJShhm4DzO3ulVm0BEcR0PMjrM6VanQo4C0w # Signing CA: ED25519 SHA256:NP4JdfkCopbjwMepq0aPrpMz13cNmEd+uDOxC/j9N40 @@ -87,16 +87,16 @@ DSA_FINGERPRINT = 'SHA256:YCdJ2lYU+FSkWUud7zg1SJszprXoRGNU/GVcqXUjgC8' # permit-pty # permit-user-rc ECDSA_CERT_SIGNED_BY_ED25519_VALID_OPTS = ( - b'ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgtC' + - b'ips7/sOOOTAgiawGlQhM6pb26t0FfQ1jG60m+tOg0AAAAIbmlzdHAyNTYAAABBBOf55Wc0yzaJPtxXxBGZKmAUozbYXwxZGFS1c/FaJbwLp' + - b'q/wvanQKM01uU73swNIt+ZFra9kRSi21xjzgMPn7U0AAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAJQAAAA1m' + - b'b3JjZS1jb21tYW5kAAAAEAAAAAwvdXNyL2Jpbi9jc2gAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW5' + - b'0LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLX' + - b'JjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAII3qYBforim0x87UXpaTDNFnhFTyb+TPCJVQpEAOHTL6AAAAUwAAAAtzc2gtZWQyN' + - b'TUxOQAAAEAdp3eOLRN5t2wW29TBWbz604uuXg88jH4RA4HDhbRupa/x2rN3j6iZQ4VXPLA4JtdfIslHFkH6HUlxU8XsoJwP ' + - b'ansible@ansible-host' + b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgtC" + + b"ips7/sOOOTAgiawGlQhM6pb26t0FfQ1jG60m+tOg0AAAAIbmlzdHAyNTYAAABBBOf55Wc0yzaJPtxXxBGZKmAUozbYXwxZGFS1c/FaJbwLp" + + b"q/wvanQKM01uU73swNIt+ZFra9kRSi21xjzgMPn7U0AAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAJQAAAA1m" + + b"b3JjZS1jb21tYW5kAAAAEAAAAAwvdXNyL2Jpbi9jc2gAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW5" + + b"0LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLX" + + b"JjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAII3qYBforim0x87UXpaTDNFnhFTyb+TPCJVQpEAOHTL6AAAAUwAAAAtzc2gtZWQyN" + + b"TUxOQAAAEAdp3eOLRN5t2wW29TBWbz604uuXg88jH4RA4HDhbRupa/x2rN3j6iZQ4VXPLA4JtdfIslHFkH6HUlxU8XsoJwP " + + b"ansible@ansible-host" ) -ECDSA_FINGERPRINT = 'SHA256:w9lp4zGRJShhm4DzO3ulVm0BEcR0PMjrM6VanQo4C0w' +ECDSA_FINGERPRINT = "SHA256:w9lp4zGRJShhm4DzO3ulVm0BEcR0PMjrM6VanQo4C0w" # Type: ssh-ed25519-cert-v01@openssh.com user certificate # Public key: ED25519-CERT SHA256:NP4JdfkCopbjwMepq0aPrpMz13cNmEd+uDOxC/j9N40 # Signing CA: RSA SHA256:SvUwwUer4AwsdePYseJR3LcZS8lnKi6BqiL51Dop030 @@ -109,57 +109,123 @@ ECDSA_FINGERPRINT = 'SHA256:w9lp4zGRJShhm4DzO3ulVm0BEcR0PMjrM6VanQo4C0w' # Extensions: # test UNKNOWN OPTION (len 0) ED25519_CERT_SIGNED_BY_RSA_INVALID_OPTS = ( - b'ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIP034YpKn6BDcwxqFnVrKt' + - b'kNX7k6X7hxZ7lADp5LAxHrAAAAII3qYBforim0x87UXpaTDNFnhFTyb+TPCJVQpEAOHTL6AAAAAAAAAAAAAAABAAAABHRlc3QAAAAAAAAAA' + - b'AAAAAD//////////wAAABkAAAAEdGVzdAAAAA0AAAAJdW5kZWZpbmVkAAAADAAAAAR0ZXN0AAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAAD' + - b'AQABAAABAQDKYIJtpFaWpTNNifmuV3DM9BBdngMG28jWPy4C/SoZg4EP7mkYUsG6hN+LgjOL17YEF7bKDEWPl9sQS92iD+AuAPrjnHVQ9VG' + - b'5hbTYiQAaicj6hxqBoNqGQWxDzhZL4B35MgqmoUOBGnzYA/fKgqhRVzOXbWFxKLtzSJzB+Z+kmeoBzq+4MazL4BkoyPZMrIMnvxiluv+kqE' + - b'9SWeJ/5e7WXdtbYTnSR4WN3gW/BMKEoKQk/UGwuPvCiRq+y8LorJP4B1Wfwlm/meqtbTidXyCcQPR9xWpce3rRjLLT6cimUjWrbx7Q1Slsy' + - b'pdkclgPSTu9Jg457am8tnQUgnL7VdetAAABDwAAAAdzc2gtcnNhAAABAMZLNacwOMNexYUaFK1nU0JPQTv4fM73QDG3xURtDsIbI6DAcA1y' + - b'KkvgjJcxlZHx0APJ+i1lWNAvPeOmuPTioymjIEuwxi0VGuAoVKgjmIy6aXH2z3YMxy9cGOq6LNfI4c58iBHR5ejVHAzvIg3rowypVsCGugL' + - b'7WJpz3eypBJt4TglwRTJpp54IMN2CyDQm0N97x9ris8jQQHlCF2EgZp1u4aOiZJTSJ5d4hapO0uZwXOI9AIWy/lmx0/6jX07MWrs4iXpfiF' + - b'5T4s6kEn7YW4SaJ0Z7xGp3V0vDOxh+jwHZGD5GM449Il6QxQwDY5BSJq+iMR467yaIjw2g8Kt4ZiU= ansible@ansible-host' + b"ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIP034YpKn6BDcwxqFnVrKt" + + b"kNX7k6X7hxZ7lADp5LAxHrAAAAII3qYBforim0x87UXpaTDNFnhFTyb+TPCJVQpEAOHTL6AAAAAAAAAAAAAAABAAAABHRlc3QAAAAAAAAAA" + + b"AAAAAD//////////wAAABkAAAAEdGVzdAAAAA0AAAAJdW5kZWZpbmVkAAAADAAAAAR0ZXN0AAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAAD" + + b"AQABAAABAQDKYIJtpFaWpTNNifmuV3DM9BBdngMG28jWPy4C/SoZg4EP7mkYUsG6hN+LgjOL17YEF7bKDEWPl9sQS92iD+AuAPrjnHVQ9VG" + + b"5hbTYiQAaicj6hxqBoNqGQWxDzhZL4B35MgqmoUOBGnzYA/fKgqhRVzOXbWFxKLtzSJzB+Z+kmeoBzq+4MazL4BkoyPZMrIMnvxiluv+kqE" + + b"9SWeJ/5e7WXdtbYTnSR4WN3gW/BMKEoKQk/UGwuPvCiRq+y8LorJP4B1Wfwlm/meqtbTidXyCcQPR9xWpce3rRjLLT6cimUjWrbx7Q1Slsy" + + b"pdkclgPSTu9Jg457am8tnQUgnL7VdetAAABDwAAAAdzc2gtcnNhAAABAMZLNacwOMNexYUaFK1nU0JPQTv4fM73QDG3xURtDsIbI6DAcA1y" + + b"KkvgjJcxlZHx0APJ+i1lWNAvPeOmuPTioymjIEuwxi0VGuAoVKgjmIy6aXH2z3YMxy9cGOq6LNfI4c58iBHR5ejVHAzvIg3rowypVsCGugL" + + b"7WJpz3eypBJt4TglwRTJpp54IMN2CyDQm0N97x9ris8jQQHlCF2EgZp1u4aOiZJTSJ5d4hapO0uZwXOI9AIWy/lmx0/6jX07MWrs4iXpfiF" + + b"5T4s6kEn7YW4SaJ0Z7xGp3V0vDOxh+jwHZGD5GM449Il6QxQwDY5BSJq+iMR467yaIjw2g8Kt4ZiU= ansible@ansible-host" ) -ED25519_FINGERPRINT = 'SHA256:NP4JdfkCopbjwMepq0aPrpMz13cNmEd+uDOxC/j9N40' +ED25519_FINGERPRINT = "SHA256:NP4JdfkCopbjwMepq0aPrpMz13cNmEd+uDOxC/j9N40" # garbage -INVALID_DATA = b'yDspTN+BJzvIK2Q+CRD3qBDVSi+YqSxwyz432VEaHKlXbuLURirY0QpuBCqgR6tCtWW5vEGkXKZ3' +INVALID_DATA = ( + b"yDspTN+BJzvIK2Q+CRD3qBDVSi+YqSxwyz432VEaHKlXbuLURirY0QpuBCqgR6tCtWW5vEGkXKZ3" +) -VALID_OPTS = [OpensshCertificateOption('critical', 'force-command', '/usr/bin/csh')] -INVALID_OPTS = [OpensshCertificateOption('critical', 'test', 'undefined')] +VALID_OPTS = [OpensshCertificateOption("critical", "force-command", "/usr/bin/csh")] +INVALID_OPTS = [OpensshCertificateOption("critical", "test", "undefined")] VALID_EXTENSIONS = [ - OpensshCertificateOption('extension', 'permit-x11-forwarding', ''), - OpensshCertificateOption('extension', 'permit-agent-forwarding', ''), - OpensshCertificateOption('extension', 'permit-port-forwarding', ''), - OpensshCertificateOption('extension', 'permit-pty', ''), - OpensshCertificateOption('extension', 'permit-user-rc', ''), + OpensshCertificateOption("extension", "permit-x11-forwarding", ""), + OpensshCertificateOption("extension", "permit-agent-forwarding", ""), + OpensshCertificateOption("extension", "permit-port-forwarding", ""), + OpensshCertificateOption("extension", "permit-pty", ""), + OpensshCertificateOption("extension", "permit-user-rc", ""), ] -INVALID_EXTENSIONS = [OpensshCertificateOption('extension', 'test', '')] +INVALID_EXTENSIONS = [OpensshCertificateOption("extension", "test", "")] VALID_TIME_PARAMETERS = [ - (0, "always", "always", 0, - 0xFFFFFFFFFFFFFFFF, "forever", "forever", 253402300800, - ""), - ("always", "always", "always", 0, - "forever", "forever", "forever", 253402300800, - ""), - (315532800, "1980-01-01T00:00:00", "19800101000000", 315532800, - 631152000, "1990-01-01T00:00:00", "19900101000000", 631152000, - "19800101000000:19900101000000"), - ("1980-01-01", "1980-01-01T00:00:00", "19800101000000", 315532800, - "1990-01-01", "1990-01-01T00:00:00", "19900101000000", 631152000, - "19800101000000:19900101000000"), - ("1980-01-01 00:00:00", "1980-01-01T00:00:00", "19800101000000", 315532800, - "1990-01-01 00:00:00", "1990-01-01T00:00:00", "19900101000000", 631152000, - "19800101000000:19900101000000"), - ("1980-01-01T00:00:00", "1980-01-01T00:00:00", "19800101000000", 315532800, - "1990-01-01T00:00:00", "1990-01-01T00:00:00", "19900101000000", 631152000, - "19800101000000:19900101000000"), - ("always", "always", "always", 0, - "1990-01-01T00:00:00", "1990-01-01T00:00:00", "19900101000000", 631152000, - "always:19900101000000"), - ("1980-01-01", "1980-01-01T00:00:00", "19800101000000", 315532800, - "forever", "forever", "forever", 253402300800, - "19800101000000:forever"), + ( + 0, + "always", + "always", + 0, + 0xFFFFFFFFFFFFFFFF, + "forever", + "forever", + 253402300800, + "", + ), + ( + "always", + "always", + "always", + 0, + "forever", + "forever", + "forever", + 253402300800, + "", + ), + ( + 315532800, + "1980-01-01T00:00:00", + "19800101000000", + 315532800, + 631152000, + "1990-01-01T00:00:00", + "19900101000000", + 631152000, + "19800101000000:19900101000000", + ), + ( + "1980-01-01", + "1980-01-01T00:00:00", + "19800101000000", + 315532800, + "1990-01-01", + "1990-01-01T00:00:00", + "19900101000000", + 631152000, + "19800101000000:19900101000000", + ), + ( + "1980-01-01 00:00:00", + "1980-01-01T00:00:00", + "19800101000000", + 315532800, + "1990-01-01 00:00:00", + "1990-01-01T00:00:00", + "19900101000000", + 631152000, + "19800101000000:19900101000000", + ), + ( + "1980-01-01T00:00:00", + "1980-01-01T00:00:00", + "19800101000000", + 315532800, + "1990-01-01T00:00:00", + "1990-01-01T00:00:00", + "19900101000000", + 631152000, + "19800101000000:19900101000000", + ), + ( + "always", + "always", + "always", + 0, + "1990-01-01T00:00:00", + "1990-01-01T00:00:00", + "19900101000000", + 631152000, + "always:19900101000000", + ), + ( + "1980-01-01", + "1980-01-01T00:00:00", + "19800101000000", + 315532800, + "forever", + "forever", + "forever", + 253402300800, + "19800101000000:forever", + ), ] INVALID_TIME_PARAMETERS = [ @@ -184,41 +250,53 @@ INVALID_VALIDITY_TEST = [ ] VALID_OPTIONS = [ - ("force-command=/usr/bin/csh", OpensshCertificateOption('critical', 'force-command', '/usr/bin/csh')), - ("Force-Command=/Usr/Bin/Csh", OpensshCertificateOption('critical', 'force-command', '/Usr/Bin/Csh')), - ("permit-x11-forwarding", OpensshCertificateOption('extension', 'permit-x11-forwarding', '')), - ("permit-X11-forwarding", OpensshCertificateOption('extension', 'permit-x11-forwarding', '')), - ("critical:foo=bar", OpensshCertificateOption('critical', 'foo', 'bar')), - ("extension:foo", OpensshCertificateOption('extension', 'foo', '')), + ( + "force-command=/usr/bin/csh", + OpensshCertificateOption("critical", "force-command", "/usr/bin/csh"), + ), + ( + "Force-Command=/Usr/Bin/Csh", + OpensshCertificateOption("critical", "force-command", "/Usr/Bin/Csh"), + ), + ( + "permit-x11-forwarding", + OpensshCertificateOption("extension", "permit-x11-forwarding", ""), + ), + ( + "permit-X11-forwarding", + OpensshCertificateOption("extension", "permit-x11-forwarding", ""), + ), + ("critical:foo=bar", OpensshCertificateOption("critical", "foo", "bar")), + ("extension:foo", OpensshCertificateOption("extension", "foo", "")), ] INVALID_OPTIONS = [ "foobar", "foo=bar", - 'foo:bar=baz', + "foo:bar=baz", [], ] def test_rsa_certificate(tmpdir): - cert_file = tmpdir / 'id_rsa-cert.pub' - cert_file.write(RSA_CERT_SIGNED_BY_DSA, mode='wb') + cert_file = tmpdir / "id_rsa-cert.pub" + cert_file.write(RSA_CERT_SIGNED_BY_DSA, mode="wb") cert = OpensshCertificate.load(str(cert_file)) - assert cert.key_id == 'test' + assert cert.key_id == "test" assert cert.serial == 0 - assert cert.type_string == 'ssh-rsa-cert-v01@openssh.com' + assert cert.type_string == "ssh-rsa-cert-v01@openssh.com" assert cert.public_key == RSA_FINGERPRINT assert cert.signing_key == DSA_FINGERPRINT def test_dsa_certificate(tmpdir): - cert_file = tmpdir / 'id_dsa-cert.pub' + cert_file = tmpdir / "id_dsa-cert.pub" cert_file.write(DSA_CERT_SIGNED_BY_ECDSA_NO_OPTS) cert = OpensshCertificate.load(str(cert_file)) - assert cert.type_string == 'ssh-dss-cert-v01@openssh.com' + assert cert.type_string == "ssh-dss-cert-v01@openssh.com" assert cert.public_key == DSA_FINGERPRINT assert cert.signing_key == ECDSA_FINGERPRINT assert cert.critical_options == [] @@ -226,11 +304,11 @@ def test_dsa_certificate(tmpdir): def test_ecdsa_certificate(tmpdir): - cert_file = tmpdir / 'id_ecdsa-cert.pub' + cert_file = tmpdir / "id_ecdsa-cert.pub" cert_file.write(ECDSA_CERT_SIGNED_BY_ED25519_VALID_OPTS) cert = OpensshCertificate.load(str(cert_file)) - assert cert.type_string == 'ecdsa-sha2-nistp256-cert-v01@openssh.com' + assert cert.type_string == "ecdsa-sha2-nistp256-cert-v01@openssh.com" assert cert.public_key == ECDSA_FINGERPRINT assert cert.signing_key == ED25519_FINGERPRINT assert cert.critical_options == VALID_OPTS @@ -238,11 +316,11 @@ def test_ecdsa_certificate(tmpdir): def test_ed25519_certificate(tmpdir): - cert_file = tmpdir / 'id_ed25519-cert.pub' + cert_file = tmpdir / "id_ed25519-cert.pub" cert_file.write(ED25519_CERT_SIGNED_BY_RSA_INVALID_OPTS) cert = OpensshCertificate.load(str(cert_file)) - assert cert.type_string == 'ssh-ed25519-cert-v01@openssh.com' + assert cert.type_string == "ssh-ed25519-cert-v01@openssh.com" assert cert.public_key == ED25519_FINGERPRINT assert cert.signing_key == RSA_FINGERPRINT assert cert.critical_options == INVALID_OPTS @@ -251,7 +329,7 @@ def test_ed25519_certificate(tmpdir): def test_invalid_data(tmpdir): result = False - cert_file = tmpdir / 'invalid-cert.pub' + cert_file = tmpdir / "invalid-cert.pub" cert_file.write(INVALID_DATA) try: @@ -262,17 +340,24 @@ def test_invalid_data(tmpdir): @pytest.mark.parametrize( - "valid_from,valid_from_hr,valid_from_openssh,valid_from_timestamp," + - "valid_to,valid_to_hr,valid_to_openssh,valid_to_timestamp," + - "validity_string", - VALID_TIME_PARAMETERS + "valid_from,valid_from_hr,valid_from_openssh,valid_from_timestamp," + + "valid_to,valid_to_hr,valid_to_openssh,valid_to_timestamp," + + "validity_string", + VALID_TIME_PARAMETERS, ) -def test_valid_time_parameters(valid_from, valid_from_hr, valid_from_openssh, valid_from_timestamp, - valid_to, valid_to_hr, valid_to_openssh, valid_to_timestamp, - validity_string): +def test_valid_time_parameters( + valid_from, + valid_from_hr, + valid_from_openssh, + valid_from_timestamp, + valid_to, + valid_to_hr, + valid_to_openssh, + valid_to_timestamp, + validity_string, +): time_parameters = OpensshCertificateTimeParameters( - valid_from=valid_from, - valid_to=valid_to + valid_from=valid_from, valid_to=valid_to ) assert time_parameters.valid_from(date_format="human_readable") == valid_from_hr assert time_parameters.valid_from(date_format="openssh") == valid_from_openssh @@ -296,7 +381,9 @@ def test_valid_validity_test(valid_from, valid_to, valid_at): @pytest.mark.parametrize("valid_from,valid_to,valid_at", INVALID_VALIDITY_TEST) def test_invalid_validity_test(valid_from, valid_to, valid_at): - assert not OpensshCertificateTimeParameters(valid_from, valid_to).within_range(valid_at) + assert not OpensshCertificateTimeParameters(valid_from, valid_to).within_range( + valid_at + ) @pytest.mark.parametrize("option_string,option_object", VALID_OPTIONS) @@ -311,18 +398,18 @@ def test_invalid_options(option_string): def test_parse_option_list(): - critical_options, extensions = parse_option_list(['force-command=/usr/bin/csh']) + critical_options, extensions = parse_option_list(["force-command=/usr/bin/csh"]) critical_option_objects = [ - OpensshCertificateOption.from_string('force-command=/usr/bin/csh'), + OpensshCertificateOption.from_string("force-command=/usr/bin/csh"), ] extension_objects = [ - OpensshCertificateOption.from_string('permit-x11-forwarding'), - OpensshCertificateOption.from_string('permit-agent-forwarding'), - OpensshCertificateOption.from_string('permit-port-forwarding'), - OpensshCertificateOption.from_string('permit-user-rc'), - OpensshCertificateOption.from_string('permit-pty'), + OpensshCertificateOption.from_string("permit-x11-forwarding"), + OpensshCertificateOption.from_string("permit-agent-forwarding"), + OpensshCertificateOption.from_string("permit-port-forwarding"), + OpensshCertificateOption.from_string("permit-user-rc"), + OpensshCertificateOption.from_string("permit-pty"), ] assert set(critical_options) == set(critical_option_objects) @@ -330,11 +417,13 @@ def test_parse_option_list(): def test_parse_option_list_with_directives(): - critical_options, extensions = parse_option_list(['clear', 'no-pty', 'permit-pty', 'permit-user-rc']) + critical_options, extensions = parse_option_list( + ["clear", "no-pty", "permit-pty", "permit-user-rc"] + ) extension_objects = [ - OpensshCertificateOption.from_string('permit-user-rc'), - OpensshCertificateOption.from_string('permit-pty'), + OpensshCertificateOption.from_string("permit-user-rc"), + OpensshCertificateOption.from_string("permit-pty"), ] assert set(critical_options) == set() @@ -342,10 +431,12 @@ def test_parse_option_list_with_directives(): def test_parse_option_list_case_sensitivity(): - critical_options, extensions = parse_option_list(['CLEAR', 'no-X11-forwarding', 'permit-X11-forwarding']) + critical_options, extensions = parse_option_list( + ["CLEAR", "no-X11-forwarding", "permit-X11-forwarding"] + ) extension_objects = [ - OpensshCertificateOption.from_string('permit-x11-forwarding'), + OpensshCertificateOption.from_string("permit-x11-forwarding"), ] assert set(critical_options) == set() diff --git a/tests/unit/plugins/module_utils/openssh/test_cryptography.py b/tests/unit/plugins/module_utils/openssh/test_cryptography.py index a563958f..62813388 100644 --- a/tests/unit/plugins/module_utils/openssh/test_cryptography.py +++ b/tests/unit/plugins/module_utils/openssh/test_cryptography.py @@ -30,25 +30,25 @@ from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptogra DEFAULT_KEY_PARAMS = [ ( - 'rsa', + "rsa", None, None, None, ), ( - 'dsa', + "dsa", None, None, None, ), ( - 'ecdsa', + "ecdsa", None, None, None, ), ( - 'ed25519', + "ed25519", None, None, None, @@ -57,46 +57,46 @@ DEFAULT_KEY_PARAMS = [ VALID_USER_KEY_PARAMS = [ ( - 'rsa', + "rsa", 8192, - 'change_me'.encode('UTF-8'), - 'comment', + "change_me".encode("UTF-8"), + "comment", ), ( - 'dsa', + "dsa", 1024, - 'change_me'.encode('UTF-8'), - 'comment', + "change_me".encode("UTF-8"), + "comment", ), ( - 'ecdsa', + "ecdsa", 521, - 'change_me'.encode('UTF-8'), - 'comment', + "change_me".encode("UTF-8"), + "comment", ), ( - 'ed25519', + "ed25519", 256, - 'change_me'.encode('UTF-8'), - 'comment', + "change_me".encode("UTF-8"), + "comment", ), ] INVALID_USER_KEY_PARAMS = [ ( - 'dne', + "dne", None, None, None, ), ( - 'rsa', + "rsa", None, [1, 2, 3], - 'comment', + "comment", ), ( - 'ecdsa', + "ecdsa", None, None, [1, 2, 3], @@ -105,31 +105,31 @@ INVALID_USER_KEY_PARAMS = [ INVALID_KEY_SIZES = [ ( - 'rsa', + "rsa", 1023, None, None, ), ( - 'rsa', + "rsa", 16385, None, None, ), ( - 'dsa', + "dsa", 256, None, None, ), ( - 'ecdsa', + "ecdsa", 1024, None, None, ), ( - 'ed25519', + "ed25519", 1024, None, None, @@ -143,16 +143,20 @@ def test_default_key_params(keytype, size, passphrase, comment): result = True default_sizes = { - 'rsa': 2048, - 'dsa': 1024, - 'ecdsa': 256, - 'ed25519': 256, + "rsa": 2048, + "dsa": 1024, + "ecdsa": 256, + "ed25519": 256, } default_comment = "%s@%s" % (getuser(), gethostname()) - pair = OpensshKeypair.generate(keytype=keytype, size=size, passphrase=passphrase, comment=comment) + pair = OpensshKeypair.generate( + keytype=keytype, size=size, passphrase=passphrase, comment=comment + ) try: - pair = OpensshKeypair.generate(keytype=keytype, size=size, passphrase=passphrase, comment=comment) + pair = OpensshKeypair.generate( + keytype=keytype, size=size, passphrase=passphrase, comment=comment + ) if pair.size != default_sizes[pair.key_type] or pair.comment != default_comment: result = False except Exception as e: @@ -168,7 +172,9 @@ def test_valid_user_key_params(keytype, size, passphrase, comment): result = True try: - pair = OpensshKeypair.generate(keytype=keytype, size=size, passphrase=passphrase, comment=comment) + pair = OpensshKeypair.generate( + keytype=keytype, size=size, passphrase=passphrase, comment=comment + ) if pair.key_type != keytype or pair.size != size or pair.comment != comment: result = False except Exception as e: @@ -184,7 +190,9 @@ def test_invalid_user_key_params(keytype, size, passphrase, comment): result = False try: - OpensshKeypair.generate(keytype=keytype, size=size, passphrase=passphrase, comment=comment) + OpensshKeypair.generate( + keytype=keytype, size=size, passphrase=passphrase, comment=comment + ) except (InvalidCommentError, InvalidKeyTypeError, InvalidPassphraseError): result = True except Exception as e: @@ -200,7 +208,9 @@ def test_invalid_key_sizes(keytype, size, passphrase, comment): result = False try: - OpensshKeypair.generate(keytype=keytype, size=size, passphrase=passphrase, comment=comment) + OpensshKeypair.generate( + keytype=keytype, size=size, passphrase=passphrase, comment=comment + ) except InvalidKeySizeError: result = True except Exception as e: @@ -221,7 +231,10 @@ def test_valid_comment_update(): print(e) pass - assert pair.comment == new_comment and pair.public_key.split(b' ', 2)[2].decode() == new_comment + assert ( + pair.comment == new_comment + and pair.public_key.split(b" ", 2)[2].decode() == new_comment + ) @pytest.mark.skipif(not HAS_OPENSSH_SUPPORT, reason="requires cryptography") @@ -242,7 +255,7 @@ def test_invalid_comment_update(): def test_valid_passphrase_update(): result = False - passphrase = "change_me".encode('UTF-8') + passphrase = "change_me".encode("UTF-8") try: tmpdir = mkdtemp() @@ -254,7 +267,7 @@ def test_valid_passphrase_update(): with open(keyfilename, "w+b") as keyfile: keyfile.write(pair1.private_key) - with open(keyfilename + '.pub', "w+b") as pubkeyfile: + with open(keyfilename + ".pub", "w+b") as pubkeyfile: pubkeyfile.write(pair1.public_key) pair2 = OpensshKeypair.load(path=keyfilename, passphrase=passphrase) @@ -264,8 +277,8 @@ def test_valid_passphrase_update(): finally: if os.path.exists(keyfilename): remove(keyfilename) - if os.path.exists(keyfilename + '.pub'): - remove(keyfilename + '.pub') + if os.path.exists(keyfilename + ".pub"): + remove(keyfilename + ".pub") if os.path.exists(tmpdir): rmdir(tmpdir) @@ -299,7 +312,7 @@ def test_invalid_privatekey(): with open(keyfilename, "w+b") as keyfile: keyfile.write(pair.private_key[1:]) - with open(keyfilename + '.pub', "w+b") as pubkeyfile: + with open(keyfilename + ".pub", "w+b") as pubkeyfile: pubkeyfile.write(pair.public_key) OpensshKeypair.load(path=keyfilename) @@ -308,8 +321,8 @@ def test_invalid_privatekey(): finally: if os.path.exists(keyfilename): remove(keyfilename) - if os.path.exists(keyfilename + '.pub'): - remove(keyfilename + '.pub') + if os.path.exists(keyfilename + ".pub"): + remove(keyfilename + ".pub") if os.path.exists(tmpdir): rmdir(tmpdir) @@ -330,7 +343,7 @@ def test_mismatched_keypair(): with open(keyfilename, "w+b") as keyfile: keyfile.write(pair1.private_key) - with open(keyfilename + '.pub', "w+b") as pubkeyfile: + with open(keyfilename + ".pub", "w+b") as pubkeyfile: pubkeyfile.write(pair2.public_key) OpensshKeypair.load(path=keyfilename) @@ -339,8 +352,8 @@ def test_mismatched_keypair(): finally: if os.path.exists(keyfilename): remove(keyfilename) - if os.path.exists(keyfilename + '.pub'): - remove(keyfilename + '.pub') + if os.path.exists(keyfilename + ".pub"): + remove(keyfilename + ".pub") if os.path.exists(tmpdir): rmdir(tmpdir) @@ -350,54 +363,62 @@ def test_mismatched_keypair(): @pytest.mark.skipif(not HAS_OPENSSH_SUPPORT, reason="requires cryptography") def test_keypair_comparison(): assert OpensshKeypair.generate() != OpensshKeypair.generate() - assert OpensshKeypair.generate() != OpensshKeypair.generate(keytype='dsa') - assert OpensshKeypair.generate() != OpensshKeypair.generate(keytype='ed25519') - assert OpensshKeypair.generate(keytype='ed25519') != OpensshKeypair.generate(keytype='ed25519') + assert OpensshKeypair.generate() != OpensshKeypair.generate(keytype="dsa") + assert OpensshKeypair.generate() != OpensshKeypair.generate(keytype="ed25519") + assert OpensshKeypair.generate(keytype="ed25519") != OpensshKeypair.generate( + keytype="ed25519" + ) try: tmpdir = mkdtemp() keys = { - 'rsa': { - 'pair': OpensshKeypair.generate(), - 'filename': os.path.join(tmpdir, "id_rsa"), + "rsa": { + "pair": OpensshKeypair.generate(), + "filename": os.path.join(tmpdir, "id_rsa"), }, - 'dsa': { - 'pair': OpensshKeypair.generate(keytype='dsa', passphrase='change_me'.encode('UTF-8')), - 'filename': os.path.join(tmpdir, "id_dsa"), + "dsa": { + "pair": OpensshKeypair.generate( + keytype="dsa", passphrase="change_me".encode("UTF-8") + ), + "filename": os.path.join(tmpdir, "id_dsa"), + }, + "ed25519": { + "pair": OpensshKeypair.generate(keytype="ed25519"), + "filename": os.path.join(tmpdir, "id_ed25519"), }, - 'ed25519': { - 'pair': OpensshKeypair.generate(keytype='ed25519'), - 'filename': os.path.join(tmpdir, "id_ed25519"), - } } for v in keys.values(): - with open(v['filename'], "w+b") as keyfile: - keyfile.write(v['pair'].private_key) - with open(v['filename'] + '.pub', "w+b") as pubkeyfile: - pubkeyfile.write(v['pair'].public_key) + with open(v["filename"], "w+b") as keyfile: + keyfile.write(v["pair"].private_key) + with open(v["filename"] + ".pub", "w+b") as pubkeyfile: + pubkeyfile.write(v["pair"].public_key) - assert keys['rsa']['pair'] == OpensshKeypair.load(path=keys['rsa']['filename']) + assert keys["rsa"]["pair"] == OpensshKeypair.load(path=keys["rsa"]["filename"]) - loaded_dsa_key = OpensshKeypair.load(path=keys['dsa']['filename'], passphrase='change_me'.encode('UTF-8')) - assert keys['dsa']['pair'] == loaded_dsa_key + loaded_dsa_key = OpensshKeypair.load( + path=keys["dsa"]["filename"], passphrase="change_me".encode("UTF-8") + ) + assert keys["dsa"]["pair"] == loaded_dsa_key - loaded_dsa_key.update_passphrase('change_me_again'.encode('UTF-8')) - assert keys['dsa']['pair'] != loaded_dsa_key + loaded_dsa_key.update_passphrase("change_me_again".encode("UTF-8")) + assert keys["dsa"]["pair"] != loaded_dsa_key - loaded_dsa_key.update_passphrase('change_me'.encode('UTF-8')) - assert keys['dsa']['pair'] == loaded_dsa_key + loaded_dsa_key.update_passphrase("change_me".encode("UTF-8")) + assert keys["dsa"]["pair"] == loaded_dsa_key loaded_dsa_key.comment = "comment" - assert keys['dsa']['pair'] != loaded_dsa_key + assert keys["dsa"]["pair"] != loaded_dsa_key - assert keys['ed25519']['pair'] == OpensshKeypair.load(path=keys['ed25519']['filename']) + assert keys["ed25519"]["pair"] == OpensshKeypair.load( + path=keys["ed25519"]["filename"] + ) finally: for v in keys.values(): - if os.path.exists(v['filename']): - remove(v['filename']) - if os.path.exists(v['filename'] + '.pub'): - remove(v['filename'] + '.pub') + if os.path.exists(v["filename"]): + remove(v["filename"]) + if os.path.exists(v["filename"] + ".pub"): + remove(v["filename"] + ".pub") if os.path.exists(tmpdir): rmdir(tmpdir) assert OpensshKeypair.generate() != [] diff --git a/tests/unit/plugins/module_utils/openssh/test_utils.py b/tests/unit/plugins/module_utils/openssh/test_utils.py index 3ebca4a6..c1b71202 100644 --- a/tests/unit/plugins/module_utils/openssh/test_utils.py +++ b/tests/unit/plugins/module_utils/openssh/test_utils.py @@ -20,13 +20,8 @@ from ansible_collections.community.crypto.plugins.module_utils.openssh.utils imp SSH_VERSION_STRING = "OpenSSH_7.9p1, OpenSSL 1.1.0i-fips 14 Aug 2018" SSH_VERSION_NUMBER = "7.9" -VALID_BOOLEAN = [ - True, - False -] -INVALID_BOOLEAN = [ - 0x02 -] +VALID_BOOLEAN = [True, False] +INVALID_BOOLEAN = [0x02] VALID_UINT32 = [ 0x00, 0x01, @@ -48,7 +43,7 @@ INVALID_UINT64 = [ -1, ] VALID_STRING = [ - b'test string', + b"test string", ] INVALID_STRING = [ [], @@ -56,10 +51,10 @@ INVALID_STRING = [ # See https://datatracker.ietf.org/doc/html/rfc4251#section-5 for examples source VALID_MPINT = [ 0x00, - 0x9a378f9b2e332a7, + 0x9A378F9B2E332A7, 0x80, -0x1234, - -0xdeadbeef, + -0xDEADBEEF, # Additional large int test 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, ] @@ -107,7 +102,10 @@ def test_invalid_uint64(uint64): @pytest.mark.parametrize("ssh_string", VALID_STRING) def test_valid_string(ssh_string): - assert OpensshParser(_OpensshWriter().string(ssh_string).bytes()).string() == ssh_string + assert ( + OpensshParser(_OpensshWriter().string(ssh_string).bytes()).string() + == ssh_string + ) @pytest.mark.parametrize("ssh_string", INVALID_STRING) @@ -128,7 +126,7 @@ def test_invalid_mpint(mpint): def test_valid_seek(): - buffer = bytearray(b'buffer') + buffer = bytearray(b"buffer") parser = OpensshParser(buffer) parser.seek(len(buffer)) assert parser.remaining_bytes() == 0 @@ -137,7 +135,7 @@ def test_valid_seek(): def test_invalid_seek(): - buffer = b'buffer' + buffer = b"buffer" parser = OpensshParser(buffer) with pytest.raises(ValueError): @@ -148,5 +146,5 @@ def test_invalid_seek(): def test_writer_bytes(): - buffer = bytearray(b'buffer') + buffer = bytearray(b"buffer") assert _OpensshWriter(buffer).bytes() == buffer diff --git a/tests/unit/plugins/module_utils/test_time.py b/tests/unit/plugins/module_utils/test_time.py index 241b2b8a..3bf33813 100644 --- a/tests/unit/plugins/module_utils/test_time.py +++ b/tests/unit/plugins/module_utils/test_time.py @@ -39,259 +39,319 @@ def cartesian_product(list1, list2): result = [] for item1 in list1: if not is_sequence(item1): - item1 = (item1, ) + item1 = (item1,) elif not isinstance(item1, tuple): item1 = tuple(item1) for item2 in list2: if not is_sequence(item2): - item2 = (item2, ) + item2 = (item2,) elif not isinstance(item2, tuple): item2 = tuple(item2) result.append(item1 + item2) return result -TEST_REMOVE_TIMEZONE = cartesian_product(TIMEZONES, [ - ( - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), - datetime.datetime(2024, 1, 1, 0, 1, 2), - ), - ( - datetime.datetime(2024, 1, 1, 0, 1, 2), - datetime.datetime(2024, 1, 1, 0, 1, 2), - ), -]) +TEST_REMOVE_TIMEZONE = cartesian_product( + TIMEZONES, + [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), + ( + datetime.datetime(2024, 1, 1, 0, 1, 2), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), + ], +) -TEST_UTC_TIMEZONE = cartesian_product(TIMEZONES, [ - ( - datetime.datetime(2024, 1, 1, 0, 1, 2), - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), - ), - ( - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), - ), -]) +TEST_UTC_TIMEZONE = cartesian_product( + TIMEZONES, + [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2), + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + ), + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + ), + ], +) -TEST_EPOCH_SECONDS = cartesian_product(TIMEZONES, [ - (0, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0)), - (1E-6, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1)), - (1E-3, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1000)), - (3691.2, dict(year=1970, day=1, month=1, hour=1, minute=1, second=31, microsecond=200000)), -]) +TEST_EPOCH_SECONDS = cartesian_product( + TIMEZONES, + [ + (0, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0)), + ( + 1e-6, + dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1), + ), + ( + 1e-3, + dict( + year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1000 + ), + ), + ( + 3691.2, + dict( + year=1970, + day=1, + month=1, + hour=1, + minute=1, + second=31, + microsecond=200000, + ), + ), + ], +) -TEST_EPOCH_TO_SECONDS = cartesian_product(TIMEZONES, [ - (datetime.datetime(1970, 1, 1, 0, 1, 2, 0), 62), - (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=UTC), 62), -]) +TEST_EPOCH_TO_SECONDS = cartesian_product( + TIMEZONES, + [ + (datetime.datetime(1970, 1, 1, 0, 1, 2, 0), 62), + (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=UTC), 62), + ], +) -TEST_CONVERT_RELATIVE_TO_DATETIME = cartesian_product(TIMEZONES, [ - ( - '+0', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 1, 0, 0, 0), - ), - ( - '+1s', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), - datetime.datetime(2024, 1, 1, 0, 0, 1), - ), - ( - '-10w20d30h40m50s', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), - datetime.datetime(2023, 10, 1, 17, 19, 10), - ), - ( - '+0', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), - ), - ( - '+1s', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), - datetime.datetime(2024, 1, 1, 0, 0, 1, tzinfo=UTC), - ), - ( - '-10w20d30h40m50s', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2023, 10, 1, 17, 19, 10, tzinfo=UTC), - ), -]) +TEST_CONVERT_RELATIVE_TO_DATETIME = cartesian_product( + TIMEZONES, + [ + ( + "+0", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 1, 0, 0, 0), + ), + ( + "+1s", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 0, 1), + ), + ( + "-10w20d30h40m50s", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2023, 10, 1, 17, 19, 10), + ), + ( + "+0", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + ), + ( + "+1s", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 0, 1, tzinfo=UTC), + ), + ( + "-10w20d30h40m50s", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 10, 1, 17, 19, 10, tzinfo=UTC), + ), + ], +) -TEST_GET_RELATIVE_TIME_OPTION = cartesian_product(TIMEZONES, [ - ( - '+1d2h3m4s', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 2, 3, 4), - ), - ( - '-1w10d24h', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2023, 12, 14, 0, 0, 0), - ), - ( - '20240102040506Z', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 4, 5, 6), - ), - ( - '202401020405Z', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 4, 5, 0), - ), - ( - '+1d2h3m4s', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 2, 3, 4, tzinfo=UTC), - ), - ( - '-1w10d24h', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2023, 12, 14, 0, 0, 0, tzinfo=UTC), - ), - ( - '20240102040506Z', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 4, 5, 6, tzinfo=UTC), - ), - ( - '202401020405Z', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 4, 5, 0, tzinfo=UTC), - ), - ( - '+1d2h3m4s', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '20240102020304Z', - ), - ( - '-1w10d24h', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '20231214000000Z', - ), - ( - '20240102040506Z', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '20240102040506Z', - ), - ( - '202401020405Z', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '202401020405Z', - ), -]) +TEST_GET_RELATIVE_TIME_OPTION = cartesian_product( + TIMEZONES, + [ + ( + "+1d2h3m4s", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 2, 3, 4), + ), + ( + "-1w10d24h", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 12, 14, 0, 0, 0), + ), + ( + "20240102040506Z", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 6), + ), + ( + "202401020405Z", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 0), + ), + ( + "+1d2h3m4s", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 2, 3, 4, tzinfo=UTC), + ), + ( + "-1w10d24h", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 12, 14, 0, 0, 0, tzinfo=UTC), + ), + ( + "20240102040506Z", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 6, tzinfo=UTC), + ), + ( + "202401020405Z", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 0, tzinfo=UTC), + ), + ( + "+1d2h3m4s", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "20240102020304Z", + ), + ( + "-1w10d24h", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "20231214000000Z", + ), + ( + "20240102040506Z", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "20240102040506Z", + ), + ( + "202401020405Z", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "202401020405Z", + ), + ], +) if sys.version_info >= (3, 5): ONE_HOUR_PLUS = datetime.timezone(datetime.timedelta(hours=1)) - TEST_REMOVE_TIMEZONE.extend(cartesian_product(TIMEZONES, [ - ( - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), - datetime.datetime(2023, 12, 31, 23, 1, 2), - ), - ])) - TEST_UTC_TIMEZONE.extend(cartesian_product(TIMEZONES, [ - ( - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), - datetime.datetime(2023, 12, 31, 23, 1, 2, tzinfo=UTC), - ), - ])) - TEST_EPOCH_TO_SECONDS.extend(cartesian_product(TIMEZONES, [ - (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=ONE_HOUR_PLUS), 62 - 3600), - ])) - TEST_GET_RELATIVE_TIME_OPTION.extend(cartesian_product(TIMEZONES, [ - ( - '20240102040506+0100', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 3, 5, 6), - ), - ( - '202401020405+0100', - 'foo', - 'cryptography', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 3, 5, 0), - ), - ( - '20240102040506+0100', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 3, 5, 6, tzinfo=UTC), - ), - ( - '202401020405+0100', - 'foo', - 'cryptography', - True, - datetime.datetime(2024, 1, 1, 0, 0, 0), - datetime.datetime(2024, 1, 2, 3, 5, 0, tzinfo=UTC), - ), - ( - '20240102040506+0100', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '20240102040506+0100', - ), - ( - '202401020405+0100', - 'foo', - 'pyopenssl', - False, - datetime.datetime(2024, 1, 1, 0, 0, 0), - '202401020405+0100', - ), - ])) + TEST_REMOVE_TIMEZONE.extend( + cartesian_product( + TIMEZONES, + [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), + datetime.datetime(2023, 12, 31, 23, 1, 2), + ), + ], + ) + ) + TEST_UTC_TIMEZONE.extend( + cartesian_product( + TIMEZONES, + [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), + datetime.datetime(2023, 12, 31, 23, 1, 2, tzinfo=UTC), + ), + ], + ) + ) + TEST_EPOCH_TO_SECONDS.extend( + cartesian_product( + TIMEZONES, + [ + ( + datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=ONE_HOUR_PLUS), + 62 - 3600, + ), + ], + ) + ) + TEST_GET_RELATIVE_TIME_OPTION.extend( + cartesian_product( + TIMEZONES, + [ + ( + "20240102040506+0100", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 6), + ), + ( + "202401020405+0100", + "foo", + "cryptography", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 0), + ), + ( + "20240102040506+0100", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 6, tzinfo=UTC), + ), + ( + "202401020405+0100", + "foo", + "cryptography", + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 0, tzinfo=UTC), + ), + ( + "20240102040506+0100", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "20240102040506+0100", + ), + ( + "202401020405+0100", + "foo", + "pyopenssl", + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + "202401020405+0100", + ), + ], + ) + ) @pytest.mark.parametrize("timezone, input, expected", TEST_REMOVE_TIMEZONE) @@ -338,7 +398,7 @@ def test_epoch_seconds(timezone, seconds, timestamp): ts_wo_tz = datetime.datetime(**timestamp) assert seconds == get_epoch_seconds(ts_wo_tz) timestamp_w_tz = dict(timestamp) - timestamp_w_tz['tzinfo'] = UTC + timestamp_w_tz["tzinfo"] = UTC ts_w_tz = datetime.datetime(**timestamp_w_tz) assert seconds == get_epoch_seconds(ts_w_tz) output_1 = from_epoch_seconds(seconds, with_timezone=False) @@ -353,15 +413,33 @@ def test_epoch_to_seconds(timezone, timestamp, expected_seconds): assert expected_seconds == get_epoch_seconds(timestamp) -@pytest.mark.parametrize("timezone, relative_time_string, with_timezone, now, expected", TEST_CONVERT_RELATIVE_TO_DATETIME) -def test_convert_relative_to_datetime(timezone, relative_time_string, with_timezone, now, expected): +@pytest.mark.parametrize( + "timezone, relative_time_string, with_timezone, now, expected", + TEST_CONVERT_RELATIVE_TO_DATETIME, +) +def test_convert_relative_to_datetime( + timezone, relative_time_string, with_timezone, now, expected +): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): - output = convert_relative_to_datetime(relative_time_string, with_timezone=with_timezone, now=now) + output = convert_relative_to_datetime( + relative_time_string, with_timezone=with_timezone, now=now + ) assert expected == output -@pytest.mark.parametrize("timezone, input_string, input_name, backend, with_timezone, now, expected", TEST_GET_RELATIVE_TIME_OPTION) -def test_get_relative_time_option(timezone, input_string, input_name, backend, with_timezone, now, expected): +@pytest.mark.parametrize( + "timezone, input_string, input_name, backend, with_timezone, now, expected", + TEST_GET_RELATIVE_TIME_OPTION, +) +def test_get_relative_time_option( + timezone, input_string, input_name, backend, with_timezone, now, expected +): with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): - output = get_relative_time_option(input_string, input_name, backend=backend, with_timezone=with_timezone, now=now) + output = get_relative_time_option( + input_string, + input_name, + backend=backend, + with_timezone=with_timezone, + now=now, + ) assert expected == output diff --git a/tests/unit/plugins/modules/test_luks_device.py b/tests/unit/plugins/modules/test_luks_device.py index 728d9e9f..89bcd294 100644 --- a/tests/unit/plugins/modules/test_luks_device.py +++ b/tests/unit/plugins/modules/test_luks_device.py @@ -25,11 +25,13 @@ class DummyModule(object): # ===== Handler & CryptHandler methods tests ===== + def test_generate_luks_name(monkeypatch): module = DummyModule() module.params["passphrase_encoding"] = "text" - monkeypatch.setattr(luks_device.Handler, "_run_command", - lambda x, y: [0, "UUID", ""]) + monkeypatch.setattr( + luks_device.Handler, "_run_command", lambda x, y: [0, "UUID", ""] + ) crypt = luks_device.CryptHandler(module) assert crypt.generate_luks_name("/dev/dummy") == "luks-UUID" @@ -37,8 +39,11 @@ def test_generate_luks_name(monkeypatch): def test_get_container_name_by_device(monkeypatch): module = DummyModule() module.params["passphrase_encoding"] = "text" - monkeypatch.setattr(luks_device.Handler, "_run_command", - lambda x, y: [0, "crypt container_name", ""]) + monkeypatch.setattr( + luks_device.Handler, + "_run_command", + lambda x, y: [0, "crypt container_name", ""], + ) crypt = luks_device.CryptHandler(module) assert crypt.get_container_name_by_device("/dev/dummy") == "container_name" @@ -46,8 +51,11 @@ def test_get_container_name_by_device(monkeypatch): def test_get_container_device_by_name(monkeypatch): module = DummyModule() module.params["passphrase_encoding"] = "text" - monkeypatch.setattr(luks_device.Handler, "_run_command", - lambda x, y: [0, "device: /dev/luksdevice", ""]) + monkeypatch.setattr( + luks_device.Handler, + "_run_command", + lambda x, y: [0, "device: /dev/luksdevice", ""], + ) crypt = luks_device.CryptHandler(module) assert crypt.get_container_device_by_name("dummy") == "/dev/luksdevice" @@ -60,15 +68,11 @@ def test_run_luks_remove(monkeypatch): module = DummyModule() module.params["passphrase_encoding"] = "text" - monkeypatch.setattr(luks_device.CryptHandler, - "get_container_name_by_device", - lambda x, y: None) - monkeypatch.setattr(luks_device.Handler, - "_run_command", - run_command_check) - monkeypatch.setattr(luks_device, - "wipe_luks_headers", - lambda device: True) + monkeypatch.setattr( + luks_device.CryptHandler, "get_container_name_by_device", lambda x, y: None + ) + monkeypatch.setattr(luks_device.Handler, "_run_command", run_command_check) + monkeypatch.setattr(luks_device, "wipe_luks_headers", lambda device: True) crypt = luks_device.CryptHandler(module) crypt.run_luks_remove("dummy") @@ -95,14 +99,16 @@ LUKS_CREATE_DATA = ( ("dummy", None, "corge", "present", True, None, "dummy", "dummy", False), ("dummy", "key", None, "present", False, None, None, None, True), ("dummy", "key", None, "present", False, None, None, "dummy", True), - ("dummy", "key", None, "present", False, None, "dummy", None, True)) + ("dummy", "key", None, "present", False, None, "dummy", None, True), +) # device, state, is_luks, expected LUKS_REMOVE_DATA = ( ("dummy", "absent", True, True), (None, "absent", True, False), ("dummy", "present", True, False), - ("dummy", "absent", False, False)) + ("dummy", "absent", False, False), +) # device, key, passphrase, state, name, name_by_dev, expected LUKS_OPEN_DATA = ( @@ -121,7 +127,8 @@ LUKS_OPEN_DATA = ( (None, None, "quux", "opened", "name", None, False), ("dummy", None, None, "opened", "name", None, False), ("dummy", None, "quuz", "opened", "name", "name", False), - ("dummy", None, "corge", "opened", "beer", "name", "exception")) + ("dummy", None, "corge", "opened", "beer", "name", "exception"), +) # device, dev_by_name, name, name_by_dev, state, label, expected LUKS_CLOSE_DATA = ( @@ -131,7 +138,8 @@ LUKS_CLOSE_DATA = ( ("dummy", "dummy", "name", "name", "closed", None, True), (None, "dummy", "name", "name", "closed", None, True), ("dummy", "dummy", None, "name", "closed", None, True), - (None, "dummy", None, "name", "closed", None, False)) + (None, "dummy", None, "name", "closed", None, False), +) # device, key, passphrase, new_key, new_passphrase, state, label, expected LUKS_ADD_KEY_DATA = ( @@ -150,7 +158,8 @@ LUKS_ADD_KEY_DATA = ( ("dummy", "key", None, None, None, "present", None, False), ("dummy", "key", None, None, "new_pass", "absent", None, "exception"), ("dummy", None, "pass", None, "new_pass", "present", None, True), - (None, None, "pass", None, "new_pass", "present", "labelName", True)) + (None, None, "pass", None, "new_pass", "present", "labelName", True), +) # device, remove_key, remove_passphrase, state, label, expected LUKS_REMOVE_KEY_DATA = ( @@ -163,15 +172,26 @@ LUKS_REMOVE_KEY_DATA = ( (None, None, "foo", None, "present", None, False), (None, None, "foo", None, "present", "labelName", True), ("dummy", None, None, None, "present", None, False), - ("dummy", None, "foo", None, "absent", None, "exception")) + ("dummy", None, "foo", None, "absent", None, "exception"), +) -@pytest.mark.parametrize("device, keyfile, passphrase, state, is_luks, " + - "label, cipher, hash_, expected", - ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]) - for d in LUKS_CREATE_DATA)) -def test_luks_create(device, keyfile, passphrase, state, is_luks, label, cipher, hash_, - expected, monkeypatch): +@pytest.mark.parametrize( + "device, keyfile, passphrase, state, is_luks, " + "label, cipher, hash_, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]) for d in LUKS_CREATE_DATA), +) +def test_luks_create( + device, + keyfile, + passphrase, + state, + is_luks, + label, + cipher, + hash_, + expected, + monkeypatch, +): module = DummyModule() module.params["device"] = device @@ -183,12 +203,14 @@ def test_luks_create(device, keyfile, passphrase, state, is_luks, label, cipher, module.params["cipher"] = cipher module.params["hash"] = hash_ - monkeypatch.setattr(luks_device.CryptHandler, "is_luks", - lambda x, y: is_luks) + monkeypatch.setattr(luks_device.CryptHandler, "is_luks", lambda x, y: is_luks) crypt = luks_device.CryptHandler(module) if device is None: - monkeypatch.setattr(luks_device.Handler, "get_device_by_label", - lambda x, y: [0, "/dev/dummy", ""]) + monkeypatch.setattr( + luks_device.Handler, + "get_device_by_label", + lambda x, y: [0, "/dev/dummy", ""], + ) try: conditions = luks_device.ConditionsHandler(module, crypt) assert conditions.luks_create() == expected @@ -196,9 +218,10 @@ def test_luks_create(device, keyfile, passphrase, state, is_luks, label, cipher, assert expected == "exception" -@pytest.mark.parametrize("device, state, is_luks, expected", - ((d[0], d[1], d[2], d[3]) - for d in LUKS_REMOVE_DATA)) +@pytest.mark.parametrize( + "device, state, is_luks, expected", + ((d[0], d[1], d[2], d[3]) for d in LUKS_REMOVE_DATA), +) def test_luks_remove(device, state, is_luks, expected, monkeypatch): module = DummyModule() @@ -206,8 +229,7 @@ def test_luks_remove(device, state, is_luks, expected, monkeypatch): module.params["passphrase_encoding"] = "text" module.params["state"] = state - monkeypatch.setattr(luks_device.CryptHandler, "is_luks", - lambda x, y: is_luks) + monkeypatch.setattr(luks_device.CryptHandler, "is_luks", lambda x, y: is_luks) crypt = luks_device.CryptHandler(module) try: conditions = luks_device.ConditionsHandler(module, crypt) @@ -216,12 +238,13 @@ def test_luks_remove(device, state, is_luks, expected, monkeypatch): assert expected == "exception" -@pytest.mark.parametrize("device, keyfile, passphrase, state, name, " - "name_by_dev, expected", - ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) - for d in LUKS_OPEN_DATA)) -def test_luks_open(device, keyfile, passphrase, state, name, name_by_dev, - expected, monkeypatch): +@pytest.mark.parametrize( + "device, keyfile, passphrase, state, name, name_by_dev, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) for d in LUKS_OPEN_DATA), +) +def test_luks_open( + device, keyfile, passphrase, state, name, name_by_dev, expected, monkeypatch +): module = DummyModule() module.params["device"] = device module.params["keyfile"] = keyfile @@ -230,14 +253,17 @@ def test_luks_open(device, keyfile, passphrase, state, name, name_by_dev, module.params["state"] = state module.params["name"] = name - monkeypatch.setattr(luks_device.CryptHandler, - "get_container_name_by_device", - lambda x, y: name_by_dev) - monkeypatch.setattr(luks_device.CryptHandler, - "get_container_device_by_name", - lambda x, y: device) - monkeypatch.setattr(luks_device.Handler, "_run_command", - lambda x, y: [0, device, ""]) + monkeypatch.setattr( + luks_device.CryptHandler, + "get_container_name_by_device", + lambda x, y: name_by_dev, + ) + monkeypatch.setattr( + luks_device.CryptHandler, "get_container_device_by_name", lambda x, y: device + ) + monkeypatch.setattr( + luks_device.Handler, "_run_command", lambda x, y: [0, device, ""] + ) crypt = luks_device.CryptHandler(module) try: conditions = luks_device.ConditionsHandler(module, crypt) @@ -246,12 +272,13 @@ def test_luks_open(device, keyfile, passphrase, state, name, name_by_dev, assert expected == "exception" -@pytest.mark.parametrize("device, dev_by_name, name, name_by_dev, " - "state, label, expected", - ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) - for d in LUKS_CLOSE_DATA)) -def test_luks_close(device, dev_by_name, name, name_by_dev, state, - label, expected, monkeypatch): +@pytest.mark.parametrize( + "device, dev_by_name, name, name_by_dev, state, label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) for d in LUKS_CLOSE_DATA), +) +def test_luks_close( + device, dev_by_name, name, name_by_dev, state, label, expected, monkeypatch +): module = DummyModule() module.params["device"] = device module.params["name"] = name @@ -259,12 +286,16 @@ def test_luks_close(device, dev_by_name, name, name_by_dev, state, module.params["state"] = state module.params["label"] = label - monkeypatch.setattr(luks_device.CryptHandler, - "get_container_name_by_device", - lambda x, y: name_by_dev) - monkeypatch.setattr(luks_device.CryptHandler, - "get_container_device_by_name", - lambda x, y: dev_by_name) + monkeypatch.setattr( + luks_device.CryptHandler, + "get_container_name_by_device", + lambda x, y: name_by_dev, + ) + monkeypatch.setattr( + luks_device.CryptHandler, + "get_container_device_by_name", + lambda x, y: dev_by_name, + ) crypt = luks_device.CryptHandler(module) try: conditions = luks_device.ConditionsHandler(module, crypt) @@ -273,12 +304,22 @@ def test_luks_close(device, dev_by_name, name, name_by_dev, state, assert expected == "exception" -@pytest.mark.parametrize("device, keyfile, passphrase, new_keyfile, " + - "new_passphrase, state, label, expected", - ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]) - for d in LUKS_ADD_KEY_DATA)) -def test_luks_add_key(device, keyfile, passphrase, new_keyfile, new_passphrase, - state, label, expected, monkeypatch): +@pytest.mark.parametrize( + "device, keyfile, passphrase, new_keyfile, " + + "new_passphrase, state, label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]) for d in LUKS_ADD_KEY_DATA), +) +def test_luks_add_key( + device, + keyfile, + passphrase, + new_keyfile, + new_passphrase, + state, + label, + expected, + monkeypatch, +): module = DummyModule() module.params["device"] = device module.params["keyfile"] = keyfile @@ -290,10 +331,12 @@ def test_luks_add_key(device, keyfile, passphrase, new_keyfile, new_passphrase, module.params["state"] = state module.params["label"] = label - monkeypatch.setattr(luks_device.Handler, "get_device_by_label", - lambda x, y: [0, "/dev/dummy", ""]) - monkeypatch.setattr(luks_device.CryptHandler, "luks_test_key", - lambda x, y, z, w: False) + monkeypatch.setattr( + luks_device.Handler, "get_device_by_label", lambda x, y: [0, "/dev/dummy", ""] + ) + monkeypatch.setattr( + luks_device.CryptHandler, "luks_test_key", lambda x, y, z, w: False + ) crypt = luks_device.CryptHandler(module) try: @@ -303,12 +346,21 @@ def test_luks_add_key(device, keyfile, passphrase, new_keyfile, new_passphrase, assert expected == "exception" -@pytest.mark.parametrize("device, remove_keyfile, remove_passphrase, remove_keyslot, " + - "state, label, expected", - ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) - for d in LUKS_REMOVE_KEY_DATA)) -def test_luks_remove_key(device, remove_keyfile, remove_passphrase, remove_keyslot, state, - label, expected, monkeypatch): +@pytest.mark.parametrize( + "device, remove_keyfile, remove_passphrase, remove_keyslot, " + + "state, label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) for d in LUKS_REMOVE_KEY_DATA), +) +def test_luks_remove_key( + device, + remove_keyfile, + remove_passphrase, + remove_keyslot, + state, + label, + expected, + monkeypatch, +): module = DummyModule() module.params["device"] = device @@ -319,12 +371,15 @@ def test_luks_remove_key(device, remove_keyfile, remove_passphrase, remove_keysl module.params["state"] = state module.params["label"] = label - monkeypatch.setattr(luks_device.Handler, "get_device_by_label", - lambda x, y: [0, "/dev/dummy", ""]) - monkeypatch.setattr(luks_device.Handler, "_run_command", - lambda x, y: [0, device, ""]) - monkeypatch.setattr(luks_device.CryptHandler, "luks_test_key", - lambda x, y, z, w: True) + monkeypatch.setattr( + luks_device.Handler, "get_device_by_label", lambda x, y: [0, "/dev/dummy", ""] + ) + monkeypatch.setattr( + luks_device.Handler, "_run_command", lambda x, y: [0, device, ""] + ) + monkeypatch.setattr( + luks_device.CryptHandler, "luks_test_key", lambda x, y, z, w: True + ) crypt = luks_device.CryptHandler(module) try: