#!/usr/bin/env bash
# vim: foldmarker={{{,}}}:foldmethod=marker

# pki-authority: CA-side PKI management
# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Homepage: https://debops.org/

set -o nounset -o pipefail -o errexit

umask 022

script="$(basename "${0}")"

_openssl () {
    # OpenSSL writes to stderr/stdout even when there are no errors. So just
    # display the output if the exit code was != 0 to simplify debugging.
    set +e
    _openssl_out="$(openssl "${@}" 2>&1)"
    local rc=$?
    set -e
    if [ ${rc} -ne 0 ]; then
        echo "${script}: Error: failed to run $* (Exitcode: ${rc})" >&2
        echo >&2
        echo "Details:" >&2
        echo "${_openssl_out}" >&2
        exit ${rc}
    fi
    unset _openssl_out
    return ${rc}
}

array_exists () {
    # Check if an element is in an array
    local array="$1[@]"
    local seeking=${2}
    local in=1
    for element in "${!array}"; do
        if [[ $element == "$seeking" ]]; then
            in=0
            break
        fi
    done
    return $in
}

# Inlined in the following scripts: pki-authority, pki-realm {{{
version () {
    # Normalize version numbers for comparison.
    echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'
}

key_exists () {
    # Check if an associative array has a specified key present
    # shellcheck disable=SC2086
    eval '[ ${'$1'[$2]+test_of_existence} ]'
}

get_absolute_path () {
    # Get an absolute path to a file
    if type python3 > /dev/null ; then
        python3 -c 'import sys, os.path; print(os.path.abspath(sys.argv[1]))' "${1}"
    else
        python -c 'import sys, os.path; print(os.path.abspath(sys.argv[1]))' "${1}"
    fi
}

get_relative_path () {
    # Get a relative path to a file
    if type python3 > /dev/null ; then
        python3 -c 'import sys, os.path; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "${1}" "${2:-$PWD}"
    else
        python -c 'import sys, os.path; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "${1}" "${2:-$PWD}"
    fi
}

get_dnsdomainname () {
    # Wrap dnsdomainname if it's not present (on MacOS X)

    if type dnsdomainname > /dev/null 2>&1 ; then
        dnsdomainname
    else
        local fqdn
        fqdn="$(get_fqdn)"
        if [[ ${fqdn} == *.* ]] ; then
            echo "${fqdn#*.}"
        else
            echo ""
        fi
    fi
}

get_fqdn () {
    shopt -s nocasematch
    case $OSTYPE in
        *bsd* | dragonfly* ) hostname;;
        * ) hostname -f;;
    esac
}

chmod_idempotent () {
    # chmod does a `fchmodat` (after a `stat`) even if it does not need to
    # change anything as of 8.23-4.
    # This behavior is not ideal for this script as it would update the ctime.

    local new_mode_in_octal="${1##0}"
    local file_path="${2}"

    if [ "$(stat -c %a "${file_path}")" != "${new_mode_in_octal}" ] ; then
        chmod "${new_mode_in_octal}" "${file_path}"
    fi
}

chgrp_idempotent () {
    # chgrp does a `fchownat` (after a `newfstatat`) even if it does not need
    # to change anything as of 8.23-4.
    # This behavior is not ideal for this script as it would update the ctime.

    local new_group_name="${1}"
    local file_path="${2}"

    if [ "$(stat -c %G "${file_path}")" != "${new_group_name}" ] ; then
        chgrp "${new_group_name}" "${file_path}"
    fi
}
# }}}

get_openssl_ocsp_uri_directive () {
    local ocsp_uri
    case "${config['ocsp']}" in
        true|True)
            ocsp_uri="OCSP;URI.0              = \$ocsp_url"
            ;;
        false|False)
            ocsp_uri=""
            ;;
        *)
            ocsp_uri="OCSP;URI.0              = ${config['ocsp']}"
            ;;
    esac
    echo "$ocsp_uri"
}

get_openssl_name_constraints_directive () {
    local config_domain="${1}"
    local name_constraints
    case "${config['name_constraints']}" in
        true|True)
            case "${config['name_constraints_critical']}" in
                false|False)
                    name_constraints="nameConstraints         = permitted;DNS:${config_domain},permitted;DNS:.${config_domain}"
                    ;;
                *)
                    name_constraints="nameConstraints         = critical, permitted;DNS:${config_domain},permitted;DNS:.${config_domain}"
                    ;;
            esac
            ;;
        false|False)
            name_constraints=""
            ;;
        *)
            name_constraints="nameConstraints         = ${config['name_constraints']}"
            ;;
    esac

    echo "$name_constraints"
}

get_openssl_crl_distribution_points_directive () {
    local crl_distribution_points=''
    case "${config['crl']}" in
        true|True)
            crl_distribution_points="crlDistributionPoints   = @crl_info"
            ;;
        false|False)
            crl_distribution_points=""
            ;;
        *)
            crl_distribution_points="crlDistributionPoints   = ${config['crl']}"
            ;;
    esac

    echo "$crl_distribution_points"
}

