Create a Certificate (CERT) signed by a named Certificate Authority (CA)

    If the certificate file already exists, the function just returns assuming
    the CERT already exists.

    The CN *must* match an existing CSR generated by create_csr. If it
    does not, this method does nothing.

    ca_name
        name of the CA
    CN
        common name matching the certificate signing request
    days
        number of days certificate is valid, default is 365 (1 year)

    cacert_path
        absolute path to ca certificates root directory

    ca_filename
        alternative filename for the CA

        .. versionadded:: 2015.5.3


    cert_path
        full path to the certificates directory

    cert_filename
        alternative filename for the certificate, useful when using special
        characters in the CN. If this option is set it will override
        the certificate filename output effects of ``cert_type``.
        ``type_ext`` will be completely overridden.

        .. versionadded:: 2015.5.3


    digest
        The message digest algorithm. Must be a string describing a digest
        algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically).
        For example, "md5" or "sha1". Default: 'sha256'
    replace
        Replace this certificate even if it exists

        .. versionadded:: 2015.5.1

    cert_type
        string. Either 'server' or 'client' (see create_csr() for details).

        If create_csr(type_ext=True) this function **must** be called with the
        same cert_type so it can find the CSR file.

    .. note::
        create_csr() defaults to cert_type='server'; therefore, if it was also
        called with type_ext, cert_type becomes a required argument for
        create_ca_signed_cert()

    type_ext
        bool. If set True, use ``cert_type`` as an extension to the CN when
        formatting the filename.

        e.g.: some_subject_CN_server.crt or some_subject_CN_client.crt

        This facilitates the context where both types are required for the same
        subject

        If ``cert_filename`` is `not None`, setting ``type_ext`` has no
        effect

    If the following values were set:

    .. code-block:: text

        ca.cert_base_path='/etc/pki'
        ca_name='koji'
        CN='test.egavas.org'

    the resulting signed certificate would be written in the following
    location:

    .. code-block:: text

        /etc/pki/koji/certs/test.egavas.org.crt

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_ca_signed_cert test localhost

