2020-03-09 13:11:34 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# coding: utf-8 -*-
|
|
|
|
|
2022-07-21 05:27:26 +00:00
|
|
|
# Copyright (c) Ansible Project
|
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: get_certificate
|
|
|
|
author: "John Westcott IV (@john-westcott-iv)"
|
|
|
|
short_description: Get a certificate from a host:port
|
|
|
|
description:
|
2024-07-09 22:20:24 +00:00
|
|
|
- Makes a secure connection and returns information about the presented certificate.
|
|
|
|
- The module uses the cryptography Python library.
|
|
|
|
- Support SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) only with Python 2.7 and newer.
|
2022-11-06 20:10:56 +00:00
|
|
|
extends_documentation_fragment:
|
2024-07-09 22:20:24 +00:00
|
|
|
- community.crypto.attributes
|
2022-11-06 20:10:56 +00:00
|
|
|
attributes:
|
2024-07-09 22:20:24 +00:00
|
|
|
check_mode:
|
|
|
|
support: none
|
|
|
|
details:
|
|
|
|
- This action does not modify state.
|
|
|
|
diff_mode:
|
|
|
|
support: N/A
|
|
|
|
details:
|
|
|
|
- This action does not modify state.
|
2020-03-09 13:11:34 +00:00
|
|
|
options:
|
2024-07-09 22:20:24 +00:00
|
|
|
host:
|
|
|
|
description:
|
|
|
|
- The host to get the cert for (IP is fine).
|
|
|
|
type: str
|
|
|
|
required: true
|
|
|
|
ca_cert:
|
|
|
|
description:
|
|
|
|
- A PEM file containing one or more root certificates; if present, the cert will be validated against these root certs.
|
|
|
|
- Note that this only validates the certificate is signed by the chain; not that the cert is valid for the host presenting it.
|
|
|
|
type: path
|
|
|
|
port:
|
|
|
|
description:
|
|
|
|
- The port to connect to.
|
|
|
|
type: int
|
|
|
|
required: true
|
|
|
|
server_name:
|
|
|
|
description:
|
|
|
|
- Server name used for SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) when hostname
|
|
|
|
is an IP or is different from server name.
|
|
|
|
type: str
|
|
|
|
version_added: 1.4.0
|
|
|
|
proxy_host:
|
|
|
|
description:
|
|
|
|
- Proxy host used when get a certificate.
|
|
|
|
type: str
|
|
|
|
proxy_port:
|
|
|
|
description:
|
|
|
|
- Proxy port used when get a certificate.
|
|
|
|
type: int
|
|
|
|
default: 8080
|
|
|
|
starttls:
|
|
|
|
description:
|
|
|
|
- Requests a secure connection for protocols which require clients to initiate encryption.
|
|
|
|
- Only available for V(mysql) currently.
|
|
|
|
type: str
|
|
|
|
choices:
|
|
|
|
- mysql
|
|
|
|
version_added: 1.9.0
|
|
|
|
timeout:
|
|
|
|
description:
|
|
|
|
- The timeout in seconds.
|
|
|
|
type: int
|
|
|
|
default: 10
|
|
|
|
select_crypto_backend:
|
|
|
|
description:
|
|
|
|
- Determines which crypto backend to use.
|
|
|
|
- The default choice is V(auto), which tries to use C(cryptography) if available.
|
|
|
|
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
|
|
|
type: str
|
|
|
|
default: auto
|
|
|
|
choices: [ auto, cryptography ]
|
|
|
|
ciphers:
|
|
|
|
description:
|
|
|
|
- SSL/TLS Ciphers to use for the request.
|
|
|
|
- 'When a list is provided, all ciphers are joined in order with V(:).'
|
|
|
|
- See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT)
|
|
|
|
for more details.
|
|
|
|
- The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions.
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
version_added: 2.11.0
|
|
|
|
asn1_base64:
|
|
|
|
description:
|
|
|
|
- Whether to encode the ASN.1 values in the RV(extensions) return value with Base64 or not.
|
|
|
|
- The documentation claimed for a long time that the values are Base64 encoded, but they
|
|
|
|
never were. For compatibility this option is set to V(false).
|
|
|
|
- The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0.
|
|
|
|
type: bool
|
|
|
|
version_added: 2.12.0
|
|
|
|
tls_ctx_options:
|
|
|
|
description:
|
|
|
|
- TLS context options (TLS/SSL OP flags) to use for the request.
|
|
|
|
- See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details.
|
|
|
|
- The available TLS context options is dependent on the Python and OpenSSL/LibreSSL versions.
|
|
|
|
type: list
|
|
|
|
elements: raw
|
|
|
|
version_added: 2.21.0
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
notes:
|
2024-07-09 22:20:24 +00:00
|
|
|
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
requirements:
|
2024-07-09 22:20:24 +00:00
|
|
|
- "Python >= 2.7 when using O(proxy_host)"
|
|
|
|
- "cryptography >= 1.6"
|
2024-02-18 20:27:48 +00:00
|
|
|
|
|
|
|
seealso:
|
2024-07-09 22:20:24 +00:00
|
|
|
- plugin: community.crypto.to_serial
|
|
|
|
plugin_type: filter
|
2020-03-09 13:11:34 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
cert:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: The certificate retrieved from the port.
|
|
|
|
returned: success
|
|
|
|
type: str
|
2020-03-09 13:11:34 +00:00
|
|
|
expired:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Boolean indicating if the cert is expired.
|
|
|
|
returned: success
|
|
|
|
type: bool
|
2020-03-09 13:11:34 +00:00
|
|
|
extensions:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Extensions applied to the cert.
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
elements: dict
|
|
|
|
contains:
|
|
|
|
critical:
|
|
|
|
returned: success
|
|
|
|
type: bool
|
|
|
|
description: Whether the extension is critical.
|
|
|
|
asn1_data:
|
|
|
|
returned: success
|
|
|
|
type: str
|
|
|
|
description:
|
|
|
|
- The ASN.1 content of the extension.
|
|
|
|
- If O(asn1_base64=true) this will be Base64 encoded, otherwise the raw
|
|
|
|
binary value will be returned.
|
|
|
|
- Please note that the raw binary value might not survive JSON serialization
|
|
|
|
to the Ansible controller, and also might cause failures when displaying it.
|
|
|
|
See U(https://github.com/ansible/ansible/issues/80258) for more information.
|
|
|
|
- B(Note) that depending on the C(cryptography) version used, it is
|
|
|
|
not possible to extract the ASN.1 content of the extension, but only
|
|
|
|
to provide the re-encoded content of the extension in case it was
|
|
|
|
parsed by C(cryptography). This should usually result in exactly the
|
|
|
|
same value, except if the original extension value was malformed.
|
|
|
|
name:
|
|
|
|
returned: success
|
|
|
|
type: str
|
|
|
|
description: The extension's name.
|
2020-03-09 13:11:34 +00:00
|
|
|
issuer:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Information about the issuer of the cert.
|
|
|
|
returned: success
|
|
|
|
type: dict
|
2020-03-09 13:11:34 +00:00
|
|
|
not_after:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Expiration date of the cert.
|
|
|
|
returned: success
|
|
|
|
type: str
|
2020-03-09 13:11:34 +00:00
|
|
|
not_before:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Issue date of the cert.
|
|
|
|
returned: success
|
|
|
|
type: str
|
2020-03-09 13:11:34 +00:00
|
|
|
serial_number:
|
2024-07-09 22:20:24 +00:00
|
|
|
description:
|
|
|
|
- The serial number of the cert.
|
|
|
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
|
|
|
|
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
|
|
|
|
returned: success
|
|
|
|
type: int
|
2020-03-09 13:11:34 +00:00
|
|
|
signature_algorithm:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: The algorithm used to sign the cert.
|
|
|
|
returned: success
|
|
|
|
type: str
|
2020-03-09 13:11:34 +00:00
|
|
|
subject:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: Information about the subject of the cert (C(OU), C(CN), and so on).
|
|
|
|
returned: success
|
|
|
|
type: dict
|
2020-03-09 13:11:34 +00:00
|
|
|
version:
|
2024-07-09 22:20:24 +00:00
|
|
|
description: The version number of the certificate.
|
|
|
|
returned: success
|
|
|
|
type: str
|
2020-03-09 13:11:34 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
- name: Get the cert from an RDP port
|
2020-03-31 14:23:45 +00:00
|
|
|
community.crypto.get_certificate:
|
2020-03-09 13:11:34 +00:00
|
|
|
host: "1.2.3.4"
|
|
|
|
port: 3389
|
|
|
|
delegate_to: localhost
|
|
|
|
run_once: true
|
|
|
|
register: cert
|
|
|
|
|
|
|
|
- name: Get a cert from an https port
|
2020-03-31 14:23:45 +00:00
|
|
|
community.crypto.get_certificate:
|
2020-03-09 13:11:34 +00:00
|
|
|
host: "www.google.com"
|
|
|
|
port: 443
|
|
|
|
delegate_to: localhost
|
|
|
|
run_once: true
|
|
|
|
register: cert
|
|
|
|
|
|
|
|
- name: How many days until cert expires
|
2023-05-15 19:41:18 +00:00
|
|
|
ansible.builtin.debug:
|
2020-03-09 13:11:34 +00:00
|
|
|
msg: "cert expires in: {{ expire_days }} days."
|
|
|
|
vars:
|
2024-07-09 22:20:24 +00:00
|
|
|
expire_days: >-
|
|
|
|
{{ (
|
|
|
|
(cert.not_after | ansible.builtin.to_datetime('%Y%m%d%H%M%SZ')) -
|
|
|
|
(ansible_date_time.iso8601 | ansible.builtin.to_datetime('%Y-%m-%dT%H:%M:%SZ'))
|
|
|
|
).days }}
|
2024-07-07 19:48:48 +00:00
|
|
|
|
|
|
|
- name: Allow legacy insecure renegotiation to get a cert from a legacy device
|
|
|
|
community.crypto.get_certificate:
|
|
|
|
host: "legacy-device.domain.com"
|
|
|
|
port: 443
|
|
|
|
ciphers:
|
|
|
|
- HIGH
|
|
|
|
tls_ctx_options:
|
|
|
|
- OP_ALL
|
|
|
|
- OP_NO_SSLv3
|
|
|
|
- OP_CIPHER_SERVER_PREFERENCE
|
|
|
|
- OP_ENABLE_MIDDLEBOX_COMPAT
|
|
|
|
- OP_NO_COMPRESSION
|
|
|
|
- 4 # OP_LEGACY_SERVER_CONNECT
|
|
|
|
delegate_to: localhost
|
|
|
|
run_once: true
|
|
|
|
register: legacy_cert
|
2020-03-09 13:11:34 +00:00
|
|
|
'''
|
|
|
|
|
2020-03-24 19:20:19 +00:00
|
|
|
import atexit
|
|
|
|
import base64
|
|
|
|
import traceback
|
2024-07-07 19:48:48 +00:00
|
|
|
import ssl
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
from os.path import isfile
|
2020-07-13 16:05:58 +00:00
|
|
|
from socket import create_connection, setdefaulttimeout, socket
|
|
|
|
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
2020-03-09 13:11:34 +00:00
|
|
|
|
2020-03-24 19:20:19 +00:00
|
|
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
2024-07-07 19:48:48 +00:00
|
|
|
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
|
|
|
from ansible.module_utils.six import string_types
|
2020-03-24 19:20:19 +00:00
|
|
|
|
2021-12-24 10:28:14 +00:00
|
|
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
|
|
|
|
2020-05-12 09:19:42 +00:00
|
|
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
2024-04-18 05:49:53 +00:00
|
|
|
CRYPTOGRAPHY_TIMEZONE,
|
2020-05-12 09:19:42 +00:00
|
|
|
cryptography_oid_to_name,
|
|
|
|
cryptography_get_extensions_from_cert,
|
2024-04-18 05:49:53 +00:00
|
|
|
get_not_valid_after,
|
|
|
|
get_not_valid_before,
|
2020-05-12 09:19:42 +00:00
|
|
|
)
|
2020-03-09 13:11:34 +00:00
|
|
|
|
2024-05-11 15:05:03 +00:00
|
|
|
from ansible_collections.community.crypto.plugins.module_utils.time import (
|
|
|
|
get_now_datetime,
|
|
|
|
)
|
|
|
|
|
2020-03-09 13:11:34 +00:00
|
|
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
|
|
|
|
|
|
|
CREATE_DEFAULT_CONTEXT_IMP_ERR = None
|
|
|
|
try:
|
|
|
|
from ssl import create_default_context
|
|
|
|
except ImportError:
|
|
|
|
CREATE_DEFAULT_CONTEXT_IMP_ERR = traceback.format_exc()
|
|
|
|
HAS_CREATE_DEFAULT_CONTEXT = False
|
|
|
|
else:
|
|
|
|
HAS_CREATE_DEFAULT_CONTEXT = True
|
|
|
|
|
|
|
|
CRYPTOGRAPHY_IMP_ERR = None
|
|
|
|
try:
|
|
|
|
import cryptography
|
|
|
|
import cryptography.exceptions
|
|
|
|
import cryptography.x509
|
|
|
|
from cryptography.hazmat.backends import default_backend as cryptography_backend
|
|
|
|
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
|
|
|
except ImportError:
|
|
|
|
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
|
|
|
CRYPTOGRAPHY_FOUND = False
|
|
|
|
else:
|
|
|
|
CRYPTOGRAPHY_FOUND = True
|
|
|
|
|
|
|
|
|
2021-08-15 13:40:54 +00:00
|
|
|
def send_starttls_packet(sock, server_type):
|
|
|
|
if server_type == 'mysql':
|
|
|
|
ssl_request_packet = (
|
|
|
|
b'\x20\x00\x00\x01\x85\xae\x7f\x00' +
|
|
|
|
b'\x00\x00\x00\x01\x21\x00\x00\x00' +
|
|
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
|
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
|
|
|
b'\x00\x00\x00\x00'
|
|
|
|
)
|
|
|
|
|
|
|
|
sock.recv(8192) # discard initial handshake from server for this naive implementation
|
|
|
|
sock.send(ssl_request_packet)
|
|
|
|
|
|
|
|
|
2020-03-09 13:11:34 +00:00
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
|
|
|
ca_cert=dict(type='path'),
|
|
|
|
host=dict(type='str', required=True),
|
|
|
|
port=dict(type='int', required=True),
|
|
|
|
proxy_host=dict(type='str'),
|
|
|
|
proxy_port=dict(type='int', default=8080),
|
2021-01-17 11:21:12 +00:00
|
|
|
server_name=dict(type='str'),
|
2020-03-09 13:11:34 +00:00
|
|
|
timeout=dict(type='int', default=10),
|
2021-09-28 15:46:35 +00:00
|
|
|
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
2021-08-15 13:40:54 +00:00
|
|
|
starttls=dict(type='str', choices=['mysql']),
|
2023-02-10 20:01:13 +00:00
|
|
|
ciphers=dict(type='list', elements='str'),
|
2023-08-12 10:23:37 +00:00
|
|
|
asn1_base64=dict(type='bool'),
|
2024-07-07 19:48:48 +00:00
|
|
|
tls_ctx_options=dict(type='list', elements='raw'),
|
2020-03-09 13:11:34 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
ca_cert = module.params.get('ca_cert')
|
|
|
|
host = module.params.get('host')
|
|
|
|
port = module.params.get('port')
|
|
|
|
proxy_host = module.params.get('proxy_host')
|
|
|
|
proxy_port = module.params.get('proxy_port')
|
|
|
|
timeout = module.params.get('timeout')
|
2021-01-17 11:21:12 +00:00
|
|
|
server_name = module.params.get('server_name')
|
2021-08-15 13:40:54 +00:00
|
|
|
start_tls_server_type = module.params.get('starttls')
|
2023-02-10 20:01:13 +00:00
|
|
|
ciphers = module.params.get('ciphers')
|
2023-04-16 11:34:45 +00:00
|
|
|
asn1_base64 = module.params['asn1_base64']
|
2024-07-07 19:48:48 +00:00
|
|
|
tls_ctx_options = module.params.get('tls_ctx_options')
|
2023-08-12 10:23:37 +00:00
|
|
|
if asn1_base64 is None:
|
|
|
|
module.deprecate(
|
|
|
|
'The default value `false` for asn1_base64 is deprecated and will change to `true` in '
|
|
|
|
'community.crypto 3.0.0. If you need this value, it is best to set the value explicitly '
|
|
|
|
'and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible',
|
|
|
|
version='3.0.0',
|
|
|
|
collection_name='community.crypto',
|
|
|
|
)
|
|
|
|
asn1_base64 = False
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
backend = module.params.get('select_crypto_backend')
|
|
|
|
if backend == 'auto':
|
|
|
|
# Detection what is possible
|
|
|
|
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
|
|
|
|
2021-09-28 15:46:35 +00:00
|
|
|
# Try cryptography
|
2020-03-09 13:11:34 +00:00
|
|
|
if can_use_cryptography:
|
|
|
|
backend = 'cryptography'
|
|
|
|
|
|
|
|
# Success?
|
|
|
|
if backend == 'auto':
|
2022-01-10 12:05:09 +00:00
|
|
|
module.fail_json(msg=("Cannot detect the required Python library "
|
2021-09-28 15:46:35 +00:00
|
|
|
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
|
|
|
|
|
|
|
if backend == 'cryptography':
|
2020-03-09 13:11:34 +00:00
|
|
|
if not CRYPTOGRAPHY_FOUND:
|
|
|
|
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
|
|
|
exception=CRYPTOGRAPHY_IMP_ERR)
|
|
|
|
|
|
|
|
result = dict(
|
|
|
|
changed=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
if timeout:
|
|
|
|
setdefaulttimeout(timeout)
|
|
|
|
|
|
|
|
if ca_cert:
|
|
|
|
if not isfile(ca_cert):
|
|
|
|
module.fail_json(msg="ca_cert file does not exist")
|
|
|
|
|
2020-07-13 16:05:58 +00:00
|
|
|
if not HAS_CREATE_DEFAULT_CONTEXT:
|
|
|
|
# Python < 2.7.9
|
|
|
|
if proxy_host:
|
2020-03-09 13:11:34 +00:00
|
|
|
module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.',
|
|
|
|
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
2023-02-10 20:01:13 +00:00
|
|
|
if ciphers is not None:
|
|
|
|
module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.',
|
|
|
|
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
2024-07-07 19:48:48 +00:00
|
|
|
if tls_ctx_options is not None:
|
|
|
|
module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.',
|
|
|
|
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
2020-03-09 13:11:34 +00:00
|
|
|
try:
|
2020-07-13 16:05:58 +00:00
|
|
|
# Note: get_server_certificate does not support SNI!
|
|
|
|
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
|
|
|
except Exception as e:
|
|
|
|
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
|
|
|
else:
|
|
|
|
# Python >= 2.7.9
|
|
|
|
try:
|
|
|
|
if proxy_host:
|
|
|
|
connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port)
|
|
|
|
sock = socket()
|
|
|
|
atexit.register(sock.close)
|
|
|
|
sock.connect((proxy_host, proxy_port))
|
|
|
|
sock.send(connect.encode())
|
|
|
|
sock.recv(8192)
|
|
|
|
else:
|
|
|
|
sock = create_connection((host, port))
|
|
|
|
atexit.register(sock.close)
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
if ca_cert:
|
2020-07-13 16:05:58 +00:00
|
|
|
ctx = create_default_context(cafile=ca_cert)
|
|
|
|
ctx.check_hostname = False
|
|
|
|
ctx.verify_mode = CERT_REQUIRED
|
|
|
|
else:
|
|
|
|
ctx = create_default_context()
|
|
|
|
ctx.check_hostname = False
|
|
|
|
ctx.verify_mode = CERT_NONE
|
2020-03-09 13:11:34 +00:00
|
|
|
|
2021-08-15 13:40:54 +00:00
|
|
|
if start_tls_server_type is not None:
|
|
|
|
send_starttls_packet(sock, start_tls_server_type)
|
|
|
|
|
2023-02-10 20:01:13 +00:00
|
|
|
if ciphers is not None:
|
|
|
|
ciphers_joined = ":".join(ciphers)
|
|
|
|
ctx.set_ciphers(ciphers_joined)
|
|
|
|
|
2024-07-07 19:48:48 +00:00
|
|
|
if tls_ctx_options is not None:
|
|
|
|
# Clear default ctx options
|
|
|
|
ctx.options = 0
|
|
|
|
|
|
|
|
# For each item in the tls_ctx_options list
|
|
|
|
for tls_ctx_option in tls_ctx_options:
|
|
|
|
# If the item is a string_type
|
|
|
|
if isinstance(tls_ctx_option, string_types):
|
|
|
|
# Convert tls_ctx_option to a native string
|
|
|
|
tls_ctx_option_str = to_native(tls_ctx_option)
|
|
|
|
# Get the tls_ctx_option_str attribute from ssl
|
|
|
|
tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str, None)
|
|
|
|
# If tls_ctx_option_attr is an integer
|
|
|
|
if isinstance(tls_ctx_option_attr, int):
|
|
|
|
# Set tls_ctx_option_int to the attribute value
|
|
|
|
tls_ctx_option_int = tls_ctx_option_attr
|
|
|
|
# If tls_ctx_option_attr is not an integer
|
|
|
|
else:
|
|
|
|
module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str))
|
|
|
|
# If the item is an integer
|
|
|
|
elif isinstance(tls_ctx_option, int):
|
|
|
|
# Set tls_ctx_option_int to the item value
|
|
|
|
tls_ctx_option_int = tls_ctx_option
|
|
|
|
# If the item is not a string nor integer
|
|
|
|
else:
|
|
|
|
module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option))
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Add the int value of the item to ctx options
|
|
|
|
ctx.options |= tls_ctx_option_int
|
|
|
|
except Exception as e:
|
|
|
|
module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int))
|
|
|
|
|
2021-01-17 11:21:12 +00:00
|
|
|
cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True)
|
2020-03-09 13:11:34 +00:00
|
|
|
cert = DER_cert_to_PEM_cert(cert)
|
|
|
|
except Exception as e:
|
2020-07-13 16:05:58 +00:00
|
|
|
if proxy_host:
|
|
|
|
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
|
|
|
proxy_host, proxy_port, host, port, e))
|
|
|
|
else:
|
|
|
|
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
result['cert'] = cert
|
|
|
|
|
2021-09-28 15:46:35 +00:00
|
|
|
if backend == 'cryptography':
|
2020-03-09 13:11:34 +00:00
|
|
|
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend())
|
|
|
|
result['subject'] = {}
|
|
|
|
for attribute in x509.subject:
|
2020-05-12 09:19:42 +00:00
|
|
|
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
2020-03-09 13:11:34 +00:00
|
|
|
|
2024-04-18 05:49:53 +00:00
|
|
|
result['expired'] = get_not_valid_after(x509) < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
result['extensions'] = []
|
2020-05-12 09:19:42 +00:00
|
|
|
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
|
2020-03-09 13:11:34 +00:00
|
|
|
oid = cryptography.x509.oid.ObjectIdentifier(dotted_number)
|
2023-04-16 11:34:45 +00:00
|
|
|
ext = {
|
2020-03-09 13:11:34 +00:00
|
|
|
'critical': entry['critical'],
|
2023-04-16 11:34:45 +00:00
|
|
|
'asn1_data': entry['value'],
|
2020-05-12 09:19:42 +00:00
|
|
|
'name': cryptography_oid_to_name(oid, short=True),
|
2023-04-16 11:34:45 +00:00
|
|
|
}
|
|
|
|
if not asn1_base64:
|
|
|
|
ext['asn1_data'] = base64.b64decode(ext['asn1_data'])
|
|
|
|
result['extensions'].append(ext)
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
result['issuer'] = {}
|
|
|
|
for attribute in x509.issuer:
|
2020-05-12 09:19:42 +00:00
|
|
|
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
2020-03-09 13:11:34 +00:00
|
|
|
|
2024-04-18 05:49:53 +00:00
|
|
|
result['not_after'] = get_not_valid_after(x509).strftime('%Y%m%d%H%M%SZ')
|
|
|
|
result['not_before'] = get_not_valid_before(x509).strftime('%Y%m%d%H%M%SZ')
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
result['serial_number'] = x509.serial_number
|
2020-05-12 09:19:42 +00:00
|
|
|
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
|
2020-03-09 13:11:34 +00:00
|
|
|
|
|
|
|
# We need the -1 offset to get the same values as pyOpenSSL
|
|
|
|
if x509.version == cryptography.x509.Version.v1:
|
|
|
|
result['version'] = 1 - 1
|
|
|
|
elif x509.version == cryptography.x509.Version.v3:
|
|
|
|
result['version'] = 3 - 1
|
|
|
|
else:
|
|
|
|
result['version'] = "unknown"
|
|
|
|
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|