initialize_environment () {

    declare -gA config

    config["pki_root"]="${PKI_ROOT:-$(pwd)}"
    config["pki_library"]="${PKI_LIBRARY:-openssl}"
    config["pki_ca_certificates"]="${PKI_CA_CERTIFICATES:-${config['pki_root']}/ca-certificates/by-group/all}"
    config["system_ca"]="true"

    config["pki_authorities"]="${config['pki_root']}/authorities"
    config["pki_requests"]="${config['pki_root']}/requests"

    config["pki_default_fqdn"]="$(get_fqdn)"
    config["pki_default_domain"]="$(get_dnsdomainname)"
    config["pki_default_domain_dn"]=$(echo "${config['pki_default_domain']}" | cut -d. -f1 | sed 's/.*/\u&/')

    config["domain"]=""
    config["ca_type"]=""
    config["issuer_name"]=""
    config["alt_authority"]=""
    config["name_constraints"]=""

    config["pki_default_sign_base"]="365"
    config["pki_default_root_sign_multiplier"]="12"
    config["pki_default_ca_sign_multiplier"]="10"
    config["pki_default_cert_sign_multiplier"]="3"

    config["root_sign_days"]=""
    config["ca_sign_days"]=""
    config["cert_sign_days"]=""
    config["key_size"]="4096"
    config["crl"]="true"
    config["ocsp"]="true"

    config["public_dir_group"]="$(id -g)"
    config["public_file_group"]="$(id -g)"

    config["private_dir_group"]="$(id -g)"
    config["private_file_group"]="$(id -g)"

    config["public_dir_mode"]="755"
    config["private_dir_mode"]="700"

    config["public_file_mode"]="644"
    config["private_file_mode"]="600"

    # shellcheck disable=SC2174
    test -d ${config['pki_root']} || mkdir -p -m ${config['public_dir_mode']} ${config['pki_root']}
}

enter_authority () {

    local authority="${1}"
    local config_file="${2:-config/authority.conf}"

    mkdir -p "${config['pki_authorities']}/${authority}"
    chmod_idempotent ${config['public_dir_mode']} "${config['pki_authorities']}/${authority}"

    cd "${config['pki_authorities']}/${authority}"
    local rc=$?

    if [ -r "${config_file}" ] ; then

        # shellcheck source=/dev/null
        . "${config_file}"
    fi

    return ${rc}
}

update_symlink () {

    local symlink_target="${1}"

    if [ -n "${symlink_target}" ] ; then

        shift 1

        for target in "${@}" ; do
            if  [ -r "${target}" ] ; then
                if [ -L "${symlink_target}" ] && [ "$(readlink "${symlink_target}")" == "${target}" ] ; then
                    break
                else
                    ln -sf "${target}" "${symlink_target}"
                fi
                break
            fi
        done

    fi
}

create_openssl_request_config () {

    local config_file="${1:-config/openssl-request.conf}"
    local config_dn="${2}"

    if [ -n "${config_file}" ] && [ -n "${config_dn}" ] && [ ! -r "${config_file}" ] ; then

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

[ req ]
default_md             = sha256
default_bits           = ${config['key_size']}
default_keyfile        = private/key.pem
prompt                 = no
encrypt_key            = no
distinguished_name     = ca_dn
utf8                   = yes
string_mask            = utf8only

[ ca_dn ]
EOF
        echo "${config_dn}" | tr "/" "\\n" | sed \
            -e 's/^[Cc]=/countryName=/' \
            -e 's/^[Ss][Tt]=/stateOrProvinceName=/' \
            -e 's/^[Ll]=/localityName=/' \
            -e 's/^[Oo]=/organizationName=/' \
            -e 's/^[Oo][Uu]=/organizationalUnitName=/' \
            -e 's/^[Cc][Nn]=/commonName=/' >> "${config_file}"

    fi
}

create_gnutls_request_config () {

    local config_file="${1:-config/gnutls-request.conf}"
    local config_dn="${2}"

    if [ -n "${config_file}" ] && [ -n "${config_dn}" ] && [ ! -r "${config_file}" ] ; then

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

EOF
        echo "${config_dn}" | tr "/" "\\n" | sed \
            -e 's/^[Cc]=/country = "/' \
            -e 's/^[Ss][Tt]=/state = "/' \
            -e 's/^[Ll]=/locality = "/' \
            -e 's/^[Oo]=/organization = "/' \
            -e 's/^[Oo][Uu]=/unit = "/' \
            -e 's/^[Cc][Nn]=/cn = "/' \
            -e 's/$/"/' >> "${config_file}"

    fi
}

