1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> 5# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11 12DOCUMENTATION = r''' 13--- 14module: x509_certificate_info 15short_description: Provide information of OpenSSL X.509 certificates 16description: 17 - This module allows one to query information on OpenSSL certificates. 18 - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the 19 cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) 20 cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with 21 C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 22 and will be removed in community.crypto 2.0.0. 23 - Note that this module was called C(openssl_certificate_info) when included directly in Ansible 24 up to version 2.9. When moved to the collection C(community.crypto), it was renamed to 25 M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the 26 old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to 27 C(community.crypto.x509_certificate_info). When using FQCNs or when using the 28 L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook) 29 keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid 30 a deprecation warning. 31requirements: 32 - PyOpenSSL >= 0.15 or cryptography >= 1.6 33author: 34 - Felix Fontein (@felixfontein) 35 - Yanis Guenane (@Spredzy) 36 - Markus Teufelberger (@MarkusTeufelberger) 37options: 38 path: 39 description: 40 - Remote absolute path where the certificate file is loaded from. 41 - Either I(path) or I(content) must be specified, but not both. 42 type: path 43 content: 44 description: 45 - Content of the X.509 certificate in PEM format. 46 - Either I(path) or I(content) must be specified, but not both. 47 type: str 48 version_added: '1.0.0' 49 valid_at: 50 description: 51 - A dict of names mapping to time specifications. Every time specified here 52 will be checked whether the certificate is valid at this point. See the 53 C(valid_at) return value for informations on the result. 54 - Time can be specified either as relative time or as absolute timestamp. 55 - Time will always be interpreted as UTC. 56 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 57 + C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (in other words, pattern C(YYYYMMDDHHMMSSZ)). 58 Note that all timestamps will be treated as being in UTC. 59 type: dict 60 select_crypto_backend: 61 description: 62 - Determines which crypto backend to use. 63 - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). 64 - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. 65 - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. 66 - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. 67 From that point on, only the C(cryptography) backend will be available. 68 type: str 69 default: auto 70 choices: [ auto, cryptography, pyopenssl ] 71 72notes: 73 - All timestamp values are provided in ASN.1 TIME format, in other words, following the C(YYYYMMDDHHMMSSZ) pattern. 74 They are all in UTC. 75 - Supports C(check_mode). 76seealso: 77- module: community.crypto.x509_certificate 78- module: community.crypto.x509_certificate_pipe 79''' 80 81EXAMPLES = r''' 82- name: Generate a Self Signed OpenSSL certificate 83 community.crypto.x509_certificate: 84 path: /etc/ssl/crt/ansible.com.crt 85 privatekey_path: /etc/ssl/private/ansible.com.pem 86 csr_path: /etc/ssl/csr/ansible.com.csr 87 provider: selfsigned 88 89 90# Get information on the certificate 91 92- name: Get information on generated certificate 93 community.crypto.x509_certificate_info: 94 path: /etc/ssl/crt/ansible.com.crt 95 register: result 96 97- name: Dump information 98 ansible.builtin.debug: 99 var: result 100 101 102# Check whether the certificate is valid or not valid at certain times, fail 103# if this is not the case. The first task (x509_certificate_info) collects 104# the information, and the second task (assert) validates the result and 105# makes the playbook fail in case something is not as expected. 106 107- name: Test whether that certificate is valid tomorrow and/or in three weeks 108 community.crypto.x509_certificate_info: 109 path: /etc/ssl/crt/ansible.com.crt 110 valid_at: 111 point_1: "+1d" 112 point_2: "+3w" 113 register: result 114 115- name: Validate that certificate is valid tomorrow, but not in three weeks 116 assert: 117 that: 118 - result.valid_at.point_1 # valid in one day 119 - not result.valid_at.point_2 # not valid in three weeks 120''' 121 122RETURN = r''' 123expired: 124 description: Whether the certificate is expired (in other words, C(notAfter) is in the past). 125 returned: success 126 type: bool 127basic_constraints: 128 description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present. 129 returned: success 130 type: list 131 elements: str 132 sample: "[CA:TRUE, pathlen:1]" 133basic_constraints_critical: 134 description: Whether the C(basic_constraints) extension is critical. 135 returned: success 136 type: bool 137extended_key_usage: 138 description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present. 139 returned: success 140 type: list 141 elements: str 142 sample: "[Biometric Info, DVCS, Time Stamping]" 143extended_key_usage_critical: 144 description: Whether the C(extended_key_usage) extension is critical. 145 returned: success 146 type: bool 147extensions_by_oid: 148 description: Returns a dictionary for every extension OID. 149 returned: success 150 type: dict 151 contains: 152 critical: 153 description: Whether the extension is critical. 154 returned: success 155 type: bool 156 value: 157 description: The Base64 encoded value (in DER format) of the extension. 158 returned: success 159 type: str 160 sample: "MAMCAQU=" 161 sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' 162key_usage: 163 description: Entries in the C(key_usage) extension, or C(none) if extension is not present. 164 returned: success 165 type: str 166 sample: "[Key Agreement, Data Encipherment]" 167key_usage_critical: 168 description: Whether the C(key_usage) extension is critical. 169 returned: success 170 type: bool 171subject_alt_name: 172 description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present. 173 returned: success 174 type: list 175 elements: str 176 sample: "[DNS:www.ansible.com, IP:1.2.3.4]" 177subject_alt_name_critical: 178 description: Whether the C(subject_alt_name) extension is critical. 179 returned: success 180 type: bool 181ocsp_must_staple: 182 description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise. 183 returned: success 184 type: bool 185ocsp_must_staple_critical: 186 description: Whether the C(ocsp_must_staple) extension is critical. 187 returned: success 188 type: bool 189issuer: 190 description: 191 - The certificate's issuer. 192 - Note that for repeated values, only the last one will be returned. 193 returned: success 194 type: dict 195 sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' 196issuer_ordered: 197 description: The certificate's issuer as an ordered list of tuples. 198 returned: success 199 type: list 200 elements: list 201 sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' 202subject: 203 description: 204 - The certificate's subject as a dictionary. 205 - Note that for repeated values, only the last one will be returned. 206 returned: success 207 type: dict 208 sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' 209subject_ordered: 210 description: The certificate's subject as an ordered list of tuples. 211 returned: success 212 type: list 213 elements: list 214 sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' 215not_after: 216 description: C(notAfter) date as ASN.1 TIME. 217 returned: success 218 type: str 219 sample: 20190413202428Z 220not_before: 221 description: C(notBefore) date as ASN.1 TIME. 222 returned: success 223 type: str 224 sample: 20190331202428Z 225public_key: 226 description: Certificate's public key in PEM format. 227 returned: success 228 type: str 229 sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." 230public_key_type: 231 description: 232 - The certificate's public key's type. 233 - One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448). 234 - Will start with C(unknown) if the key type cannot be determined. 235 returned: success 236 type: str 237 version_added: 1.7.0 238 sample: RSA 239public_key_data: 240 description: 241 - Public key data. Depends on the public key's type. 242 returned: success 243 type: dict 244 version_added: 1.7.0 245 contains: 246 size: 247 description: 248 - Bit size of modulus (RSA) or prime number (DSA). 249 type: int 250 returned: When C(public_key_type=RSA) or C(public_key_type=DSA) 251 modulus: 252 description: 253 - The RSA key's modulus. 254 type: int 255 returned: When C(public_key_type=RSA) 256 exponent: 257 description: 258 - The RSA key's public exponent. 259 type: int 260 returned: When C(public_key_type=RSA) 261 p: 262 description: 263 - The C(p) value for DSA. 264 - This is the prime modulus upon which arithmetic takes place. 265 type: int 266 returned: When C(public_key_type=DSA) 267 q: 268 description: 269 - The C(q) value for DSA. 270 - This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the 271 multiplicative group of the prime field used. 272 type: int 273 returned: When C(public_key_type=DSA) 274 g: 275 description: 276 - The C(g) value for DSA. 277 - This is the element spanning the subgroup of the multiplicative group of the prime field used. 278 type: int 279 returned: When C(public_key_type=DSA) 280 curve: 281 description: 282 - The curve's name for ECC. 283 type: str 284 returned: When C(public_key_type=ECC) 285 exponent_size: 286 description: 287 - The maximum number of bits of a private key. This is basically the bit size of the subgroup used. 288 type: int 289 returned: When C(public_key_type=ECC) 290 x: 291 description: 292 - The C(x) coordinate for the public point on the elliptic curve. 293 type: int 294 returned: When C(public_key_type=ECC) 295 y: 296 description: 297 - For C(public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve. 298 - For C(public_key_type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key. 299 type: int 300 returned: When C(public_key_type=DSA) or C(public_key_type=ECC) 301public_key_fingerprints: 302 description: 303 - Fingerprints of certificate's public key. 304 - For every hash algorithm available, the fingerprint is computed. 305 returned: success 306 type: dict 307 sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', 308 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." 309fingerprints: 310 description: 311 - Fingerprints of the DER-encoded form of the whole certificate. 312 - For every hash algorithm available, the fingerprint is computed. 313 returned: success 314 type: dict 315 sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', 316 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." 317 version_added: 1.2.0 318signature_algorithm: 319 description: The signature algorithm used to sign the certificate. 320 returned: success 321 type: str 322 sample: sha256WithRSAEncryption 323serial_number: 324 description: The certificate's serial number. 325 returned: success 326 type: int 327 sample: 1234 328version: 329 description: The certificate version. 330 returned: success 331 type: int 332 sample: 3 333valid_at: 334 description: For every time stamp provided in the I(valid_at) option, a 335 boolean whether the certificate is valid at that point in time 336 or not. 337 returned: success 338 type: dict 339subject_key_identifier: 340 description: 341 - The certificate's subject key identifier. 342 - The identifier is returned in hexadecimal, with C(:) used to separate bytes. 343 - Is C(none) if the C(SubjectKeyIdentifier) extension is not present. 344 returned: success and if the pyOpenSSL backend is I(not) used 345 type: str 346 sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' 347authority_key_identifier: 348 description: 349 - The certificate's authority key identifier. 350 - The identifier is returned in hexadecimal, with C(:) used to separate bytes. 351 - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. 352 returned: success and if the pyOpenSSL backend is I(not) used 353 type: str 354 sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' 355authority_cert_issuer: 356 description: 357 - The certificate's authority cert issuer as a list of general names. 358 - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. 359 returned: success and if the pyOpenSSL backend is I(not) used 360 type: list 361 elements: str 362 sample: "[DNS:www.ansible.com, IP:1.2.3.4]" 363authority_cert_serial_number: 364 description: 365 - The certificate's authority cert serial number. 366 - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. 367 returned: success and if the pyOpenSSL backend is I(not) used 368 type: int 369 sample: '12345' 370ocsp_uri: 371 description: The OCSP responder URI, if included in the certificate. Will be 372 C(none) if no OCSP responder URI is included. 373 returned: success 374 type: str 375''' 376 377 378from ansible.module_utils.basic import AnsibleModule 379from ansible.module_utils.six import string_types 380from ansible.module_utils.common.text.converters import to_native 381 382from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( 383 OpenSSLObjectError, 384) 385 386from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( 387 get_relative_time_option, 388) 389 390from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import ( 391 select_backend, 392) 393 394 395def main(): 396 module = AnsibleModule( 397 argument_spec=dict( 398 path=dict(type='path'), 399 content=dict(type='str'), 400 valid_at=dict(type='dict'), 401 select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), 402 ), 403 required_one_of=( 404 ['path', 'content'], 405 ), 406 mutually_exclusive=( 407 ['path', 'content'], 408 ), 409 supports_check_mode=True, 410 ) 411 if module._name == 'community.crypto.openssl_certificate_info': 412 module.deprecate("The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'", 413 version='2.0.0', collection_name='community.crypto') 414 415 if module.params['content'] is not None: 416 data = module.params['content'].encode('utf-8') 417 else: 418 try: 419 with open(module.params['path'], 'rb') as f: 420 data = f.read() 421 except (IOError, OSError) as e: 422 module.fail_json(msg='Error while reading certificate file from disk: {0}'.format(e)) 423 424 backend, module_backend = select_backend(module, module.params['select_crypto_backend'], data) 425 426 valid_at = module.params['valid_at'] 427 if valid_at: 428 for k, v in valid_at.items(): 429 if not isinstance(v, string_types): 430 module.fail_json( 431 msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v)) 432 ) 433 valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k)) 434 435 try: 436 result = module_backend.get_info() 437 438 not_before = module_backend.get_not_before() 439 not_after = module_backend.get_not_after() 440 441 result['valid_at'] = dict() 442 if valid_at: 443 for k, v in valid_at.items(): 444 result['valid_at'][k] = not_before <= v <= not_after 445 446 module.exit_json(**result) 447 except OpenSSLObjectError as exc: 448 module.fail_json(msg=to_native(exc)) 449 450 451if __name__ == "__main__": 452 main() 453