1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2017, Guillaume Delpierre <gde@llew.me> 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 10 11DOCUMENTATION = r''' 12--- 13module: openssl_pkcs12 14author: 15- Guillaume Delpierre (@gdelpierre) 16short_description: Generate OpenSSL PKCS#12 archive 17description: 18 - This module allows one to (re-)generate PKCS#12. 19 - The module can use the cryptography Python library, or the pyOpenSSL Python 20 library. By default, it tries to detect which one is available, assuming none of the 21 I(iter_size) and I(maciter_size) options are used. This can be overridden with the 22 I(select_crypto_backend) option. 23 # Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0, 24 # and will be removed in community.crypto (x+1).0.0. 25requirements: 26 - PyOpenSSL >= 0.15 or cryptography >= 3.0 27options: 28 action: 29 description: 30 - C(export) or C(parse) a PKCS#12. 31 type: str 32 default: export 33 choices: [ export, parse ] 34 other_certificates: 35 description: 36 - List of other certificates to include. Pre Ansible 2.8 this parameter was called I(ca_certificates). 37 - Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates, 38 set I(other_certificates_parse_all) to C(true). 39 type: list 40 elements: path 41 aliases: [ ca_certificates ] 42 other_certificates_parse_all: 43 description: 44 - If set to C(true), assumes that the files mentioned in I(other_certificates) can contain more than one 45 certificate per file (or even none per file). 46 type: bool 47 default: false 48 version_added: 1.4.0 49 certificate_path: 50 description: 51 - The path to read certificates and private keys from. 52 - Must be in PEM format. 53 type: path 54 force: 55 description: 56 - Should the file be regenerated even if it already exists. 57 type: bool 58 default: no 59 friendly_name: 60 description: 61 - Specifies the friendly name for the certificate and private key. 62 type: str 63 aliases: [ name ] 64 iter_size: 65 description: 66 - Number of times to repeat the encryption step. 67 - This is not considered during idempotency checks. 68 - This is only used by the C(pyopenssl) backend. When using it, the default is C(2048). 69 type: int 70 maciter_size: 71 description: 72 - Number of times to repeat the MAC step. 73 - This is not considered during idempotency checks. 74 - This is only used by the C(pyopenssl) backend. When using it, the default is C(1). 75 type: int 76 passphrase: 77 description: 78 - The PKCS#12 password. 79 - "B(Note:) PKCS12 encryption is not secure and should not be used as a security mechanism. 80 If you need to store or send a PKCS12 file safely, you should additionally encrypt it 81 with something else." 82 type: str 83 path: 84 description: 85 - Filename to write the PKCS#12 file to. 86 type: path 87 required: true 88 privatekey_passphrase: 89 description: 90 - Passphrase source to decrypt any input private keys with. 91 type: str 92 privatekey_path: 93 description: 94 - File to read private key from. 95 type: path 96 state: 97 description: 98 - Whether the file should exist or not. 99 All parameters except C(path) are ignored when state is C(absent). 100 choices: [ absent, present ] 101 default: present 102 type: str 103 src: 104 description: 105 - PKCS#12 file path to parse. 106 type: path 107 backup: 108 description: 109 - Create a backup file including a timestamp so you can get the original 110 output file back if you overwrote it with a new one by accident. 111 type: bool 112 default: no 113 return_content: 114 description: 115 - If set to C(yes), will return the (current or generated) PKCS#12's content as I(pkcs12). 116 type: bool 117 default: no 118 version_added: "1.0.0" 119 select_crypto_backend: 120 description: 121 - Determines which crypto backend to use. 122 - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). 123 If one of I(iter_size) or I(maciter_size) is used, C(auto) will always result in C(pyopenssl) to be chosen 124 for backwards compatibility. 125 - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. 126 - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. 127 # - Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0, and will be 128 # removed in community.crypto (x+1).0.0. 129 # From that point on, only the C(cryptography) backend will be available. 130 type: str 131 default: auto 132 choices: [ auto, cryptography, pyopenssl ] 133 version_added: 1.7.0 134extends_documentation_fragment: 135 - files 136seealso: 137- module: community.crypto.x509_certificate 138- module: community.crypto.openssl_csr 139- module: community.crypto.openssl_dhparam 140- module: community.crypto.openssl_privatekey 141- module: community.crypto.openssl_publickey 142''' 143 144EXAMPLES = r''' 145- name: Generate PKCS#12 file 146 community.crypto.openssl_pkcs12: 147 action: export 148 path: /opt/certs/ansible.p12 149 friendly_name: raclette 150 privatekey_path: /opt/certs/keys/key.pem 151 certificate_path: /opt/certs/cert.pem 152 other_certificates: /opt/certs/ca.pem 153 # Note that if /opt/certs/ca.pem contains multiple certificates, 154 # only the first one will be used. See the other_certificates_parse_all 155 # option for changing this behavior. 156 state: present 157 158- name: Generate PKCS#12 file 159 community.crypto.openssl_pkcs12: 160 action: export 161 path: /opt/certs/ansible.p12 162 friendly_name: raclette 163 privatekey_path: /opt/certs/keys/key.pem 164 certificate_path: /opt/certs/cert.pem 165 other_certificates_parse_all: true 166 other_certificates: 167 - /opt/certs/ca_bundle.pem 168 # Since we set other_certificates_parse_all to true, all 169 # certificates in the CA bundle are included and not just 170 # the first one. 171 - /opt/certs/intermediate.pem 172 # In case this file has multiple certificates in it, 173 # all will be included as well. 174 state: present 175 176- name: Change PKCS#12 file permission 177 community.crypto.openssl_pkcs12: 178 action: export 179 path: /opt/certs/ansible.p12 180 friendly_name: raclette 181 privatekey_path: /opt/certs/keys/key.pem 182 certificate_path: /opt/certs/cert.pem 183 other_certificates: /opt/certs/ca.pem 184 state: present 185 mode: '0600' 186 187- name: Regen PKCS#12 file 188 community.crypto.openssl_pkcs12: 189 action: export 190 src: /opt/certs/ansible.p12 191 path: /opt/certs/ansible.p12 192 friendly_name: raclette 193 privatekey_path: /opt/certs/keys/key.pem 194 certificate_path: /opt/certs/cert.pem 195 other_certificates: /opt/certs/ca.pem 196 state: present 197 mode: '0600' 198 force: yes 199 200- name: Dump/Parse PKCS#12 file 201 community.crypto.openssl_pkcs12: 202 action: parse 203 src: /opt/certs/ansible.p12 204 path: /opt/certs/ansible.pem 205 state: present 206 207- name: Remove PKCS#12 file 208 community.crypto.openssl_pkcs12: 209 path: /opt/certs/ansible.p12 210 state: absent 211''' 212 213RETURN = r''' 214filename: 215 description: Path to the generate PKCS#12 file. 216 returned: changed or success 217 type: str 218 sample: /opt/certs/ansible.p12 219privatekey: 220 description: Path to the TLS/SSL private key the public key was generated from. 221 returned: changed or success 222 type: str 223 sample: /etc/ssl/private/ansible.com.pem 224backup_file: 225 description: Name of backup file created. 226 returned: changed and if I(backup) is C(yes) 227 type: str 228 sample: /path/to/ansible.com.pem.2019-03-09@11:22~ 229pkcs12: 230 description: The (current or generated) PKCS#12's content Base64 encoded. 231 returned: if I(state) is C(present) and I(return_content) is C(yes) 232 type: str 233 version_added: "1.0.0" 234''' 235 236import abc 237import base64 238import os 239import stat 240import traceback 241 242from distutils.version import LooseVersion 243 244from ansible.module_utils.basic import AnsibleModule, missing_required_lib 245from ansible.module_utils.common.text.converters import to_bytes, to_native 246 247from ansible_collections.community.crypto.plugins.module_utils.io import ( 248 load_file_if_exists, 249 write_file, 250) 251 252from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( 253 OpenSSLObjectError, 254 OpenSSLBadPassphraseError, 255) 256 257from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( 258 parse_pkcs12, 259) 260 261from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( 262 OpenSSLObject, 263 load_privatekey, 264 load_certificate, 265) 266 267from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( 268 split_pem_list, 269) 270 271MINIMAL_CRYPTOGRAPHY_VERSION = '3.0' 272MINIMAL_PYOPENSSL_VERSION = '0.15' 273 274PYOPENSSL_IMP_ERR = None 275try: 276 import OpenSSL 277 from OpenSSL import crypto 278 PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) 279except ImportError: 280 PYOPENSSL_IMP_ERR = traceback.format_exc() 281 PYOPENSSL_FOUND = False 282else: 283 PYOPENSSL_FOUND = True 284 285CRYPTOGRAPHY_IMP_ERR = None 286try: 287 import cryptography 288 from cryptography.hazmat.primitives import serialization 289 from cryptography.hazmat.primitives.serialization.pkcs12 import serialize_key_and_certificates 290 CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) 291except ImportError: 292 CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() 293 CRYPTOGRAPHY_FOUND = False 294else: 295 CRYPTOGRAPHY_FOUND = True 296 297 298def load_certificate_set(filename, backend): 299 ''' 300 Load list of concatenated PEM files, and return a list of parsed certificates. 301 ''' 302 with open(filename, 'rb') as f: 303 data = f.read().decode('utf-8') 304 return [load_certificate(None, content=cert.encode('utf-8'), backend=backend) for cert in split_pem_list(data)] 305 306 307class PkcsError(OpenSSLObjectError): 308 pass 309 310 311class Pkcs(OpenSSLObject): 312 def __init__(self, module, backend): 313 super(Pkcs, self).__init__( 314 module.params['path'], 315 module.params['state'], 316 module.params['force'], 317 module.check_mode 318 ) 319 self.backend = backend 320 self.action = module.params['action'] 321 self.other_certificates = module.params['other_certificates'] 322 self.other_certificates_parse_all = module.params['other_certificates_parse_all'] 323 self.certificate_path = module.params['certificate_path'] 324 self.friendly_name = module.params['friendly_name'] 325 self.iter_size = module.params['iter_size'] or 2048 326 self.maciter_size = module.params['maciter_size'] or 1 327 self.passphrase = module.params['passphrase'] 328 self.pkcs12 = None 329 self.privatekey_passphrase = module.params['privatekey_passphrase'] 330 self.privatekey_path = module.params['privatekey_path'] 331 self.pkcs12_bytes = None 332 self.return_content = module.params['return_content'] 333 self.src = module.params['src'] 334 335 if module.params['mode'] is None: 336 module.params['mode'] = '0400' 337 338 self.backup = module.params['backup'] 339 self.backup_file = None 340 341 if self.other_certificates: 342 if self.other_certificates_parse_all: 343 filenames = list(self.other_certificates) 344 self.other_certificates = [] 345 for other_cert_bundle in filenames: 346 self.other_certificates.extend(load_certificate_set(other_cert_bundle, self.backend)) 347 else: 348 self.other_certificates = [ 349 load_certificate(other_cert, backend=self.backend) for other_cert in self.other_certificates 350 ] 351 352 @abc.abstractmethod 353 def generate_bytes(self, module): 354 """Generate PKCS#12 file archive.""" 355 pass 356 357 @abc.abstractmethod 358 def parse_bytes(self, pkcs12_content): 359 pass 360 361 @abc.abstractmethod 362 def _dump_privatekey(self, pkcs12): 363 pass 364 365 @abc.abstractmethod 366 def _dump_certificate(self, pkcs12): 367 pass 368 369 @abc.abstractmethod 370 def _dump_other_certificates(self, pkcs12): 371 pass 372 373 @abc.abstractmethod 374 def _get_friendly_name(self, pkcs12): 375 pass 376 377 def check(self, module, perms_required=True): 378 """Ensure the resource is in its desired state.""" 379 380 state_and_perms = super(Pkcs, self).check(module, perms_required) 381 382 def _check_pkey_passphrase(): 383 if self.privatekey_passphrase: 384 try: 385 load_privatekey(self.privatekey_path, self.privatekey_passphrase, backend=self.backend) 386 except OpenSSLObjectError: 387 return False 388 return True 389 390 if not state_and_perms: 391 return state_and_perms 392 393 if os.path.exists(self.path) and module.params['action'] == 'export': 394 dummy = self.generate_bytes(module) 395 self.src = self.path 396 try: 397 pkcs12_privatekey, pkcs12_certificate, pkcs12_other_certificates, pkcs12_friendly_name = self.parse() 398 except OpenSSLObjectError: 399 return False 400 if (pkcs12_privatekey is not None) and (self.privatekey_path is not None): 401 expected_pkey = self._dump_privatekey(self.pkcs12) 402 if pkcs12_privatekey != expected_pkey: 403 return False 404 elif bool(pkcs12_privatekey) != bool(self.privatekey_path): 405 return False 406 407 if (pkcs12_certificate is not None) and (self.certificate_path is not None): 408 expected_cert = self._dump_certificate(self.pkcs12) 409 if pkcs12_certificate != expected_cert: 410 return False 411 elif bool(pkcs12_certificate) != bool(self.certificate_path): 412 return False 413 414 if (pkcs12_other_certificates is not None) and (self.other_certificates is not None): 415 expected_other_certs = self._dump_other_certificates(self.pkcs12) 416 if set(pkcs12_other_certificates) != set(expected_other_certs): 417 return False 418 elif bool(pkcs12_other_certificates) != bool(self.other_certificates): 419 return False 420 421 if pkcs12_privatekey: 422 # This check is required because pyOpenSSL will not return a friendly name 423 # if the private key is not set in the file 424 friendly_name = self._get_friendly_name(self.pkcs12) 425 if ((friendly_name is not None) and (pkcs12_friendly_name is not None)): 426 if friendly_name != pkcs12_friendly_name: 427 return False 428 elif bool(friendly_name) != bool(pkcs12_friendly_name): 429 return False 430 elif module.params['action'] == 'parse' and os.path.exists(self.src) and os.path.exists(self.path): 431 try: 432 pkey, cert, other_certs, friendly_name = self.parse() 433 except OpenSSLObjectError: 434 return False 435 expected_content = to_bytes( 436 ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) 437 ) 438 dumped_content = load_file_if_exists(self.path, ignore_errors=True) 439 if expected_content != dumped_content: 440 return False 441 else: 442 return False 443 444 return _check_pkey_passphrase() 445 446 def dump(self): 447 """Serialize the object into a dictionary.""" 448 449 result = { 450 'filename': self.path, 451 } 452 if self.privatekey_path: 453 result['privatekey_path'] = self.privatekey_path 454 if self.backup_file: 455 result['backup_file'] = self.backup_file 456 if self.return_content: 457 if self.pkcs12_bytes is None: 458 self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True) 459 result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None 460 461 return result 462 463 def remove(self, module): 464 if self.backup: 465 self.backup_file = module.backup_local(self.path) 466 super(Pkcs, self).remove(module) 467 468 def parse(self): 469 """Read PKCS#12 file.""" 470 471 try: 472 with open(self.src, 'rb') as pkcs12_fh: 473 pkcs12_content = pkcs12_fh.read() 474 return self.parse_bytes(pkcs12_content) 475 except IOError as exc: 476 raise PkcsError(exc) 477 478 def generate(self): 479 pass 480 481 def write(self, module, content, mode=None): 482 """Write the PKCS#12 file.""" 483 if self.backup: 484 self.backup_file = module.backup_local(self.path) 485 write_file(module, content, mode) 486 if self.return_content: 487 self.pkcs12_bytes = content 488 489 490class PkcsPyOpenSSL(Pkcs): 491 def __init__(self, module): 492 super(PkcsPyOpenSSL, self).__init__(module, 'pyopenssl') 493 494 def generate_bytes(self, module): 495 """Generate PKCS#12 file archive.""" 496 self.pkcs12 = crypto.PKCS12() 497 498 if self.other_certificates: 499 self.pkcs12.set_ca_certificates(self.other_certificates) 500 501 if self.certificate_path: 502 self.pkcs12.set_certificate(load_certificate(self.certificate_path, backend=self.backend)) 503 504 if self.friendly_name: 505 self.pkcs12.set_friendlyname(to_bytes(self.friendly_name)) 506 507 if self.privatekey_path: 508 try: 509 self.pkcs12.set_privatekey( 510 load_privatekey(self.privatekey_path, self.privatekey_passphrase, backend=self.backend)) 511 except OpenSSLBadPassphraseError as exc: 512 raise PkcsError(exc) 513 514 return self.pkcs12.export(self.passphrase, self.iter_size, self.maciter_size) 515 516 def parse_bytes(self, pkcs12_content): 517 try: 518 p12 = crypto.load_pkcs12(pkcs12_content, self.passphrase) 519 pkey = p12.get_privatekey() 520 if pkey is not None: 521 pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) 522 crt = p12.get_certificate() 523 if crt is not None: 524 crt = crypto.dump_certificate(crypto.FILETYPE_PEM, crt) 525 other_certs = [] 526 if p12.get_ca_certificates() is not None: 527 other_certs = [crypto.dump_certificate(crypto.FILETYPE_PEM, 528 other_cert) for other_cert in p12.get_ca_certificates()] 529 530 friendly_name = p12.get_friendlyname() 531 532 return (pkey, crt, other_certs, friendly_name) 533 except crypto.Error as exc: 534 raise PkcsError(exc) 535 536 def _dump_privatekey(self, pkcs12): 537 pk = pkcs12.get_privatekey() 538 return crypto.dump_privatekey(crypto.FILETYPE_PEM, pk) if pk else None 539 540 def _dump_certificate(self, pkcs12): 541 cert = pkcs12.get_certificate() 542 return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) if cert else None 543 544 def _dump_other_certificates(self, pkcs12): 545 return [ 546 crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert) 547 for other_cert in pkcs12.get_ca_certificates() 548 ] 549 550 def _get_friendly_name(self, pkcs12): 551 return pkcs12.get_friendlyname() 552 553 554class PkcsCryptography(Pkcs): 555 def __init__(self, module): 556 super(PkcsCryptography, self).__init__(module, 'cryptography') 557 558 def generate_bytes(self, module): 559 """Generate PKCS#12 file archive.""" 560 pkey = None 561 if self.privatekey_path: 562 try: 563 pkey = load_privatekey(self.privatekey_path, self.privatekey_passphrase, backend=self.backend) 564 except OpenSSLBadPassphraseError as exc: 565 raise PkcsError(exc) 566 567 cert = None 568 if self.certificate_path: 569 cert = load_certificate(self.certificate_path, backend=self.backend) 570 571 friendly_name = to_bytes(self.friendly_name) if self.friendly_name is not None else None 572 573 # Store fake object which can be used to retrieve the components back 574 self.pkcs12 = (pkey, cert, self.other_certificates, friendly_name) 575 576 return serialize_key_and_certificates( 577 friendly_name, 578 pkey, 579 cert, 580 self.other_certificates, 581 serialization.BestAvailableEncryption(to_bytes(self.passphrase)) 582 if self.passphrase else serialization.NoEncryption(), 583 ) 584 585 def parse_bytes(self, pkcs12_content): 586 try: 587 private_key, certificate, additional_certificates, friendly_name = parse_pkcs12( 588 pkcs12_content, self.passphrase) 589 590 pkey = None 591 if private_key is not None: 592 pkey = private_key.private_bytes( 593 encoding=serialization.Encoding.PEM, 594 format=serialization.PrivateFormat.TraditionalOpenSSL, 595 encryption_algorithm=serialization.NoEncryption(), 596 ) 597 598 crt = None 599 if certificate is not None: 600 crt = certificate.public_bytes(serialization.Encoding.PEM) 601 602 other_certs = [] 603 if additional_certificates is not None: 604 other_certs = [ 605 other_cert.public_bytes(serialization.Encoding.PEM) 606 for other_cert in additional_certificates 607 ] 608 609 return (pkey, crt, other_certs, friendly_name) 610 except ValueError as exc: 611 raise PkcsError(exc) 612 613 # The following methods will get self.pkcs12 passed, which is computed as: 614 # 615 # self.pkcs12 = (pkey, cert, self.other_certificates, self.friendly_name) 616 617 def _dump_privatekey(self, pkcs12): 618 return pkcs12[0].private_bytes( 619 encoding=serialization.Encoding.PEM, 620 format=serialization.PrivateFormat.TraditionalOpenSSL, 621 encryption_algorithm=serialization.NoEncryption(), 622 ) if pkcs12[0] else None 623 624 def _dump_certificate(self, pkcs12): 625 return pkcs12[1].public_bytes(serialization.Encoding.PEM) if pkcs12[1] else None 626 627 def _dump_other_certificates(self, pkcs12): 628 return [other_cert.public_bytes(serialization.Encoding.PEM) for other_cert in pkcs12[2]] 629 630 def _get_friendly_name(self, pkcs12): 631 return pkcs12[3] 632 633 634def select_backend(module, backend): 635 if backend == 'auto': 636 # Detection what is possible 637 can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) 638 can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) 639 640 # If no restrictions are provided, first try cryptography, then pyOpenSSL 641 if module.params['iter_size'] is not None or module.params['maciter_size'] is not None: 642 # If iter_size or maciter_size is specified, use pyOpenSSL backend 643 backend = 'pyopenssl' 644 elif can_use_cryptography: 645 backend = 'cryptography' 646 elif can_use_pyopenssl: 647 backend = 'pyopenssl' 648 649 # Success? 650 if backend == 'auto': 651 module.fail_json(msg=("Can't detect any of the required Python libraries " 652 "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( 653 MINIMAL_CRYPTOGRAPHY_VERSION, 654 MINIMAL_PYOPENSSL_VERSION)) 655 656 if backend == 'pyopenssl': 657 if not PYOPENSSL_FOUND: 658 module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), 659 exception=PYOPENSSL_IMP_ERR) 660 # module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', 661 # version='x.0.0', collection_name='community.crypto') 662 return backend, PkcsPyOpenSSL(module) 663 elif backend == 'cryptography': 664 if not CRYPTOGRAPHY_FOUND: 665 module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), 666 exception=CRYPTOGRAPHY_IMP_ERR) 667 return backend, PkcsCryptography(module) 668 else: 669 raise ValueError('Unsupported value for backend: {0}'.format(backend)) 670 671 672def main(): 673 argument_spec = dict( 674 action=dict(type='str', default='export', choices=['export', 'parse']), 675 other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']), 676 other_certificates_parse_all=dict(type='bool', default=False), 677 certificate_path=dict(type='path'), 678 force=dict(type='bool', default=False), 679 friendly_name=dict(type='str', aliases=['name']), 680 iter_size=dict(type='int'), 681 maciter_size=dict(type='int'), 682 passphrase=dict(type='str', no_log=True), 683 path=dict(type='path', required=True), 684 privatekey_passphrase=dict(type='str', no_log=True), 685 privatekey_path=dict(type='path'), 686 state=dict(type='str', default='present', choices=['absent', 'present']), 687 src=dict(type='path'), 688 backup=dict(type='bool', default=False), 689 return_content=dict(type='bool', default=False), 690 select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), 691 ) 692 693 required_if = [ 694 ['action', 'parse', ['src']], 695 ] 696 697 module = AnsibleModule( 698 add_file_common_args=True, 699 argument_spec=argument_spec, 700 required_if=required_if, 701 supports_check_mode=True, 702 ) 703 704 backend, pkcs12 = select_backend(module, module.params['select_crypto_backend']) 705 706 base_dir = os.path.dirname(module.params['path']) or '.' 707 if not os.path.isdir(base_dir): 708 module.fail_json( 709 name=base_dir, 710 msg="The directory '%s' does not exist or the path is not a directory" % base_dir 711 ) 712 713 try: 714 changed = False 715 716 if module.params['state'] == 'present': 717 if module.check_mode: 718 result = pkcs12.dump() 719 result['changed'] = module.params['force'] or not pkcs12.check(module) 720 module.exit_json(**result) 721 722 if not pkcs12.check(module, perms_required=False) or module.params['force']: 723 if module.params['action'] == 'export': 724 if not module.params['friendly_name']: 725 module.fail_json(msg='Friendly_name is required') 726 pkcs12_content = pkcs12.generate_bytes(module) 727 pkcs12.write(module, pkcs12_content, 0o600) 728 changed = True 729 else: 730 pkey, cert, other_certs, friendly_name = pkcs12.parse() 731 dump_content = ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) 732 pkcs12.write(module, to_bytes(dump_content)) 733 changed = True 734 735 file_args = module.load_file_common_arguments(module.params) 736 if module.check_file_absent_if_check_mode(file_args['path']): 737 changed = True 738 elif module.set_fs_attributes_if_different(file_args, changed): 739 changed = True 740 else: 741 if module.check_mode: 742 result = pkcs12.dump() 743 result['changed'] = os.path.exists(module.params['path']) 744 module.exit_json(**result) 745 746 if os.path.exists(module.params['path']): 747 pkcs12.remove(module) 748 changed = True 749 750 result = pkcs12.dump() 751 result['changed'] = changed 752 if os.path.exists(module.params['path']): 753 file_mode = "%04o" % stat.S_IMODE(os.stat(module.params['path']).st_mode) 754 result['mode'] = file_mode 755 756 module.exit_json(**result) 757 except OpenSSLObjectError as exc: 758 module.fail_json(msg=to_native(exc)) 759 760 761if __name__ == '__main__': 762 main() 763