create_openssl_selfsign_config () {

    local config_file="${1:-config/openssl-selfsign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"

    if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then
        local ocsp_uri
        ocsp_uri="$(get_openssl_ocsp_uri_directive)"

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

[ default ]
name                    = ${config_name}
domain_suffix           = ${config_domain}
aia_url                 = http://\$name.\$domain_suffix/crt/
crl_url                 = http://\$name.\$domain_suffix/crl/
ocsp_url                = http://\$name.\$domain_suffix/ocsp/
default_ca              = ca_default
name_opt                = utf8,esc_ctrl,multiline,lname,align

[ ca_default ]
home                    = .
database                = \$home/database/index
serial                  = \$home/database/serial
crlnumber               = \$home/database/crlnumber
certificate             = \$home/subject/cert.pem
private_key             = \$home/private/key.pem
RANDFILE                = \$home/private/random
new_certs_dir           = \$home/certs
unique_subject          = no
copy_extensions         = none
default_days            = ${config['root_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_root_sign_multiplier'] ))}
default_crl_days        = 365
default_md              = sha256
policy                  = policy_default
x509_extensions         = extension_default

[ crl_info ]
URI.0                   = \$crl_url

[ issuer_info ]
caIssuers;URI.0         = \$aia_url
${ocsp_uri}

[ extension_ocsp ]
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:false
extendedKeyUsage        = OCSPSigning
keyUsage                = critical, digitalSignature
subjectKeyIdentifier    = hash

[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

[ extension_default ]
EOF

        local name_constraints=''
        name_constraints="$(get_openssl_name_constraints_directive "$config_domain")"
        local crl_distribution_points=''
        crl_distribution_points="$(get_openssl_crl_distribution_points_directive)"
        if [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
basicConstraints       = critical, CA:TRUE
keyUsage               = critical, keyCertSign, cRLSign
subjectKeyIdentifier   = hash
${name_constraints}

EOF
        elif [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
authorityInfoAccess     = @issuer_info
basicConstraints        = critical, CA:TRUE, pathlen:0
${crl_distribution_points}
keyUsage                = critical, keyCertSign, cRLSign
subjectKeyIdentifier    = hash
${name_constraints}

EOF
        fi

    fi
}

create_gnutls_selfsign_config () {

    local config_file="${1:-config/gnutls-selfsign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"

    if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

expiration_days = ${config['root_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_root_sign_multiplier'] ))}

EOF

        if [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
ca
cert_signing_key
crl_signing_key

EOF
        elif [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
ca
cert_signing_key
crl_signing_key
path_len = 0

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        fi

    fi
}

create_openssl_sign_config () {

    local config_file="${1:-config/openssl-sign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"
    local config_issuer="${5}"

    if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then
        local ocsp_uri
        ocsp_uri="$(get_openssl_ocsp_uri_directive)"

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

[ default ]
name                    = ${config_name}
domain_suffix           = ${config_domain}
aia_url                 = http://\$name.\$domain_suffix/crt/
crl_url                 = http://\$name.\$domain_suffix/crl/
ocsp_url                = http://\$name.\$domain_suffix/ocsp/
default_ca              = ca_default
name_opt                = utf8,esc_ctrl,multiline,lname,align

[ ca_default ]
home                    = .
database                = \$home/database/index
serial                  = \$home/database/serial
crlnumber               = \$home/database/crlnumber
certificate             = \$home/subject/cert.pem
private_key             = \$home/private/key.pem
RANDFILE                = \$home/private/random
new_certs_dir           = \$home/certs
unique_subject          = no
policy                  = policy_default
x509_extensions         = extension_default
EOF

        if [ -z "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
copy_extensions         = none
default_days            = ${config['ca_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_ca_sign_multiplier'] ))}
default_crl_days        = 365
default_md              = sha256

EOF
        elif [ -z "${config_issuer}" ] && [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
copy_extensions         = copy
default_days            = ${config['cert_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_cert_sign_multiplier'] ))}
default_crl_days        = 365
default_md              = sha256

EOF
        elif [ -n "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "server" ] ; then
            cat << EOF >> "${config_file}"
copy_extensions         = copy
default_days            = ${config['cert_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_cert_sign_multiplier'] ))}
default_crl_days        = 30
default_md              = sha256

EOF
        fi

        cat << EOF >> "${config_file}"
[ crl_info ]
URI.0                   = \$crl_url

[ issuer_info ]
caIssuers;URI.0         = \$aia_url
${ocsp_uri}

[ extension_ocsp ]
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:false
extendedKeyUsage        = OCSPSigning
keyUsage                = critical, digitalSignature
subjectKeyIdentifier    = hash

EOF

        if [ -z "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        elif [ -z "${config_issuer}" ] && [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        elif [ -n "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "server" ] ; then
            cat << EOF >> "${config_file}"
