1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyrigt: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'community'}
13
14DOCUMENTATION = r'''
15---
16module: openssl_csr
17version_added: '2.4'
18short_description: Generate OpenSSL Certificate Signing Request (CSR)
19description:
20    - This module allows one to (re)generate OpenSSL certificate signing requests.
21    - It uses the pyOpenSSL python library to interact with openssl. This module supports
22      the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple
23      extensions.
24    - "Please note that the module regenerates existing CSR if it doesn't match the module's
25      options, or if it seems to be corrupt. If you are concerned that this could overwrite
26      your existing CSR, consider using the I(backup) option."
27    - The module can use the cryptography Python library, or the pyOpenSSL Python
28      library. By default, it tries to detect which one is available. This can be
29      overridden with the I(select_crypto_backend) option. Please note that the
30      PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
31requirements:
32    - Either cryptography >= 1.3
33    - Or pyOpenSSL >= 0.15
34author:
35- Yanis Guenane (@Spredzy)
36options:
37    state:
38        description:
39            - Whether the certificate signing request should exist or not, taking action if the state is different from what is stated.
40        type: str
41        default: present
42        choices: [ absent, present ]
43    digest:
44        description:
45            - The digest used when signing the certificate signing request with the private key.
46        type: str
47        default: sha256
48    privatekey_path:
49        description:
50            - The path to the private key to use when signing the certificate signing request.
51            - Required if I(state) is C(present).
52        type: path
53    privatekey_passphrase:
54        description:
55            - The passphrase for the private key.
56            - This is required if the private key is password protected.
57        type: str
58    version:
59        description:
60            - The version of the certificate signing request.
61            - "The only allowed value according to L(RFC 2986,https://tools.ietf.org/html/rfc2986#section-4.1)
62               is 1."
63        type: int
64        default: 1
65    force:
66        description:
67            - Should the certificate signing request be forced regenerated by this ansible module.
68        type: bool
69        default: no
70    path:
71        description:
72            - The name of the file into which the generated OpenSSL certificate signing request will be written.
73        type: path
74        required: true
75    subject:
76        description:
77            - Key/value pairs that will be present in the subject name field of the certificate signing request.
78            - If you need to specify more than one value with the same key, use a list as value.
79        type: dict
80        version_added: '2.5'
81    country_name:
82        description:
83            - The countryName field of the certificate signing request subject.
84        type: str
85        aliases: [ C, countryName ]
86    state_or_province_name:
87        description:
88            - The stateOrProvinceName field of the certificate signing request subject.
89        type: str
90        aliases: [ ST, stateOrProvinceName ]
91    locality_name:
92        description:
93            - The localityName field of the certificate signing request subject.
94        type: str
95        aliases: [ L, localityName ]
96    organization_name:
97        description:
98            - The organizationName field of the certificate signing request subject.
99        type: str
100        aliases: [ O, organizationName ]
101    organizational_unit_name:
102        description:
103            - The organizationalUnitName field of the certificate signing request subject.
104        type: str
105        aliases: [ OU, organizationalUnitName ]
106    common_name:
107        description:
108            - The commonName field of the certificate signing request subject.
109        type: str
110        aliases: [ CN, commonName ]
111    email_address:
112        description:
113            - The emailAddress field of the certificate signing request subject.
114        type: str
115        aliases: [ E, emailAddress ]
116    subject_alt_name:
117        description:
118            - SAN extension to attach to the certificate signing request.
119            - This can either be a 'comma separated string' or a YAML list.
120            - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
121              C(otherName) and the ones specific to your CA)
122            - Note that if no SAN is specified, but a common name, the common
123              name will be added as a SAN except if C(useCommonNameForSAN) is
124              set to I(false).
125            - More at U(https://tools.ietf.org/html/rfc5280#section-4.2.1.6).
126        type: list
127        elements: str
128        aliases: [ subjectAltName ]
129    subject_alt_name_critical:
130        description:
131            - Should the subjectAltName extension be considered as critical.
132        type: bool
133        aliases: [ subjectAltName_critical ]
134    use_common_name_for_san:
135        description:
136            - If set to C(yes), the module will fill the common name in for
137              C(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
138        type: bool
139        default: yes
140        version_added: '2.8'
141        aliases: [ useCommonNameForSAN ]
142    key_usage:
143        description:
144            - This defines the purpose (e.g. encipherment, signature, certificate signing)
145              of the key contained in the certificate.
146        type: list
147        elements: str
148        aliases: [ keyUsage ]
149    key_usage_critical:
150        description:
151            - Should the keyUsage extension be considered as critical.
152        type: bool
153        aliases: [ keyUsage_critical ]
154    extended_key_usage:
155        description:
156            - Additional restrictions (e.g. client authentication, server authentication)
157              on the allowed purposes for which the public key may be used.
158        type: list
159        elements: str
160        aliases: [ extKeyUsage, extendedKeyUsage ]
161    extended_key_usage_critical:
162        description:
163            - Should the extkeyUsage extension be considered as critical.
164        type: bool
165        aliases: [ extKeyUsage_critical, extendedKeyUsage_critical ]
166    basic_constraints:
167        description:
168            - Indicates basic constraints, such as if the certificate is a CA.
169        type: list
170        elements: str
171        version_added: '2.5'
172        aliases: [ basicConstraints ]
173    basic_constraints_critical:
174        description:
175            - Should the basicConstraints extension be considered as critical.
176        type: bool
177        version_added: '2.5'
178        aliases: [ basicConstraints_critical ]
179    ocsp_must_staple:
180        description:
181            - Indicates that the certificate should contain the OCSP Must Staple
182              extension (U(https://tools.ietf.org/html/rfc7633)).
183        type: bool
184        version_added: '2.5'
185        aliases: [ ocspMustStaple ]
186    ocsp_must_staple_critical:
187        description:
188            - Should the OCSP Must Staple extension be considered as critical
189            - Note that according to the RFC, this extension should not be marked
190              as critical, as old clients not knowing about OCSP Must Staple
191              are required to reject such certificates
192              (see U(https://tools.ietf.org/html/rfc7633#section-4)).
193        type: bool
194        version_added: '2.5'
195        aliases: [ ocspMustStaple_critical ]
196    select_crypto_backend:
197        description:
198            - Determines which crypto backend to use.
199            - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
200            - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
201            - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
202            - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
203              From that point on, only the C(cryptography) backend will be available.
204        type: str
205        default: auto
206        choices: [ auto, cryptography, pyopenssl ]
207        version_added: '2.8'
208    backup:
209        description:
210            - Create a backup file including a timestamp so you can get the original
211              CSR back if you overwrote it with a new one by accident.
212        type: bool
213        default: no
214        version_added: "2.8"
215    create_subject_key_identifier:
216        description:
217            - Create the Subject Key Identifier from the public key.
218            - "Please note that commercial CAs can ignore the value, respectively use a value of
219               their own choice instead. Specifying this option is mostly useful for self-signed
220               certificates or for own CAs."
221            - Note that this is only supported if the C(cryptography) backend is used!
222        type: bool
223        default: no
224        version_added: "2.9"
225    subject_key_identifier:
226        description:
227            - The subject key identifier as a hex string, where two bytes are separated by colons.
228            - "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
229            - "Please note that commercial CAs ignore this value, respectively use a value of their
230               own choice. Specifying this option is mostly useful for self-signed certificates
231               or for own CAs."
232            - Note that this option can only be used if I(create_subject_key_identifier) is C(no).
233            - Note that this is only supported if the C(cryptography) backend is used!
234        type: str
235        version_added: "2.9"
236    authority_key_identifier:
237        description:
238            - The authority key identifier as a hex string, where two bytes are separated by colons.
239            - "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
240            - If specified, I(authority_cert_issuer) must also be specified.
241            - "Please note that commercial CAs ignore this value, respectively use a value of their
242               own choice. Specifying this option is mostly useful for self-signed certificates
243               or for own CAs."
244            - Note that this is only supported if the C(cryptography) backend is used!
245            - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
246              I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
247        type: str
248        version_added: "2.9"
249    authority_cert_issuer:
250        description:
251            - Names that will be present in the authority cert issuer field of the certificate signing request.
252            - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
253              C(otherName) and the ones specific to your CA)
254            - "Example: C(DNS:ca.example.org)"
255            - If specified, I(authority_key_identifier) must also be specified.
256            - "Please note that commercial CAs ignore this value, respectively use a value of their
257               own choice. Specifying this option is mostly useful for self-signed certificates
258               or for own CAs."
259            - Note that this is only supported if the C(cryptography) backend is used!
260            - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
261              I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
262        type: list
263        elements: str
264        version_added: "2.9"
265    authority_cert_serial_number:
266        description:
267            - The authority cert serial number.
268            - Note that this is only supported if the C(cryptography) backend is used!
269            - "Please note that commercial CAs ignore this value, respectively use a value of their
270               own choice. Specifying this option is mostly useful for self-signed certificates
271               or for own CAs."
272            - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier),
273              I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
274        type: int
275        version_added: "2.9"
276extends_documentation_fragment:
277- files
278notes:
279    - If the certificate signing request already exists it will be checked whether subjectAltName,
280      keyUsage, extendedKeyUsage and basicConstraints only contain the requested values, whether
281      OCSP Must Staple is as requested, and if the request was signed by the given private key.
282seealso:
283- module: openssl_certificate
284- module: openssl_dhparam
285- module: openssl_pkcs12
286- module: openssl_privatekey
287- module: openssl_publickey
288'''
289
290EXAMPLES = r'''
291- name: Generate an OpenSSL Certificate Signing Request
292  openssl_csr:
293    path: /etc/ssl/csr/www.ansible.com.csr
294    privatekey_path: /etc/ssl/private/ansible.com.pem
295    common_name: www.ansible.com
296
297- name: Generate an OpenSSL Certificate Signing Request with a passphrase protected private key
298  openssl_csr:
299    path: /etc/ssl/csr/www.ansible.com.csr
300    privatekey_path: /etc/ssl/private/ansible.com.pem
301    privatekey_passphrase: ansible
302    common_name: www.ansible.com
303
304- name: Generate an OpenSSL Certificate Signing Request with Subject information
305  openssl_csr:
306    path: /etc/ssl/csr/www.ansible.com.csr
307    privatekey_path: /etc/ssl/private/ansible.com.pem
308    country_name: FR
309    organization_name: Ansible
310    email_address: jdoe@ansible.com
311    common_name: www.ansible.com
312
313- name: Generate an OpenSSL Certificate Signing Request with subjectAltName extension
314  openssl_csr:
315    path: /etc/ssl/csr/www.ansible.com.csr
316    privatekey_path: /etc/ssl/private/ansible.com.pem
317    subject_alt_name: 'DNS:www.ansible.com,DNS:m.ansible.com'
318
319- name: Generate an OpenSSL CSR with subjectAltName extension with dynamic list
320  openssl_csr:
321    path: /etc/ssl/csr/www.ansible.com.csr
322    privatekey_path: /etc/ssl/private/ansible.com.pem
323    subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}"
324  with_dict:
325    dns_server:
326    - www.ansible.com
327    - m.ansible.com
328
329- name: Force regenerate an OpenSSL Certificate Signing Request
330  openssl_csr:
331    path: /etc/ssl/csr/www.ansible.com.csr
332    privatekey_path: /etc/ssl/private/ansible.com.pem
333    force: yes
334    common_name: www.ansible.com
335
336- name: Generate an OpenSSL Certificate Signing Request with special key usages
337  openssl_csr:
338    path: /etc/ssl/csr/www.ansible.com.csr
339    privatekey_path: /etc/ssl/private/ansible.com.pem
340    common_name: www.ansible.com
341    key_usage:
342      - digitalSignature
343      - keyAgreement
344    extended_key_usage:
345      - clientAuth
346
347- name: Generate an OpenSSL Certificate Signing Request with OCSP Must Staple
348  openssl_csr:
349    path: /etc/ssl/csr/www.ansible.com.csr
350    privatekey_path: /etc/ssl/private/ansible.com.pem
351    common_name: www.ansible.com
352    ocsp_must_staple: yes
353'''
354
355RETURN = r'''
356privatekey:
357    description: Path to the TLS/SSL private key the CSR was generated for
358    returned: changed or success
359    type: str
360    sample: /etc/ssl/private/ansible.com.pem
361filename:
362    description: Path to the generated Certificate Signing Request
363    returned: changed or success
364    type: str
365    sample: /etc/ssl/csr/www.ansible.com.csr
366subject:
367    description: A list of the subject tuples attached to the CSR
368    returned: changed or success
369    type: list
370    elements: list
371    sample: "[('CN', 'www.ansible.com'), ('O', 'Ansible')]"
372subjectAltName:
373    description: The alternative names this CSR is valid for
374    returned: changed or success
375    type: list
376    elements: str
377    sample: [ 'DNS:www.ansible.com', 'DNS:m.ansible.com' ]
378keyUsage:
379    description: Purpose for which the public key may be used
380    returned: changed or success
381    type: list
382    elements: str
383    sample: [ 'digitalSignature', 'keyAgreement' ]
384extendedKeyUsage:
385    description: Additional restriction on the public key purposes
386    returned: changed or success
387    type: list
388    elements: str
389    sample: [ 'clientAuth' ]
390basicConstraints:
391    description: Indicates if the certificate belongs to a CA
392    returned: changed or success
393    type: list
394    elements: str
395    sample: ['CA:TRUE', 'pathLenConstraint:0']
396ocsp_must_staple:
397    description: Indicates whether the certificate has the OCSP
398                 Must Staple feature enabled
399    returned: changed or success
400    type: bool
401    sample: false
402backup_file:
403    description: Name of backup file created.
404    returned: changed and if I(backup) is C(yes)
405    type: str
406    sample: /path/to/www.ansible.com.csr.2019-03-09@11:22~
407'''
408
409import abc
410import binascii
411import os
412import traceback
413from distutils.version import LooseVersion
414
415from ansible.module_utils import crypto as crypto_utils
416from ansible.module_utils.basic import AnsibleModule, missing_required_lib
417from ansible.module_utils._text import to_native, to_bytes, to_text
418from ansible.module_utils.compat import ipaddress as compat_ipaddress
419
420MINIMAL_PYOPENSSL_VERSION = '0.15'
421MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
422
423PYOPENSSL_IMP_ERR = None
424try:
425    import OpenSSL
426    from OpenSSL import crypto
427    PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
428except ImportError:
429    PYOPENSSL_IMP_ERR = traceback.format_exc()
430    PYOPENSSL_FOUND = False
431else:
432    PYOPENSSL_FOUND = True
433    if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
434        # OpenSSL 1.1.0 or newer
435        OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
436        OPENSSL_MUST_STAPLE_VALUE = b"status_request"
437    else:
438        # OpenSSL 1.0.x or older
439        OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
440        OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
441
442CRYPTOGRAPHY_IMP_ERR = None
443try:
444    import cryptography
445    import cryptography.x509
446    import cryptography.x509.oid
447    import cryptography.exceptions
448    import cryptography.hazmat.backends
449    import cryptography.hazmat.primitives.serialization
450    import cryptography.hazmat.primitives.hashes
451    CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
452except ImportError:
453    CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
454    CRYPTOGRAPHY_FOUND = False
455else:
456    CRYPTOGRAPHY_FOUND = True
457    CRYPTOGRAPHY_MUST_STAPLE_NAME = cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
458    CRYPTOGRAPHY_MUST_STAPLE_VALUE = b"\x30\x03\x02\x01\x05"
459
460
461class CertificateSigningRequestError(crypto_utils.OpenSSLObjectError):
462    pass
463
464
465class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
466
467    def __init__(self, module):
468        super(CertificateSigningRequestBase, self).__init__(
469            module.params['path'],
470            module.params['state'],
471            module.params['force'],
472            module.check_mode
473        )
474        self.digest = module.params['digest']
475        self.privatekey_path = module.params['privatekey_path']
476        self.privatekey_passphrase = module.params['privatekey_passphrase']
477        self.version = module.params['version']
478        self.subjectAltName = module.params['subject_alt_name']
479        self.subjectAltName_critical = module.params['subject_alt_name_critical']
480        self.keyUsage = module.params['key_usage']
481        self.keyUsage_critical = module.params['key_usage_critical']
482        self.extendedKeyUsage = module.params['extended_key_usage']
483        self.extendedKeyUsage_critical = module.params['extended_key_usage_critical']
484        self.basicConstraints = module.params['basic_constraints']
485        self.basicConstraints_critical = module.params['basic_constraints_critical']
486        self.ocspMustStaple = module.params['ocsp_must_staple']
487        self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical']
488        self.create_subject_key_identifier = module.params['create_subject_key_identifier']
489        self.subject_key_identifier = module.params['subject_key_identifier']
490        self.authority_key_identifier = module.params['authority_key_identifier']
491        self.authority_cert_issuer = module.params['authority_cert_issuer']
492        self.authority_cert_serial_number = module.params['authority_cert_serial_number']
493        self.request = None
494        self.privatekey = None
495
496        if self.create_subject_key_identifier and self.subject_key_identifier is not None:
497            module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true')
498
499        self.backup = module.params['backup']
500        self.backup_file = None
501
502        self.subject = [
503            ('C', module.params['country_name']),
504            ('ST', module.params['state_or_province_name']),
505            ('L', module.params['locality_name']),
506            ('O', module.params['organization_name']),
507            ('OU', module.params['organizational_unit_name']),
508            ('CN', module.params['common_name']),
509            ('emailAddress', module.params['email_address']),
510        ]
511
512        if module.params['subject']:
513            self.subject = self.subject + crypto_utils.parse_name_field(module.params['subject'])
514        self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
515
516        if not self.subjectAltName and module.params['use_common_name_for_san']:
517            for sub in self.subject:
518                if sub[0] in ('commonName', 'CN'):
519                    self.subjectAltName = ['DNS:%s' % sub[1]]
520                    break
521
522        if self.subject_key_identifier is not None:
523            try:
524                self.subject_key_identifier = binascii.unhexlify(self.subject_key_identifier.replace(':', ''))
525            except Exception as e:
526                raise CertificateSigningRequestError('Cannot parse subject_key_identifier: {0}'.format(e))
527
528        if self.authority_key_identifier is not None:
529            try:
530                self.authority_key_identifier = binascii.unhexlify(self.authority_key_identifier.replace(':', ''))
531            except Exception as e:
532                raise CertificateSigningRequestError('Cannot parse authority_key_identifier: {0}'.format(e))
533
534    @abc.abstractmethod
535    def _generate_csr(self):
536        pass
537
538    def generate(self, module):
539        '''Generate the certificate signing request.'''
540        if not self.check(module, perms_required=False) or self.force:
541            result = self._generate_csr()
542            if self.backup:
543                self.backup_file = module.backup_local(self.path)
544            crypto_utils.write_file(module, result)
545            self.changed = True
546
547        file_args = module.load_file_common_arguments(module.params)
548        if module.set_fs_attributes_if_different(file_args, False):
549            self.changed = True
550
551    @abc.abstractmethod
552    def _load_private_key(self):
553        pass
554
555    @abc.abstractmethod
556    def _check_csr(self):
557        pass
558
559    def check(self, module, perms_required=True):
560        """Ensure the resource is in its desired state."""
561        state_and_perms = super(CertificateSigningRequestBase, self).check(module, perms_required)
562
563        self._load_private_key()
564
565        if not state_and_perms:
566            return False
567
568        return self._check_csr()
569
570    def remove(self, module):
571        if self.backup:
572            self.backup_file = module.backup_local(self.path)
573        super(CertificateSigningRequestBase, self).remove(module)
574
575    def dump(self):
576        '''Serialize the object into a dictionary.'''
577
578        result = {
579            'privatekey': self.privatekey_path,
580            'filename': self.path,
581            'subject': self.subject,
582            'subjectAltName': self.subjectAltName,
583            'keyUsage': self.keyUsage,
584            'extendedKeyUsage': self.extendedKeyUsage,
585            'basicConstraints': self.basicConstraints,
586            'ocspMustStaple': self.ocspMustStaple,
587            'changed': self.changed
588        }
589        if self.backup_file:
590            result['backup_file'] = self.backup_file
591
592        return result
593
594
595class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
596
597    def __init__(self, module):
598        if module.params['create_subject_key_identifier']:
599            module.fail_json(msg='You cannot use create_subject_key_identifier with the pyOpenSSL backend!')
600        for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number'):
601            if module.params[o] is not None:
602                module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
603        super(CertificateSigningRequestPyOpenSSL, self).__init__(module)
604
605    def _generate_csr(self):
606        req = crypto.X509Req()
607        req.set_version(self.version - 1)
608        subject = req.get_subject()
609        for entry in self.subject:
610            if entry[1] is not None:
611                # Workaround for https://github.com/pyca/pyopenssl/issues/165
612                nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(entry[0]))
613                if nid == 0:
614                    raise CertificateSigningRequestError('Unknown subject field identifier "{0}"'.format(entry[0]))
615                res = OpenSSL._util.lib.X509_NAME_add_entry_by_NID(subject._name, nid, OpenSSL._util.lib.MBSTRING_UTF8, to_bytes(entry[1]), -1, -1, 0)
616                if res == 0:
617                    raise CertificateSigningRequestError('Invalid value for subject field identifier "{0}": {1}'.format(entry[0], entry[1]))
618
619        extensions = []
620        if self.subjectAltName:
621            altnames = ', '.join(self.subjectAltName)
622            try:
623                extensions.append(crypto.X509Extension(b"subjectAltName", self.subjectAltName_critical, altnames.encode('ascii')))
624            except OpenSSL.crypto.Error as e:
625                raise CertificateSigningRequestError(
626                    'Error while parsing Subject Alternative Names {0} (check for missing type prefix, such as "DNS:"!): {1}'.format(
627                        ', '.join(["{0}".format(san) for san in self.subjectAltName]), str(e)
628                    )
629                )
630
631        if self.keyUsage:
632            usages = ', '.join(self.keyUsage)
633            extensions.append(crypto.X509Extension(b"keyUsage", self.keyUsage_critical, usages.encode('ascii')))
634
635        if self.extendedKeyUsage:
636            usages = ', '.join(self.extendedKeyUsage)
637            extensions.append(crypto.X509Extension(b"extendedKeyUsage", self.extendedKeyUsage_critical, usages.encode('ascii')))
638
639        if self.basicConstraints:
640            usages = ', '.join(self.basicConstraints)
641            extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii')))
642
643        if self.ocspMustStaple:
644            extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE))
645
646        if extensions:
647            req.add_extensions(extensions)
648
649        req.set_pubkey(self.privatekey)
650        req.sign(self.privatekey, self.digest)
651        self.request = req
652
653        return crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.request)
654
655    def _load_private_key(self):
656        try:
657            self.privatekey = crypto_utils.load_privatekey(self.privatekey_path, self.privatekey_passphrase)
658        except crypto_utils.OpenSSLBadPassphraseError as exc:
659            raise CertificateSigningRequestError(exc)
660
661    def _normalize_san(self, san):
662        # Apparently OpenSSL returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
663        # although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
664        if san.startswith('IP Address:'):
665            san = 'IP:' + san[len('IP Address:'):]
666        if san.startswith('IP:'):
667            ip = compat_ipaddress.ip_address(san[3:])
668            san = 'IP:{0}'.format(ip.compressed)
669        return san
670
671    def _check_csr(self):
672        def _check_subject(csr):
673            subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject]
674            current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in csr.get_subject().get_components()]
675            if not set(subject) == set(current_subject):
676                return False
677
678            return True
679
680        def _check_subjectAltName(extensions):
681            altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '')
682            altnames = [self._normalize_san(altname.strip()) for altname in
683                        to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()]
684            if self.subjectAltName:
685                if (set(altnames) != set([self._normalize_san(to_text(name)) for name in self.subjectAltName]) or
686                        altnames_ext.get_critical() != self.subjectAltName_critical):
687                    return False
688            else:
689                if altnames:
690                    return False
691
692            return True
693
694        def _check_keyUsage_(extensions, extName, expected, critical):
695            usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
696            if (not usages_ext and expected) or (usages_ext and not expected):
697                return False
698            elif not usages_ext and not expected:
699                return True
700            else:
701                current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')]
702                expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected]
703                return set(current) == set(expected) and usages_ext[0].get_critical() == critical
704
705        def _check_keyUsage(extensions):
706            usages_ext = [ext for ext in extensions if ext.get_short_name() == b'keyUsage']
707            if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
708                return False
709            elif not usages_ext and not self.keyUsage:
710                return True
711            else:
712                # OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
713                # (since keyUsage has a fixed bitfield for these values and is not extensible).
714                # Therefore, we create an extension for the wanted values, and compare the
715                # data of the extensions (which is the serialized bitfield).
716                expected_ext = crypto.X509Extension(b"keyUsage", False, ', '.join(self.keyUsage).encode('ascii'))
717                return usages_ext[0].get_data() == expected_ext.get_data() and usages_ext[0].get_critical() == self.keyUsage_critical
718
719        def _check_extenededKeyUsage(extensions):
720            return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical)
721
722        def _check_basicConstraints(extensions):
723            return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical)
724
725        def _check_ocspMustStaple(extensions):
726            oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE]
727            if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
728                # Older versions of libssl don't know about OCSP Must Staple
729                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'])
730            if self.ocspMustStaple:
731                return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical
732            else:
733                return len(oms_ext) == 0
734
735        def _check_extensions(csr):
736            extensions = csr.get_extensions()
737            return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
738                    _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
739                    _check_ocspMustStaple(extensions))
740
741        def _check_signature(csr):
742            try:
743                return csr.verify(self.privatekey)
744            except crypto.Error:
745                return False
746
747        try:
748            csr = crypto_utils.load_certificate_request(self.path, backend='pyopenssl')
749        except Exception as dummy:
750            return False
751
752        return _check_subject(csr) and _check_extensions(csr) and _check_signature(csr)
753
754
755class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
756
757    def __init__(self, module):
758        super(CertificateSigningRequestCryptography, self).__init__(module)
759        self.cryptography_backend = cryptography.hazmat.backends.default_backend()
760        self.module = module
761        if self.version != 1:
762            module.warn('The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)')
763
764    def _generate_csr(self):
765        csr = cryptography.x509.CertificateSigningRequestBuilder()
766        try:
767            csr = csr.subject_name(cryptography.x509.Name([
768                cryptography.x509.NameAttribute(crypto_utils.cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
769            ]))
770        except ValueError as e:
771            raise CertificateSigningRequestError(e)
772
773        if self.subjectAltName:
774            csr = csr.add_extension(cryptography.x509.SubjectAlternativeName([
775                crypto_utils.cryptography_get_name(name) for name in self.subjectAltName
776            ]), critical=self.subjectAltName_critical)
777
778        if self.keyUsage:
779            params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
780            csr = csr.add_extension(cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical)
781
782        if self.extendedKeyUsage:
783            usages = [crypto_utils.cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
784            csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages), critical=self.extendedKeyUsage_critical)
785
786        if self.basicConstraints:
787            params = {}
788            ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
789            csr = csr.add_extension(cryptography.x509.BasicConstraints(ca, path_length), critical=self.basicConstraints_critical)
790
791        if self.ocspMustStaple:
792            try:
793                # This only works with cryptography >= 2.1
794                csr = csr.add_extension(cryptography.x509.TLSFeature([cryptography.x509.TLSFeatureType.status_request]), critical=self.ocspMustStaple_critical)
795            except AttributeError as dummy:
796                csr = csr.add_extension(
797                    cryptography.x509.UnrecognizedExtension(CRYPTOGRAPHY_MUST_STAPLE_NAME, CRYPTOGRAPHY_MUST_STAPLE_VALUE),
798                    critical=self.ocspMustStaple_critical
799                )
800
801        if self.create_subject_key_identifier:
802            csr = csr.add_extension(
803                cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()),
804                critical=False
805            )
806        elif self.subject_key_identifier is not None:
807            csr = csr.add_extension(cryptography.x509.SubjectKeyIdentifier(self.subject_key_identifier), critical=False)
808
809        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:
810            issuers = None
811            if self.authority_cert_issuer is not None:
812                issuers = [crypto_utils.cryptography_get_name(n) for n in self.authority_cert_issuer]
813            csr = csr.add_extension(
814                cryptography.x509.AuthorityKeyIdentifier(self.authority_key_identifier, issuers, self.authority_cert_serial_number),
815                critical=False
816            )
817
818        digest = None
819        if crypto_utils.cryptography_key_needs_digest_for_signing(self.privatekey):
820            if self.digest == 'sha256':
821                digest = cryptography.hazmat.primitives.hashes.SHA256()
822            elif self.digest == 'sha384':
823                digest = cryptography.hazmat.primitives.hashes.SHA384()
824            elif self.digest == 'sha512':
825                digest = cryptography.hazmat.primitives.hashes.SHA512()
826            elif self.digest == 'sha1':
827                digest = cryptography.hazmat.primitives.hashes.SHA1()
828            elif self.digest == 'md5':
829                digest = cryptography.hazmat.primitives.hashes.MD5()
830            # FIXME
831            else:
832                raise CertificateSigningRequestError('Unsupported digest "{0}"'.format(self.digest))
833        try:
834            self.request = csr.sign(self.privatekey, digest, self.cryptography_backend)
835        except TypeError as e:
836            if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None:
837                self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
838            raise
839
840        return self.request.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM)
841
842    def _load_private_key(self):
843        try:
844            with open(self.privatekey_path, 'rb') as f:
845                self.privatekey = cryptography.hazmat.primitives.serialization.load_pem_private_key(
846                    f.read(),
847                    None if self.privatekey_passphrase is None else to_bytes(self.privatekey_passphrase),
848                    backend=self.cryptography_backend
849                )
850        except Exception as e:
851            raise CertificateSigningRequestError(e)
852
853    def _check_csr(self):
854        def _check_subject(csr):
855            subject = [(crypto_utils.cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject]
856            current_subject = [(sub.oid, sub.value) for sub in csr.subject]
857            return set(subject) == set(current_subject)
858
859        def _find_extension(extensions, exttype):
860            return next(
861                (ext for ext in extensions if isinstance(ext.value, exttype)),
862                None
863            )
864
865        def _check_subjectAltName(extensions):
866            current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName)
867            current_altnames = [str(altname) for altname in current_altnames_ext.value] if current_altnames_ext else []
868            altnames = [str(crypto_utils.cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
869            if set(altnames) != set(current_altnames):
870                return False
871            if altnames:
872                if current_altnames_ext.critical != self.subjectAltName_critical:
873                    return False
874            return True
875
876        def _check_keyUsage(extensions):
877            current_keyusage_ext = _find_extension(extensions, cryptography.x509.KeyUsage)
878            if not self.keyUsage:
879                return current_keyusage_ext is None
880            elif current_keyusage_ext is None:
881                return False
882            params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
883            for param in params:
884                if getattr(current_keyusage_ext.value, '_' + param) != params[param]:
885                    return False
886            if current_keyusage_ext.critical != self.keyUsage_critical:
887                return False
888            return True
889
890        def _check_extenededKeyUsage(extensions):
891            current_usages_ext = _find_extension(extensions, cryptography.x509.ExtendedKeyUsage)
892            current_usages = [str(usage) for usage in current_usages_ext.value] if current_usages_ext else []
893            usages = [str(crypto_utils.cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
894            if set(current_usages) != set(usages):
895                return False
896            if usages:
897                if current_usages_ext.critical != self.extendedKeyUsage_critical:
898                    return False
899            return True
900
901        def _check_basicConstraints(extensions):
902            bc_ext = _find_extension(extensions, cryptography.x509.BasicConstraints)
903            current_ca = bc_ext.value.ca if bc_ext else False
904            current_path_length = bc_ext.value.path_length if bc_ext else None
905            ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
906            # Check CA flag
907            if ca != current_ca:
908                return False
909            # Check path length
910            if path_length != current_path_length:
911                return False
912            # Check criticality
913            if self.basicConstraints:
914                return bc_ext is not None and bc_ext.critical == self.basicConstraints_critical
915            else:
916                return bc_ext is None
917
918        def _check_ocspMustStaple(extensions):
919            try:
920                # This only works with cryptography >= 2.1
921                tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
922                has_tlsfeature = True
923            except AttributeError as dummy:
924                tlsfeature_ext = next(
925                    (ext for ext in extensions if ext.value.oid == CRYPTOGRAPHY_MUST_STAPLE_NAME),
926                    None
927                )
928                has_tlsfeature = False
929            if self.ocspMustStaple:
930                if not tlsfeature_ext or tlsfeature_ext.critical != self.ocspMustStaple_critical:
931                    return False
932                if has_tlsfeature:
933                    return cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
934                else:
935                    return tlsfeature_ext.value.value == CRYPTOGRAPHY_MUST_STAPLE_VALUE
936            else:
937                return tlsfeature_ext is None
938
939        def _check_subject_key_identifier(extensions):
940            ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier)
941            if self.create_subject_key_identifier or self.subject_key_identifier is not None:
942                if not ext or ext.critical:
943                    return False
944                if self.create_subject_key_identifier:
945                    digest = cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()).digest
946                    return ext.value.digest == digest
947                else:
948                    return ext.value.digest == self.subject_key_identifier
949            else:
950                return ext is None
951
952        def _check_authority_key_identifier(extensions):
953            ext = _find_extension(extensions, cryptography.x509.AuthorityKeyIdentifier)
954            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:
955                if not ext or ext.critical:
956                    return False
957                aci = None
958                csr_aci = None
959                if self.authority_cert_issuer is not None:
960                    aci = [str(crypto_utils.cryptography_get_name(n)) for n in self.authority_cert_issuer]
961                if ext.value.authority_cert_issuer is not None:
962                    csr_aci = [str(n) for n in ext.value.authority_cert_issuer]
963                return (ext.value.key_identifier == self.authority_key_identifier
964                        and csr_aci == aci
965                        and ext.value.authority_cert_serial_number == self.authority_cert_serial_number)
966            else:
967                return ext is None
968
969        def _check_extensions(csr):
970            extensions = csr.extensions
971            return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
972                    _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
973                    _check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and
974                    _check_authority_key_identifier(extensions))
975
976        def _check_signature(csr):
977            if not csr.is_signature_valid:
978                return False
979            # To check whether public key of CSR belongs to private key,
980            # encode both public keys and compare PEMs.
981            key_a = csr.public_key().public_bytes(
982                cryptography.hazmat.primitives.serialization.Encoding.PEM,
983                cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
984            )
985            key_b = self.privatekey.public_key().public_bytes(
986                cryptography.hazmat.primitives.serialization.Encoding.PEM,
987                cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
988            )
989            return key_a == key_b
990
991        try:
992            csr = crypto_utils.load_certificate_request(self.path, backend='cryptography')
993        except Exception as dummy:
994            return False
995
996        return _check_subject(csr) and _check_extensions(csr) and _check_signature(csr)
997
998
999def main():
1000    module = AnsibleModule(
1001        argument_spec=dict(
1002            state=dict(type='str', default='present', choices=['absent', 'present']),
1003            digest=dict(type='str', default='sha256'),
1004            privatekey_path=dict(type='path'),
1005            privatekey_passphrase=dict(type='str', no_log=True),
1006            version=dict(type='int', default=1),
1007            force=dict(type='bool', default=False),
1008            path=dict(type='path', required=True),
1009            subject=dict(type='dict'),
1010            country_name=dict(type='str', aliases=['C', 'countryName']),
1011            state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']),
1012            locality_name=dict(type='str', aliases=['L', 'localityName']),
1013            organization_name=dict(type='str', aliases=['O', 'organizationName']),
1014            organizational_unit_name=dict(type='str', aliases=['OU', 'organizationalUnitName']),
1015            common_name=dict(type='str', aliases=['CN', 'commonName']),
1016            email_address=dict(type='str', aliases=['E', 'emailAddress']),
1017            subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName']),
1018            subject_alt_name_critical=dict(type='bool', default=False, aliases=['subjectAltName_critical']),
1019            use_common_name_for_san=dict(type='bool', default=True, aliases=['useCommonNameForSAN']),
1020            key_usage=dict(type='list', elements='str', aliases=['keyUsage']),
1021            key_usage_critical=dict(type='bool', default=False, aliases=['keyUsage_critical']),
1022            extended_key_usage=dict(type='list', elements='str', aliases=['extKeyUsage', 'extendedKeyUsage']),
1023            extended_key_usage_critical=dict(type='bool', default=False, aliases=['extKeyUsage_critical', 'extendedKeyUsage_critical']),
1024            basic_constraints=dict(type='list', elements='str', aliases=['basicConstraints']),
1025            basic_constraints_critical=dict(type='bool', default=False, aliases=['basicConstraints_critical']),
1026            ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']),
1027            ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']),
1028            backup=dict(type='bool', default=False),
1029            create_subject_key_identifier=dict(type='bool', default=False),
1030            subject_key_identifier=dict(type='str'),
1031            authority_key_identifier=dict(type='str'),
1032            authority_cert_issuer=dict(type='list', elements='str'),
1033            authority_cert_serial_number=dict(type='int'),
1034            select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
1035        ),
1036        required_together=[('authority_cert_issuer', 'authority_cert_serial_number')],
1037        required_if=[('state', 'present', ['privatekey_path'])],
1038        add_file_common_args=True,
1039        supports_check_mode=True,
1040    )
1041
1042    base_dir = os.path.dirname(module.params['path']) or '.'
1043    if not os.path.isdir(base_dir):
1044        module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir)
1045
1046    backend = module.params['select_crypto_backend']
1047    if backend == 'auto':
1048        # Detection what is possible
1049        can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
1050        can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
1051
1052        # First try cryptography, then pyOpenSSL
1053        if can_use_cryptography:
1054            backend = 'cryptography'
1055        elif can_use_pyopenssl:
1056            backend = 'pyopenssl'
1057
1058        # Success?
1059        if backend == 'auto':
1060            module.fail_json(msg=("Can't detect any of the required Python libraries "
1061                                  "cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
1062                                      MINIMAL_CRYPTOGRAPHY_VERSION,
1063                                      MINIMAL_PYOPENSSL_VERSION))
1064    try:
1065        if backend == 'pyopenssl':
1066            if not PYOPENSSL_FOUND:
1067                module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
1068                                 exception=PYOPENSSL_IMP_ERR)
1069            try:
1070                getattr(crypto.X509Req, 'get_extensions')
1071            except AttributeError:
1072                module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
1073
1074            module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
1075            csr = CertificateSigningRequestPyOpenSSL(module)
1076        elif backend == 'cryptography':
1077            if not CRYPTOGRAPHY_FOUND:
1078                module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
1079                                 exception=CRYPTOGRAPHY_IMP_ERR)
1080            csr = CertificateSigningRequestCryptography(module)
1081
1082        if module.params['state'] == 'present':
1083            if module.check_mode:
1084                result = csr.dump()
1085                result['changed'] = module.params['force'] or not csr.check(module)
1086                module.exit_json(**result)
1087
1088            csr.generate(module)
1089
1090        else:
1091            if module.check_mode:
1092                result = csr.dump()
1093                result['changed'] = os.path.exists(module.params['path'])
1094                module.exit_json(**result)
1095
1096            csr.remove(module)
1097
1098        result = csr.dump()
1099        module.exit_json(**result)
1100    except crypto_utils.OpenSSLObjectError as exc:
1101        module.fail_json(msg=to_native(exc))
1102
1103
1104if __name__ == "__main__":
1105    main()
1106