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