[ policy_default ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

EOF
        fi

        local name_constraints=''
        name_constraints="$(get_openssl_name_constraints_directive "$config_domain")"
        local crl_distribution_points=''
        crl_distribution_points="$(get_openssl_crl_distribution_points_directive)"
        if [ -z "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical, CA:TRUE, pathlen:0
${crl_distribution_points}
keyUsage                = critical, keyCertSign, cRLSign
subjectKeyIdentifier    = hash
${name_constraints}

EOF
        elif [ -z "${config_issuer}" ] && [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always, issuer:always
basicConstraints        = critical, CA:FALSE
${crl_distribution_points}
extendedKeyUsage        = clientAuth, serverAuth
keyUsage                = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier    = hash

EOF
        elif [ -n "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "server" ] ; then
            cat << EOF >> "${config_file}"
[ extension_default ]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always, issuer:always
basicConstraints        = critical, CA:FALSE
${crl_distribution_points}
extendedKeyUsage        = clientAuth, serverAuth
keyUsage                = critical, digitalSignature, keyEncipherment
subjectKeyIdentifier    = hash

EOF
        fi

    fi
}

create_gnutls_sign_config () {

    local config_file="${1:-config/gnutls-sign.conf}"
    local config_name="${2}"
    local config_domain="${3}"
    local config_ca_type="${4}"
    local config_issuer="${5}"

    if [ -n "${config_file}" ] && [ ! -r "${config_file}" ] ; then

        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

EOF

        if [ -z "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "root" ] ; then
            cat << EOF >> "${config_file}"
expiration_days = ${config['ca_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_ca_sign_multiplier'] ))}

ca
cert_signing_key
crl_signing_key
path_len = 0

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        elif [ -z "${config_issuer}" ] && [ "${config_ca_type}" = "service" ] ; then
            cat << EOF >> "${config_file}"
expiration_days = ${config['cert_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_cert_sign_multiplier'] ))}
#copy_extensions         = copy

signing_key
encryption_key
tls_www_client
tls_www_server

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        elif [ -n "${config_issuer}" ] && [ -z "${config_ca_type}" ] || [ "${config_ca_type}" = "server" ] ; then
            cat << EOF >> "${config_file}"
expiration_days = ${config['cert_sign_days']:-$(( config['pki_default_sign_base'] * config['pki_default_cert_sign_multiplier'] ))}
#copy_extensions         = copy

signing_key
encryption_key
tls_www_client
tls_www_server

#ca_issuers_uri  = "http://${config_name}.${config_domain}/crt/"
crl_dist_points = "http://${config_name}.${config_domain}/crl/"
#ocsp_uri        = "http://${config_name}.${config_domain}/ocsp/"

EOF
        fi
    fi

}

save_authority_config () {

    local config_file="${1:-config/authority.conf}"

    # shellcheck disable=SC2034
    local ignored_config_vars=( pki_root pki_requests pki_authorities pki_ca_certificates public_file_group public_dir_group private_file_group private_dir_group )

    if [ -r "${config_file}" ] ; then
        if grep -q -E "^#\\s+Configuration\\s+file\\s+generated\\s+by\\s+pki-authority$" "${config_file}" ; then
            rm -f "${config_file}"
        fi
    fi

    if [ ! -r "${config_file}" ] ; then
        cat << EOF > "${config_file}"
# Configuration file generated by pki-authority

EOF
        for key in "${!config[@]}" ; do
            if ! array_exists ignored_config_vars "${key}" ; then
                echo "config['${key}']='${config[${key}]}'" >> "${config_file}"
            fi
        done
    fi
}

check_openssl_req_session_match () {
    local check_req="${1}"
    local check_session="${PKI_SESSION_TOKEN:-}"

    if [ -n "${check_req}" ] && [ -n "${check_session}" ] ; then
        if [ -r "${check_req}" ] ; then
            openssl asn1parse -in "${check_req}" | grep -q "${check_session}"
            rc=$?
            return ${rc}
        fi
    fi
    return 1
}

check_gnutls_req_session_match () {
    return "$(check_openssl_req_session_match "${@}")"
}

check_openssl_crt_ca_match () {
    local check_crt="${1}"
    local check_ca="${2}"
    local check_ca_issuer="${3:-}"

    if [ -n "${check_crt}" ] && [ -n "${check_ca}" ] ; then
        if [ -r "${check_crt}" ] && [ -r "${check_ca}" ] ; then
            if [ -n "${check_ca_issuer}" ] && [ -r "${check_ca_issuer}" ] ; then
                _openssl verify -CAfile "${check_ca_issuer}" -untrusted "${check_ca}" "${check_crt}"
                local rc=$?
            else
                _openssl verify -CAfile "${check_ca}" "${check_crt}"
                local rc=$?
            fi
            return ${rc}
        fi
    fi
    return 1
}

check_gnutls_crt_ca_match () {
    return "$(check_openssl_crt_ca_match "${@}")"
}

check_openssl_req_crt_match () {
    local check_req="${1}"
    local check_crt="${2}"

    local req_modulus
    local crt_modulus

    req_modulus="$(openssl req  -noout -modulus -in "${check_req}" | base64)"
    crt_modulus="$(openssl x509 -noout -modulus -in "${check_crt}" | base64)"

    if [ "${req_modulus}" = "${crt_modulus}" ] ; then
        return 0
    else
        return 1
    fi
}

