diff --git a/lib/ansible/modules/crypto/openssl_csr.py b/lib/ansible/modules/crypto/openssl_csr.py index 3a37080a098..24948dbf5c5 100644 --- a/lib/ansible/modules/crypto/openssl_csr.py +++ b/lib/ansible/modules/crypto/openssl_csr.py @@ -22,7 +22,8 @@ short_description: Generate OpenSSL Certificate Signing Request (CSR) description: - "This module allows one to (re)generate OpenSSL certificate signing requests. It uses the pyOpenSSL python library to interact with openssl. This module supports - the subjectAltName as well as the keyUsage and extendedKeyUsage extensions." + the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple + extensions." requirements: - "python-pyOpenSSL >= 0.15" options: @@ -148,12 +149,29 @@ options: description: - Should the basicConstraints extension be considered as critical version_added: 2.5 + ocsp_must_staple: + required: false + aliases: ['ocspMustStaple'] + description: + - Indicates that the certificate should contain the OCSP Must Staple + extension (U(https://tools.ietf.org/html/rfc7633)). + version_added: 2.5 + ocsp_must_staple_critical: + required: false + aliases: [ 'ocspMustStaple_critical' ] + description: + - Should the OCSP Must Staple extension be considered as critical + - "Warning: according to the RFC, this extension should not be marked + as critical, as old clients not knowing about OCSP Must Staple + are required to reject such certificates + (see U(https://tools.ietf.org/html/rfc7633#section-4))." + version_added: 2.5 extends_documentation_fragment: files notes: - "If the certificate signing request already exists it will be checked whether subjectAltName, - keyUsage and extendedKeyUsage only contain the requested values and if the request was signed - by the given private key" + keyUsage, extendedKeyUsage and basicConstraints only contain the requested values, whether + OCSP Must Staple is as requested, and if the request was signed by the given private key." ''' @@ -204,6 +222,13 @@ EXAMPLES = ''' - keyAgreement extended_key_usage: - clientAuth + +# Generate an OpenSSL Certificate Signing Request with OCSP Must Staple +- openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + ocsp_must_staple: true ''' @@ -243,6 +268,12 @@ basicConstraints: returned: changed or success type: list sample: ['CA:TRUE', 'pathLenConstraint:0'] +ocsp_must_staple: + description: Indicates whether the certificate has the OCSP + Must Staple feature enabled + returned: changed or success + type: bool + sample: false ''' import os @@ -258,6 +289,14 @@ except ImportError: pyopenssl_found = False else: pyopenssl_found = True + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # OpenSSL 1.1.0 or newer + MUST_STAPLE_NAME = b"tlsfeature" + MUST_STAPLE_VALUE = b"status_request" + else: + # OpenSSL 1.0.x or older + MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" + MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" class CertificateSigningRequestError(crypto_utils.OpenSSLObjectError): @@ -285,6 +324,8 @@ class CertificateSigningRequest(crypto_utils.OpenSSLObject): self.extendedKeyUsage_critical = module.params['extendedKeyUsage_critical'] self.basicConstraints = module.params['basicConstraints'] self.basicConstraints_critical = module.params['basicConstraints_critical'] + self.ocspMustStaple = module.params['ocspMustStaple'] + self.ocspMustStaple_critical = module.params['ocspMustStaple_critical'] self.request = None self.privatekey = None @@ -338,6 +379,9 @@ class CertificateSigningRequest(crypto_utils.OpenSSLObject): usages = ', '.join(self.basicConstraints) extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii'))) + if self.ocspMustStaple: + extensions.append(crypto.X509Extension(MUST_STAPLE_NAME, self.ocspMustStaple_critical, MUST_STAPLE_VALUE)) + if extensions: req.add_extensions(extensions) @@ -407,10 +451,21 @@ class CertificateSigningRequest(crypto_utils.OpenSSLObject): def _check_basicConstraints(extensions): return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical) + def _check_ocspMustStaple(extensions): + oms_ext = [ext for ext in extensions if ext.get_short_name() == MUST_STAPLE_NAME and str(ext) == MUST_STAPLE_VALUE] + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: + # Older versions of libssl don't know about OCSP Must Staple + oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05']) + if self.ocspMustStaple: + return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical + else: + return len(oms_ext) == 0 + def _check_extensions(csr): extensions = csr.get_extensions() return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and - _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions)) + _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and + _check_ocspMustStaple(extensions)) def _check_signature(csr): try: @@ -436,6 +491,7 @@ class CertificateSigningRequest(crypto_utils.OpenSSLObject): 'keyUsage': self.keyUsage, 'extendedKeyUsage': self.extendedKeyUsage, 'basicConstraints': self.basicConstraints, + 'ocspMustStaple': self.ocspMustStaple, 'changed': self.changed } @@ -468,6 +524,8 @@ def main(): extendedKeyUsage_critical=dict(aliases=['extKeyUsage_critical', 'extended_key_usage_critical'], default=False, type='bool'), basicConstraints=dict(aliases=['basic_constraints'], type='list'), basicConstraints_critical=dict(aliases=['basic_constraints_critical'], default=False, type='bool'), + ocspMustStaple=dict(aliases=['ocsp_must_staple'], default=False, type='bool'), + ocspMustStaple_critical=dict(aliases=['ocsp_must_staple_critical'], default=False, type='bool'), ), add_file_common_args=True, supports_check_mode=True, diff --git a/test/integration/targets/openssl_csr/tasks/main.yml b/test/integration/targets/openssl_csr/tasks/main.yml index f3652531b65..1476467cfd4 100644 --- a/test/integration/targets/openssl_csr/tasks/main.yml +++ b/test/integration/targets/openssl_csr/tasks/main.yml @@ -51,6 +51,21 @@ privatekey_path: '{{ output_dir }}/privatekey.pem' commonName: www.ansible.com + - name: Generate CSR with OCSP Must Staple + openssl_csr: + path: '{{ output_dir }}/csr_ocsp.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: "DNS:www.ansible.com" + ocsp_must_staple: true + + - name: Generate CSR with OCSP Must Staple (test idempotency) + openssl_csr: + path: '{{ output_dir }}/csr_ocsp.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: "DNS:www.ansible.com" + ocsp_must_staple: true + register: csr_ocsp_idempotency + - import_tasks: ../tests/validate.yml when: pyopenssl_version.stdout is version('0.15', '>=') diff --git a/test/integration/targets/openssl_csr/tests/validate.yml b/test/integration/targets/openssl_csr/tests/validate.yml index 7ce291080ca..ff205734c31 100644 --- a/test/integration/targets/openssl_csr/tests/validate.yml +++ b/test/integration/targets/openssl_csr/tests/validate.yml @@ -19,7 +19,7 @@ - name: Validate CSR_KU_XKU (assert idempotency) assert: that: - - csr_ku_xku.changed == False + - csr_ku_xku is not changed - name: Validate old_API CSR (test - Common Name) shell: "openssl req -noout -subject -in {{ output_dir }}/csr_oldapi.csr -nameopt oneline,-space_eq" @@ -34,3 +34,18 @@ that: - csr_oldapi_cn.stdout.split('=')[-1] == 'www.ansible.com' - csr_oldapi_modulus.stdout == privatekey_modulus.stdout + +- name: Validate OCSP Must Staple CSR (test - everything) + shell: "openssl req -noout -in {{ output_dir }}/csr_ocsp.csr -text" + register: csr_ocsp + +- name: Validate OCSP Must Staple CSR (assert) + assert: + that: + - "(csr_ocsp.stdout is search('\\s+TLS Feature:\\s*\\n\\s+status_request\\s+')) or + (csr_ocsp.stdout is search('\\s+1.3.6.1.5.5.7.1.24:\\s*\\n\\s+0\\.\\.\\.\\.\\s+'))" + +- name: Validate OCSP Must Staple CSR (assert idempotency) + assert: + that: + - csr_ocsp_idempotency is not changed