def create_ca_signed_cert(ca_name,
                          CN,
                          days=365,
                          cacert_path=None,
                          ca_filename=None,
                          cert_path=None,
                          cert_filename=None,
                          digest='sha256',
                          cert_type=None,
                          type_ext=False,
                          replace=False):
    '''
    Create a Certificate (CERT) signed by a named Certificate Authority (CA)

    If the certificate file already exists, the function just returns assuming
    the CERT already exists.

    The CN *must* match an existing CSR generated by create_csr. If it
    does not, this method does nothing.

    ca_name
        name of the CA
    CN
        common name matching the certificate signing request
    days
        number of days certificate is valid, default is 365 (1 year)

    cacert_path
        absolute path to ca certificates root directory

    ca_filename
        alternative filename for the CA

        .. versionadded:: 2015.5.3


    cert_path
        full path to the certificates directory

    cert_filename
        alternative filename for the certificate, useful when using special
        characters in the CN. If this option is set it will override
        the certificate filename output effects of ``cert_type``.
        ``type_ext`` will be completely overridden.

        .. versionadded:: 2015.5.3


    digest
        The message digest algorithm. Must be a string describing a digest
        algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically).
        For example, "md5" or "sha1". Default: 'sha256'
    replace
        Replace this certificate even if it exists

        .. versionadded:: 2015.5.1

    cert_type
        string. Either 'server' or 'client' (see create_csr() for details).

        If create_csr(type_ext=True) this function **must** be called with the
        same cert_type so it can find the CSR file.

    .. note::
        create_csr() defaults to cert_type='server'; therefore, if it was also
        called with type_ext, cert_type becomes a required argument for
        create_ca_signed_cert()

    type_ext
        bool. If set True, use ``cert_type`` as an extension to the CN when
        formatting the filename.

        e.g.: some_subject_CN_server.crt or some_subject_CN_client.crt

        This facilitates the context where both types are required for the same
        subject

        If ``cert_filename`` is `not None`, setting ``type_ext`` has no
        effect

    If the following values were set:

    .. code-block:: text

        ca.cert_base_path='/etc/pki'
        ca_name='koji'
        CN='test.egavas.org'

    the resulting signed certificate would be written in the following
    location:

    .. code-block:: text

        /etc/pki/koji/certs/test.egavas.org.crt

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_ca_signed_cert test localhost
    '''
    ret = {}

    set_ca_path(cacert_path)

    if not ca_filename:
        ca_filename = '{0}_ca_cert'.format(ca_name)

    if not cert_path:
        cert_path = '{0}/{1}/certs'.format(cert_base_path(), ca_name)

    if type_ext:
        if not cert_type:
            log.error('type_ext = True but cert_type is unset. '
                      'Certificate not written.')
            return ret
        elif cert_type:
            CN_ext = '_{0}'.format(cert_type)
    else:
        CN_ext = ''

    csr_filename = '{0}{1}'.format(CN, CN_ext)

    if not cert_filename:
        cert_filename = '{0}{1}'.format(CN, CN_ext)

    if not replace and os.path.exists(
            os.path.join(
                os.path.sep.join('{0}/{1}/certs/{2}.crt'.format(
                    cert_base_path(),
                    ca_name,
                    cert_filename).split('/')
                )
            )
    ):
        return 'Certificate "{0}" already exists'.format(cert_filename)

    try:
        maybe_fix_ssl_version(ca_name,
                              cacert_path=cacert_path,
                              ca_filename=ca_filename)
        with salt.utils.files.fopen('{0}/{1}/{2}.crt'.format(cert_base_path(),
                                                       ca_name,
                                                       ca_filename)) as fhr:
            ca_cert = OpenSSL.crypto.load_certificate(
                OpenSSL.crypto.FILETYPE_PEM, fhr.read()
            )
        with salt.utils.files.fopen('{0}/{1}/{2}.key'.format(cert_base_path(),
                                                       ca_name,
                                                       ca_filename)) as fhr:
            ca_key = OpenSSL.crypto.load_privatekey(
                OpenSSL.crypto.FILETYPE_PEM,
                fhr.read()
            )
    except IOError:
        ret['retcode'] = 1
        ret['comment'] = 'There is no CA named "{0}"'.format(ca_name)
        return ret

    try:
        csr_path = '{0}/{1}.csr'.format(cert_path, csr_filename)
        with salt.utils.files.fopen(csr_path) as fhr:
            req = OpenSSL.crypto.load_certificate_request(
                OpenSSL.crypto.FILETYPE_PEM,
                fhr.read())
    except IOError:
        ret['retcode'] = 1
        ret['comment'] = 'There is no CSR that matches the CN "{0}"'.format(
            cert_filename)
        return ret

    exts = []
    try:
        exts.extend(req.get_extensions())
    except AttributeError:
        try:
            # see: http://bazaar.launchpad.net/~exarkun/pyopenssl/master/revision/189
            # support is there from quite a long time, but without API
            # so we mimic the newly get_extensions method present in ultra
            # recent pyopenssl distros
            log.info('req.get_extensions() not supported in pyOpenSSL versions '
                     'prior to 0.15. Processing extensions internally. '
                     'Your version: %s', OpenSSL_version)

            native_exts_obj = OpenSSL._util.lib.X509_REQ_get_extensions(
                req._req)
            for i in _range(OpenSSL._util.lib.sk_X509_EXTENSION_num(
                    native_exts_obj)):
                ext = OpenSSL.crypto.X509Extension.__new__(
                    OpenSSL.crypto.X509Extension)
                ext._extension = OpenSSL._util.lib.sk_X509_EXTENSION_value(
                    native_exts_obj,
                    i)
                exts.append(ext)
        except Exception:
            log.error('X509 extensions are unsupported in pyOpenSSL '
                      'versions prior to 0.14. Upgrade required to '
                      'use extensions. Current version: %s', OpenSSL_version)

    cert = OpenSSL.crypto.X509()
    cert.set_version(2)
    cert.set_subject(req.get_subject())
    cert.gmtime_adj_notBefore(0)
    cert.gmtime_adj_notAfter(int(days) * 24 * 60 * 60)
    cert.set_serial_number(_new_serial(ca_name))
    cert.set_issuer(ca_cert.get_subject())
    cert.set_pubkey(req.get_pubkey())

    cert.add_extensions(exts)

    cert.sign(ca_key, salt.utils.stringutils.to_str(digest))

    cert_full_path = '{0}/{1}.crt'.format(cert_path, cert_filename)

    with salt.utils.files.fopen(cert_full_path, 'wb+') as crt:
        crt.write(
            salt.utils.stringutils.to_bytes(
                OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                cert)
            )
        )

    _write_cert_to_database(ca_name, cert)

    return 'Created Certificate for "{0}": "{1}/{2}.crt"'.format(
        CN,
        cert_path,
        cert_filename
    )