check_gnutls_req_crt_match () {
    return "$(check_openssl_req_crt_match "${@}")"
}

sign_openssl_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_sect="${4}"
    local sign_ext="${5}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then

        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            _openssl ca -batch -notext \
                -in "${sign_request}" -out "${sign_out}.tmp" \
                -name "${sign_sect}" -extensions "${sign_ext}" \
                -config "${sign_config}"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

            if [ -d issuer ] ; then
                if [ -r "$(dirname "${sign_out}")/intermediate.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                fi
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            else
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            fi
        fi
    fi
}

sign_openssl_host_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_alt="${4:-}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then

        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            _openssl ca -batch -notext \
                -in "${sign_request}" -out "${sign_out}.tmp" \
                -config "${sign_config}"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

            if [ -d issuer ] ; then
                if [ -r "$(dirname "${sign_out}")/intermediate.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                fi
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            else
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            fi

            if [ -n "${sign_alt}" ] ; then

                if [ -d "${sign_alt}/issuer" ] ; then
                    if [ -r "$(dirname "${sign_out}")/alt_intermediate.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem"
                    fi
                    if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                    fi
                else
                    if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                    fi
                fi

            fi
        fi
    fi
}

sign_gnutls_host_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"
    local sign_alt="${4:-}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then

        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            certtool --generate-certificate --template "${sign_config}" \
                --load-request "${sign_request}" --load-ca-privkey private/key.pem \
                --load-ca-certificate subject/cert.pem --outfile "${sign_out}.tmp"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

            if [ -d issuer ] ; then
                if [ -r "$(dirname "${sign_out}")/intermediate.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/intermediate.pem"
                fi
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            else
                if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                    if ! diff -q -N "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem" > /dev/null ; then
                        ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                    fi
                else
                    ln -sf "$(get_relative_path "subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/root.pem"
                fi
            fi

            if [ -n "${sign_alt}" ] ; then

                if [ -d "${sign_alt}/issuer" ] ; then
                    if [ -r "$(dirname "${sign_out}")/alt_intermediate.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_intermediate.pem"
                    fi
                    if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/issuer/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                    fi
                else
                    if [ -r "$(dirname "${sign_out}")/root.pem" ] ; then
                        if ! diff -q -N "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem" > /dev/null ; then
                            ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                        fi
                    else
                        ln -sf "$(get_relative_path "${sign_alt}/subject/cert.pem" "$(dirname "${sign_out}")")" "$(dirname "${sign_out}")/alt_root.pem"
                    fi
                fi

            fi
        fi
    fi
}

sign_openssl_ca_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then
        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            _openssl ca -batch -notext \
                -in "${sign_request}" -out "${sign_out}.tmp" \
                -config "${sign_config}"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

        fi
    fi

}

sign_gnutls_ca_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then
        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            certtool --generate-certificate --template "${sign_config}" \
                --load-request "${sign_request}" --load-ca-privkey private/key.pem \
                --load-ca-certificate subject/cert.pem --outfile "${sign_out}.tmp"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

        fi
    fi

}

sign_openssl_root_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then
        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            _openssl ca -selfsign -batch -notext \
                -in "${sign_request}" -out "${sign_out}.tmp" \
                -config "${sign_config}"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

        fi
    fi

}

sign_gnutls_root_certificate () {
    local sign_config="${1}"
    local sign_request="${2}"
    local sign_out="${3}"

    if [ -n "${sign_config}" ] && [ -n "${sign_request}" ] && [ -n "${sign_out}" ] ; then
        if [ -r "${sign_request}" ] && [ ! -r "${sign_out}" ] ; then

            certtool --generate-self-signed --template "${sign_config}" \
                --load-request "${sign_request}" --load-privkey private/key.pem \
                --outfile "${sign_out}.tmp"

            if [ -r "${sign_out}.tmp" ] && [ -s "${sign_out}.tmp" ] ; then
                mv "${sign_out}.tmp" "${sign_out}"
            fi

        fi
    fi

}

generate_openssl_request () {

    local req_config="${1}"
    local req_out="${2}"

    if [ -n "${req_config}" ] && [ -n "${req_out}" ] ; then
        if [ -r "${req_config}" ] && [ ! -r "${req_out}" ] ; then

            local req_keyfile
            req_keyfile=$(grep -E '^default_keyfile\s+=\s+' "${req_config}" | awk '{print $3}')
            if [ -n "${req_keyfile}" ] ; then
                openssl req -new -key "${req_keyfile}" -config "${req_config}" -out "${req_out}.tmp"
            else
                openssl req -new -config "${req_config}" -out "${req_out}.tmp"
            fi
            test -r "${req_out}.tmp" && mv "${req_out}.tmp" "${req_out}"

        fi
    fi

}

