Create a Certificate Authority (CA)

    ca_name
        name of the CA
    bits
        number of RSA key bits, default is 2048
    days
        number of days the CA will be valid, default is 365
    CN
        common name in the request, default is "localhost"
    C
        country, default is "US"
    ST
        state, default is "Utah"
    L
        locality, default is "Centerville", the city where SaltStack originated
    O
        organization, default is "SaltStack"
    OU
        organizational unit, default is None
    emailAddress
        email address for the CA owner, default is None
    cacert_path
        absolute path to ca certificates root directory
    ca_filename
        alternative filename for the CA

        .. 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

    Writes out a CA certificate based upon defined config values. If the file
    already exists, the function just returns assuming the CA certificate
    already exists.

    If the following values were set::

        ca.cert_base_path='/etc/pki'
        ca_name='koji'

    the resulting CA, and corresponding key, would be written in the following
    location::

        /etc/pki/koji/koji_ca_cert.crt
        /etc/pki/koji/koji_ca_cert.key

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_ca test_ca

def create_ca(ca_name,
              bits=2048,
              days=365,
              CN='localhost',
              C='US',
              ST='Utah',
              L='Salt Lake City',
              O='SaltStack',
              OU=None,
              emailAddress=None,
              fixmode=False,
              cacert_path=None,
              ca_filename=None,
              digest='sha256',
              onlyif=None,
              unless=None,
              replace=False):
    '''
    Create a Certificate Authority (CA)

    ca_name
        name of the CA
    bits
        number of RSA key bits, default is 2048
    days
        number of days the CA will be valid, default is 365
    CN
        common name in the request, default is "localhost"
    C
        country, default is "US"
    ST
        state, default is "Utah"
    L
        locality, default is "Centerville", the city where SaltStack originated
    O
        organization, default is "SaltStack"
    OU
        organizational unit, default is None
    emailAddress
        email address for the CA owner, default is None
    cacert_path
        absolute path to ca certificates root directory
    ca_filename
        alternative filename for the CA

        .. 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

    Writes out a CA certificate based upon defined config values. If the file
    already exists, the function just returns assuming the CA certificate
    already exists.

    If the following values were set::

        ca.cert_base_path='/etc/pki'
        ca_name='koji'

    the resulting CA, and corresponding key, would be written in the following
    location::

        /etc/pki/koji/koji_ca_cert.crt
        /etc/pki/koji/koji_ca_cert.key

    CLI Example:

    .. code-block:: bash

        salt '*' tls.create_ca test_ca
    '''
    status = _check_onlyif_unless(onlyif, unless)
    if status is not None:
        return None

    set_ca_path(cacert_path)

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

    certp = '{0}/{1}/{2}.crt'.format(
        cert_base_path(), ca_name, ca_filename)
    ca_keyp = '{0}/{1}/{2}.key'.format(
        cert_base_path(), ca_name, ca_filename)
    if not replace and not fixmode and ca_exists(ca_name, ca_filename=ca_filename):
        return 'Certificate for CA named "{0}" already exists'.format(ca_name)

    if fixmode and not os.path.exists(certp):
        raise ValueError('{0} does not exists, can\'t fix'.format(certp))

    if not os.path.exists('{0}/{1}'.format(
        cert_base_path(), ca_name)
    ):
        os.makedirs('{0}/{1}'.format(cert_base_path(),
                                     ca_name))

    # try to reuse existing ssl key
    key = None
    if os.path.exists(ca_keyp):
        with salt.utils.files.fopen(ca_keyp) as fic2:
            # try to determine the key bits
            try:
                key = OpenSSL.crypto.load_privatekey(
                    OpenSSL.crypto.FILETYPE_PEM, fic2.read())
            except OpenSSL.crypto.Error as err:
                log.warning('Error loading existing private key'
                            ' %s, generating a new key: %s',
                            ca_keyp, err)
                bck = "{0}.unloadable.{1}".format(ca_keyp,
                    datetime.utcnow().strftime("%Y%m%d%H%M%S"))
                log.info('Saving unloadable CA ssl key in %s', bck)
                os.rename(ca_keyp, bck)

    if not key:
        key = OpenSSL.crypto.PKey()
        key.generate_key(OpenSSL.crypto.TYPE_RSA, bits)

    ca = OpenSSL.crypto.X509()
    ca.set_version(2)
    ca.set_serial_number(_new_serial(ca_name))
    ca.get_subject().C = C
    ca.get_subject().ST = ST
    ca.get_subject().L = L
    ca.get_subject().O = O
    if OU:
        ca.get_subject().OU = OU
    ca.get_subject().CN = CN
    if emailAddress:
        ca.get_subject().emailAddress = emailAddress

    ca.gmtime_adj_notBefore(0)
    ca.gmtime_adj_notAfter(int(days) * 24 * 60 * 60)
    ca.set_issuer(ca.get_subject())
    ca.set_pubkey(key)

    if X509_EXT_ENABLED:
        ca.add_extensions([
            OpenSSL.crypto.X509Extension(
                b'basicConstraints', True, b'CA:TRUE, pathlen:0'),
            OpenSSL.crypto.X509Extension(
                b'keyUsage', True, b'keyCertSign, cRLSign'),
            OpenSSL.crypto.X509Extension(
                b'subjectKeyIdentifier', False, b'hash', subject=ca)])

        ca.add_extensions([
            OpenSSL.crypto.X509Extension(
                b'authorityKeyIdentifier',
                False,
                b'issuer:always,keyid:always',
                issuer=ca)])
    ca.sign(key, salt.utils.stringutils.to_str(digest))

    # always backup existing keys in case
    keycontent = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
                                                key)
    write_key = True
    if os.path.exists(ca_keyp):
        bck = "{0}.{1}".format(ca_keyp, datetime.utcnow().strftime(
            "%Y%m%d%H%M%S"))
        with salt.utils.files.fopen(ca_keyp) as fic:
            old_key = salt.utils.stringutils.to_unicode(fic.read()).strip()
            if old_key.strip() == keycontent.strip():
                write_key = False
            else:
                log.info('Saving old CA ssl key in %s', bck)
                with salt.utils.files.fopen(bck, 'w') as bckf:
                    bckf.write(old_key)
                    os.chmod(bck, 0o600)
    if write_key:
        with salt.utils.files.fopen(ca_keyp, 'wb') as ca_key:
            ca_key.write(salt.utils.stringutils.to_bytes(keycontent))

    with salt.utils.files.fopen(certp, 'wb') as ca_crt:
        ca_crt.write(
            salt.utils.stringutils.to_bytes(
                OpenSSL.crypto.dump_certificate(
                    OpenSSL.crypto.FILETYPE_PEM,
                    ca
                )
            )
        )

    _write_cert_to_database(ca_name, ca)

    ret = ('Created Private Key: "{0}/{1}/{2}.key." ').format(
        cert_base_path(), ca_name, ca_filename)
    ret += ('Created CA "{0}": "{1}/{0}/{2}.crt."').format(
        ca_name, cert_base_path(), ca_filename)

    return ret