From 1736602ce71268b65b511a48f7f3979569ebfa5f Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 19 Feb 2024 21:05:13 +0100 Subject: [PATCH] Allow to configure how serial numbers are provided to x509_crl. (#715) --- changelogs/fragments/715-x509_crl-serial.yml | 5 ++ plugins/modules/x509_crl.py | 56 ++++++++++++++++--- .../targets/x509_crl/tasks/impl.yml | 14 +++-- 3 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/715-x509_crl-serial.yml diff --git a/changelogs/fragments/715-x509_crl-serial.yml b/changelogs/fragments/715-x509_crl-serial.yml new file mode 100644 index 00000000..c745e10c --- /dev/null +++ b/changelogs/fragments/715-x509_crl-serial.yml @@ -0,0 +1,5 @@ +minor_changes: + - "x509_crl - the new option ``serial_numbers`` allow to configure in which format serial numbers can be provided + to ``revoked_certificates[].serial_number``. The default is as integers (``serial_numbers=integer``) for backwards compatibility; + setting ``serial_numbers=hex-octets`` allows to specify colon-separated hex octet strings like ``00:11:22:FF`` + (https://github.com/ansible-collections/community.crypto/issues/687, https://github.com/ansible-collections/community.crypto/pull/715)." diff --git a/plugins/modules/x509_crl.py b/plugins/modules/x509_crl.py index 4fbdf909..1ac97005 100644 --- a/plugins/modules/x509_crl.py +++ b/plugins/modules/x509_crl.py @@ -164,6 +164,21 @@ options: type: str default: sha256 + serial_numbers: + description: + - This option determines which values will be accepted for O(revoked_certificates[].serial_number). + - If set to V(integer) (default), serial numbers are assumed to be integers, for example V(66223). + (This example value is equivalent to the hex octet string V(01:02:AF).) + - If set to V(hex-octets), serial numbers are assumed to be colon-separated hex octet strings, + for example V(01:02:AF). + (This example value is equivalent to the integer V(66223).) + type: str + choices: + - integer + - hex-octets + default: integer + version_added: 2.18.0 + revoked_certificates: description: - List of certificates to be revoked. @@ -193,9 +208,13 @@ options: - Mutually exclusive with O(revoked_certificates[].path) and O(revoked_certificates[].content). One of these three options must be specified. - - This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings, - such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter). - type: int + - This option accepts integers or hex octet strings, depending on the value + of O(serial_numbers). + - If O(serial_numbers=integer), integers such as V(66223) must be provided. + - If O(serial_numbers=hex-octets), strings such as V(01:02:AF) must be provided. + - You can use the filters P(community.crypto.parse_serial#filter) and + P(community.crypto.to_serial#filter) to convert these two representations. + type: raw revocation_date: description: - The point in time the certificate was revoked. @@ -431,7 +450,9 @@ import traceback from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.common.text.converters import to_native, to_text +from ansible.module_utils.common.validation import check_type_int, check_type_str +from ansible_collections.community.crypto.plugins.module_utils.serial import parse_serial from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion from ansible_collections.community.crypto.plugins.module_utils.io import ( @@ -520,6 +541,7 @@ class CRL(OpenSSLObject): 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'] @@ -545,6 +567,8 @@ class CRL(OpenSSLObject): if self.digest is None: 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']): result = { @@ -576,7 +600,7 @@ class CRL(OpenSSLObject): ) else: # Specify serial_number (and potentially issuer) directly - result['serial_number'] = rc['serial_number'] + 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']] @@ -596,8 +620,6 @@ class CRL(OpenSSLObject): result['invalidity_date_critical'] = rc['invalidity_date_critical'] self.revoked_certificates.append(result) - self.module = module - self.backup = module.params['backup'] self.backup_file = None @@ -631,6 +653,25 @@ 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': + 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': + 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, )) + def _get_info(self, data): if data is None: return dict() @@ -896,7 +937,7 @@ def main(): options=dict( path=dict(type='path'), content=dict(type='str'), - serial_number=dict(type='int'), + 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), @@ -916,6 +957,7 @@ def main(): 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']), ), required_if=[ ('state', 'present', ['privatekey_path', 'privatekey_content'], True), diff --git a/tests/integration/targets/x509_crl/tasks/impl.yml b/tests/integration/targets/x509_crl/tasks/impl.yml index 11fa7dcc..29f2c473 100644 --- a/tests/integration/targets/x509_crl/tasks/impl.yml +++ b/tests/integration/targets/x509_crl/tasks/impl.yml @@ -119,7 +119,7 @@ - cert-2.pem register: slurp -- name: Create CRL 1 (idempotent with content, check mode) +- name: Create CRL 1 (idempotent with content and octet string serial, check mode) x509_crl: path: '{{ remote_tmp_dir }}/ca-crl1.crl' privatekey_content: "{{ slurp.results[0].content | b64decode }}" @@ -127,6 +127,7 @@ CN: Ansible last_update: 20191013000000Z next_update: 20191113000000Z + serial_numbers: hex-octets revoked_certificates: - content: "{{ slurp.results[1].content | b64decode }}" revocation_date: 20191013000000Z @@ -135,12 +136,12 @@ reason: key_compromise reason_critical: true invalidity_date: 20191012000000Z - - serial_number: 1234 + - serial_number: 04:D2 revocation_date: 20191001000000Z check_mode: true register: crl_1_idem_content_check -- name: Create CRL 1 (idempotent with content) +- name: Create CRL 1 (idempotent with content and octet string serial) x509_crl: path: '{{ remote_tmp_dir }}/ca-crl1.crl' privatekey_content: "{{ slurp.results[0].content | b64decode }}" @@ -148,6 +149,7 @@ CN: Ansible last_update: 20191013000000Z next_update: 20191113000000Z + serial_numbers: hex-octets revoked_certificates: - content: "{{ slurp.results[1].content | b64decode }}" revocation_date: 20191013000000Z @@ -156,7 +158,7 @@ reason: key_compromise reason_critical: true invalidity_date: 20191012000000Z - - serial_number: 1234 + - serial_number: 04:D2 revocation_date: 20191001000000Z register: crl_1_idem_content @@ -220,7 +222,7 @@ reason: key_compromise reason_critical: true invalidity_date: 20191012000000Z - - serial_number: 1234 + - serial_number: "1234" revocation_date: 20191001000000Z check_mode: true register: crl_1_format_idem_check @@ -242,7 +244,7 @@ reason: key_compromise reason_critical: true invalidity_date: 20191012000000Z - - serial_number: 1234 + - serial_number: "1234" revocation_date: 20191001000000Z return_content: true register: crl_1_format_idem