generate_gnutls_request () {

    local req_config="${1}"
    local req_out="${2}"

    if [ -n "${req_config}" ] && [ -n "${req_out}" ] ; then
        if [ -r "${req_config}" ] && [ ! -r "${req_out}" ] ; then

            certtool --generate-request --template "${req_config}" \
                     --load-privkey private/key.pem --outfile "${req_out}.tmp"
            # Remove text output from the request
            test -r "${req_out}.tmp" && sed -n -i '/-----BEGIN NEW CERTIFICATE REQUEST-----/,$ p' "${req_out}.tmp"
            test -r "${req_out}.tmp" && mv "${req_out}.tmp" "${req_out}"

        fi
    fi

}

generate_openssl_serial () {
    local serial_file="${1}"
    local serial_length="${2:-16}"

    if [ -n "${serial_file}" ] && [ ! -r "${serial_file}" ] ; then
        openssl rand -hex "${serial_length}" > "${serial_file}"
    fi
}

generate_gnutls_serial () {
    # GnuTLS doesn't use this, so do nothing
    return 0
}

generate_openssl_rsa_private_key () {

    local key_file="${1:-private/key.pem}"
    local key_size="${2:-${config['key_size']}}"
    local key_group="${3:-${config['private_file_group']}}"

    test -r "${key_file}" || openssl genrsa -out "${key_file}.tmp" "${key_size}"

    if [ -r "${key_file}.tmp" ] ; then
        chmod "${config['private_file_mode']}" "${key_file}.tmp"
        chgrp "${key_group}" "${key_file}.tmp"
        mv "${key_file}.tmp" "${key_file}"
    fi

    chmod_idempotent ${config['private_file_mode']} "${key_file}"
    chgrp_idempotent "${key_group}" "${key_file}"

}

generate_gnutls_rsa_private_key () {

    local key_file="${1:-private/key.pem}"
    local key_size="${2:-${config['key_size']}}"
    local key_group="${3:-${config['private_file_group']}}"

    if ! [ -r "${key_file}" ] ; then
        if [ "$(version "$(certtool --version | head -n 1 | awk '{print $NF}')")" -lt "$(version 3.3.0)" ] ; then
            certtool --generate-privkey --outfile "${key_file}.tmp" --bits "${key_size}"
        else
            certtool --generate-privkey --rsa --outfile "${key_file}.tmp" --bits "${key_size}"
        fi
    fi

    if [ -r "${key_file}.tmp" ] ; then
        sed -n -i '/-----BEGIN RSA PRIVATE KEY-----/,$ p' "${key_file}.tmp"
        chmod "${config['private_file_mode']}" "${key_file}.tmp"
        chgrp "${key_group}" "${key_file}.tmp"
        mv "${key_file}.tmp" "${key_file}"
    fi

    chmod_idempotent "${config['private_file_mode']}" "${key_file}"
    chgrp_idempotent "${key_group}" "${key_file}"

}

create_public_directories () {

    local dir_group="${1}"
    if [ -n "${dir_group}" ] ; then
        shift
    else
        return 1
    fi

    local directories
    read -r -a directories <<< "${@}"

    for directory in "${directories[@]}" ; do
        mkdir -p "${directory}"
        chmod_idempotent "${config['public_dir_mode']}" "${directory}"
        chgrp_idempotent "${dir_group}" "${directory}"
    done

}

create_private_directories () {

    local dir_group="${1}"
    if [ -n "${dir_group}" ] ; then
        shift
    else
        return 1
    fi

    local directories
    read -r -a directories <<< "${@}"

    for directory in "${directories[@]}" ; do
        mkdir -p "${directory}"
        chmod_idempotent "${config['private_dir_mode']}" "${directory}"
        chgrp_idempotent "${dir_group}" "${directory}"
    done

}

sub_sign () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    req)
                        args["req"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    req=*)
                        args["req"]=${OPTARG#*=}
                        ;;
                    out)
                        args["out"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    out=*)
                        args["out"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} init <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    local name=""
    local req=""
    local out=""

    if key_exists args "name" ; then

        enter_authority "${args['name']}"

        local library="${config['pki_library']}"
        local input
        local output
        input="$(get_absolute_path ${args['req']})"
        output="$(get_absolute_path ${args['out']})"

        sign_${library}_certificate "config/${library}-sign.conf" "${input}" "${output}"

    fi
}

