#!/usr/bin/python
# coding: utf-8 -*-

# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: get_certificate
author: "John Westcott IV (@john-westcott-iv)"
version_added: "2.8"
short_description: Get a certificate from a host:port
description:
    - Makes a secure connection and returns information about the presented certificate
options:
    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
    timeout:
      description:
        - The timeout in seconds
      type: int
      default: 10

notes:
  - When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.

requirements:
  - "pyOpenSSL >= 0.15"
'''

RETURN = '''
cert:
    description: The certificate retrieved from the port
    returned: success
    type: str
expired:
    description: Boolean indicating if the cert is expired
    returned: success
    type: bool
extensions:
    description: Extensions applied to the cert
    returned: success
    type: list
issuer:
    description: Information about the issuer of the cert
    returned: success
    type: dict
not_after:
    description: Expiration date of the cert
    returned: success
    type: str
not_before:
    description: Issue date of the cert
    returned: success
    type: str
serial_number:
    description: The serial number of the cert
    returned: success
    type: str
signature_algorithm:
    description: The algorithm used to sign the cert
    returned: success
    type: str
subject:
    description: Information about the subject of the cert (OU, CN, etc)
    returned: success
    type: dict
version:
    description: The version number of the certificate
    returned: success
    type: str
'''

EXAMPLES = '''
- name: Get the cert from an RDP port
  get_certificate:
    host: "1.2.3.4"
    port: 3389
  delegate_to: localhost
  run_once: true
  register: cert

- name: Get a cert from an https port
  get_certificate:
    host: "www.google.com"
    port: 443
  delegate_to: localhost
  run_once: true
  register: cert

- name: How many days until cert expires
  debug:
    msg: "cert expires in: {{ expire_days }} days."
  vars:
    expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}"
'''

import traceback

from ansible.module_utils.basic import AnsibleModule, missing_required_lib

from os.path import isfile
from ssl import get_server_certificate
from socket import setdefaulttimeout

PYOPENSSL_IMP_ERR = None
try:
    from OpenSSL import crypto
except ImportError:
    PYOPENSSL_IMP_ERR = traceback.format_exc()
    pyopenssl_found = False
else:
    pyopenssl_found = True


def main():
    module = AnsibleModule(
        argument_spec=dict(
            ca_cert=dict(type='path'),
            host=dict(type='str', required=True),
            port=dict(type='int', required=True),
            timeout=dict(type='int', default=10),
        ),
    )

    ca_cert = module.params.get('ca_cert')
    host = module.params.get('host')
    port = module.params.get('port')
    timeout = module.params.get('timeout')

    result = dict(
        changed=False,
    )

    if not pyopenssl_found:
        module.fail_json(msg=missing_required_lib('pyOpenSSL >= 0.15'), exception=PYOPENSSL_IMP_ERR)

    if timeout:
        setdefaulttimeout(timeout)

    if ca_cert:
        if not isfile(ca_cert):
            module.fail_json(msg="ca_cert file does not exist")

    try:
        cert = get_server_certificate((host, port), ca_certs=ca_cert)
        x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
    except Exception as e:
        module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))

    result['cert'] = cert
    result['subject'] = {}
    for component in x509.get_subject().get_components():
        result['subject'][component[0]] = component[1]

    result['expired'] = x509.has_expired()

    result['extensions'] = []
    extension_count = x509.get_extension_count()
    for index in range(0, extension_count):
        extension = x509.get_extension(index)
        result['extensions'].append({
            'critical': extension.get_critical(),
            'asn1_data': extension.get_data(),
            'name': extension.get_short_name(),
        })

    result['issuer'] = {}
    for component in x509.get_issuer().get_components():
        result['issuer'][component[0]] = component[1]

    result['not_after'] = x509.get_notAfter()
    result['not_before'] = x509.get_notBefore()

    result['serial_number'] = x509.get_serial_number()
    result['signature_algorithm'] = x509.get_signature_algorithm()

    result['version'] = x509.get_version()

    module.exit_json(**result)


if __name__ == '__main__':
    main()