sub_sign-by-host () {

    if [ $# -gt 0 ] ; then
        if [ -d requests ] && [ -d realms ] ; then
            for authority in requests/* ; do
                if [ -r "authorities/$(basename "${authority}")/subject/cert.pem" ] ; then
                    for host in "${authority}"/* ; do
                        for play_host in "${@}" ; do
                            if [ "$(basename "${host}")" = "${play_host}" ] ; then
                                for realm in "${host}"/* ; do
                                    if [ -r "${realm}/request.pem" ] ; then

                                        local input
                                        local output
                                        input="$(get_absolute_path "${realm}"/request.pem)"
                                        output="$(get_absolute_path "realms/by-host/$(basename "${play_host}")/$(basename "${realm}")/internal/cert.pem")"

                                        enter_authority "$(basename "${authority}")"

                                        local alt_authority=""

                                        if [ -n "${config['alt_authority']}" ] && [ -d ../${config['alt_authority']} ] ; then
                                            alt_authority="$(get_absolute_path ../${config['alt_authority']})"
                                        fi

                                        if [ -r "${output}" ] && check_${config['pki_library']}_req_session_match "${input}" ; then
                                            rm -f "${output}"
                                        fi

                                        if [ -r "${output}" ] && ! check_${config['pki_library']}_req_crt_match "${input}" "${output}" ; then
                                            rm -f "${output}"
                                            if ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                                rm -f "${input}"
                                            fi
                                        fi

                                        if [ -r "${output}" ] && ! check_${config['pki_library']}_crt_ca_match "${output}" "subject/cert.pem" "issuer/subject/cert.pem" ; then
                                            rm -f "${output}"
                                            if ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                                rm -f "${input}"
                                            fi
                                        fi

                                        if [ ! -r "${output}" ] && ! check_${config['pki_library']}_req_session_match "${input}" ; then
                                            rm -f "${input}"
                                        fi

                                        if [ -r "${input}" ] ; then
                                            sign_${config['pki_library']}_host_certificate "config/${config['pki_library']}-sign.conf" "${input}" "${output}" "${alt_authority}"
                                        fi

                                        cd - > /dev/null

                                    fi
                                done
                            fi
                        done
                    done
                fi
            done
        fi
    fi

}

sub_new-ca () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    system-ca)
                        # shellcheck disable=SC2154
                        args["system_ca"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    system-ca=*)
                        args["system_ca"]=${OPTARG#*=}
                        ;;
                    alt-authority)
                        args["alt_authority"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    alt-authority=*)
                        args["alt_authority"]=${OPTARG#*=}
                        ;;
                    library)
                        args["pki_library"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    library=*)
                        args["pki_library"]=${OPTARG#*=}
                        ;;
                    subdomain)
                        # shellcheck disable=SC2154
                        args["subdomain"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    subdomain=*)
                        args["subdomain"]=${OPTARG#*=}
                        ;;
                    subject)
                        # shellcheck disable=SC2154
                        args["subject"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    subject=*)
                        args["subject"]=${OPTARG#*=}
                        ;;
                    type)
                        # shellcheck disable=SC2154
                        args["ca_type"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    type=*)
                        args["ca_type"]=${OPTARG#*=}
                        ;;
                    issuer-name)
                        # shellcheck disable=SC2154
                        args["issuer_name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    issuer-name=*)
                        args["issuer_name"]=${OPTARG#*=}
                        ;;
                    domain)
                        # shellcheck disable=SC2154
                        args["domain"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    domain=*)
                        args["domain"]=${OPTARG#*=}
                        ;;
                    root-sign-days)
                        args["root_sign_days"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    root-sign-days=*)
                        args["root_sign_days"]=${OPTARG#*=}
                        ;;
                    ca-sign-days)
                        args["ca_sign_days"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    ca-sign-days=*)
                        args["ca_sign_days"]=${OPTARG#*=}
                        ;;
                    cert-sign-days)
                        args["cert_sign_days"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    cert-sign-days=*)
                        args["cert_sign_days"]=${OPTARG#*=}
                        ;;
                    key-size)
                        args["key_size"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    key-size=*)
                        args["key_size"]=${OPTARG#*=}
                        ;;
                    crl)
                        args["crl"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    crl=*)
                        args["crl"]=${OPTARG#*=}
                        ;;
                    ocsp)
                        args["ocsp"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    ocsp=*)
                        args["ocsp"]=${OPTARG#*=}
                        ;;
                    name-constraints)
                        args["name_constraints"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    name-constraints=*)
                        args["name_constraints"]=${OPTARG#*=}
                        ;;
                    name-constraints-critical)
                        args["name_constraints_critical"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    name-constraints-critical=*)
                        args["name_constraints_critical"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} new-ca <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    if key_exists args "name" ; then

        enter_authority "${args['name']}"

        local config_changed="false"

        for key in "${!args[@]}" ; do
            if [ -n "${args[${key}]+x}" ] ; then
                if key_exists config "${key}" ; then
                    if [ "${config[${key}]}" != "${args[${key}]}" ] ; then
                        local config_changed="true"
                        config["${key}"]="${args[${key}]}"
                    fi
                else
                    local config_changed="true"
                    config["${key}"]="${args[${key}]}"
                fi
            fi
        done

        create_public_directories ${config['public_dir_group']} config database subject certs requests signed
        create_private_directories ${config['private_dir_group']} private

        if [ "${config_changed}" = "true" ] ; then
            save_authority_config config/authority.conf
        fi

        local name="${config['name']}"
        local library="${config['pki_library']}"

        generate_${library}_rsa_private_key private/key.pem

        create_${library}_request_config config/${library}-request.conf "${config['subject']}"
        if [ -z "${config['issuer_name']}" ] ; then
            create_${library}_selfsign_config config/${library}-selfsign.conf "${config['subdomain']}" "${config['domain']}" "${config['ca_type']}"
        fi
        create_${library}_sign_config config/${library}-sign.conf "${config['subdomain']}" "${config['domain']}" "${config['ca_type']}" "${config['issuer_name']}"
        if [ -n "${config['issuer_name']}" ] ; then
            update_symlink issuer ../${config['issuer_name']}
        fi

        if [ "${library}" = "openssl" ] ; then
            generate_${library}_serial database/serial
            test -r database/index || touch database/index
        fi

        generate_${library}_request config/${library}-request.conf subject/request.pem

        if [ -z "${config['issuer_name']}" ] && [ ! -L issuer ] ; then

            sign_${library}_root_certificate config/${library}-selfsign.conf \
                subject/request.pem subject/cert.pem

        elif [ -n "${config['issuer_name']}" ] && [ -L issuer ] ; then
            local subject_dir
            subject_dir="$(pwd)"
            cd issuer || exit 1
            sign_${library}_ca_certificate config/${library}-sign.conf \
                "${subject_dir}/subject/request.pem" "${subject_dir}/subject/cert.pem"
            cd - > /dev/null
        fi

        if [ "${config['system_ca']}" = "true" ] && [ -n "${config['pki_ca_certificates']}" ] && [ -n "${config['subdomain']}" ] && [ -z "${config['issuer_name']}" ] && [ ! -L issuer ] ; then
            if [ -r "$(dirname "${config['pki_ca_certificates']}")/${config['subdomain']}.${config['domain']}.crt" ] ; then
                if ! diff -q -N "$(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}")" "${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt" > /dev/null ; then
                    ln -sf "$(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}")" "${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt"
                fi
            else
                ln -sf "$(get_relative_path "subject/cert.pem" "${config['pki_ca_certificates']}")" "${config['pki_ca_certificates']}/${config['subdomain']}.${config['domain']}.crt"
            fi
        fi

    fi
}

sub_init () {

    local -A args

    local optspec=":hn-:"
    while getopts "${optspec}" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    name)
                        args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    name=*)
                        args["name"]=${OPTARG#*=}
                        ;;
                    library)
                        args["pki_library"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    library=*)
                        args["pki_library"]=${OPTARG#*=}
                        ;;
                    default-sign-base)
                        args["pki_default_sign_base"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    default-sign-base=*)
                        args["pki_default_sign_base"]=${OPTARG#*=}
                        ;;
                    root-sign-multiplier)
                        args["pki_default_root_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    root-sign-multiplier=*)
                        args["pki_default_root_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    ca-sign-multiplier)
                        args["pki_default_ca_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    ca-sign-multiplier=*)
                        args["pki_default_ca_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    cert-sign-multiplier)
                        args["pki_default_cert_sign_multiplier"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                        ;;
                    cert-sign-multiplier=*)
                        args["pki_default_cert_sign_multiplier"]=${OPTARG#*=}
                        ;;
                    *)
                        if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                            echo "Unknown option --${OPTARG}" >&2
                        fi
                        ;;
                esac
                ;;
            h)
                echo "usage: ${script} init <-n|--name[=]authority>" >&2
                exit 2
                ;;
            n)
                args["name"]="${!OPTIND}"; OPTIND=$(( OPTIND + 1 ))
                ;;
            *)
                if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                    echo "Non-option argument: '-${OPTARG}'" >&2
                fi
                ;;
        esac
    done

    if key_exists args "name" ; then

        enter_authority ${args['name']}

        for key in "${!args[@]}" ; do
            if [ -n "${args[${key}]+x}" ] ; then
                if key_exists config "${key}" ; then
                    if [ "${config[${key}]}" != "${args[${key}]}" ] ; then
                        local config_changed="true"
                        config["${key}"]="${args[${key}]}"
                    fi
                else
                    local config_changed="true"
                    config["${key}"]="${args[${key}]}"
                fi
            fi
        done

        create_public_directories ${config['public_dir_group']} config database subject certs requests signed
        create_private_directories ${config['private_dir_group']} private

        if [ "${config_changed}" = "true" ] ; then
            save_authority_config config/authority.conf
        fi

    fi
}

print_usage () {
    cat << EOF
Usage: ${script} <command> [parameters]
EOF

}

initialize_environment

if [ $# -gt 0 ] ; then

    subcommand="${1}"

    if [ -n "${subcommand}" ] ; then
        case "${subcommand}" in
            init|new-ca|sign|sign-by-host)
                shift
                "sub_${subcommand}" "${@}"
                ;;

            *)
                echo "${script}: Error: unknown subcommand '${subcommand}'" >&2
                print_usage
                exit 1
                ;;
        esac
    fi

else
    print_usage
    exit 1
fi
