#!/bin/bash
# Выпуск сертификатов для сервера и сервера-реплики
#
set -ue

HOSTNAME="`hostname -s`"
DOMAIN="`hostname -d`"
FQDN="`hostname`"

timestamp_format="%y%m%d-%H%M%S"
timestamp="`date -u +\"$timestamp_format\"`"
certdir="/etc/ssl/freeipa"
sekey_parm="rsa:2048"
secrt_days="3650"
export_needed=""
remote_admin="admin"
push_needed=""
certdb="/etc/apache2/nssdb"
execute=""
pin=""
certversion="4.8.x"

clr_def="\033[0m"
clr_bld="\033[32m"
clr_red="\033[31m"
clr_ylw="\033[33m"

cacrt="$certdir/ca.crt"
cakey="$certdir/ca.key"

function reset_fqdn() {
    secsr="$certdir/$FQDN-$timestamp.csr"
    secrt="$certdir/$FQDN-$timestamp.crt"
    sekey="$certdir/$FQDN.key"
    sep12="$certdir/$FQDN-$timestamp.p12"
}

function help() {
    echo -e "Выпуск сертификата для сервера FreeIPA."
    echo -e "Вызов: $0 [--host FQDN] [--cacrt FILE.CRT] [--cakey FILE.KEY] [--sekey FQDN.KEY] [--help]"
    echo -e "Если будет создан новый закрытый ключ сервера он будет размещён в файле \"FQDN.key\"."
    echo -e "Созданный закрытый ключ будет далее использоваться для выпуска и перевыпуска всех сертификатов."
    echo -e "$clr_red!!! Замена закрытых ключей данным сценарием не поддерживается !!!$clr_def"
    echo -e "Выпущенный сертификат будет размещен в файле \"FQDN-TIMESTAMP.crt\" в каталоге для размещения сертификатов."
    echo -e "Экспортированный в контейнер сертификат будет размещен в файле \"FQDN-TIMESTAMP.p12\" в каталоге для размещения сертификатов."
    echo -e "Сертификаты могут быть скопированы на удалённую машину и зарегистрированы там в БД сертификатов.."
    echo -e "В качестве TIMESTAMP используются текущие дата и время в формате: \"$timestamp_format\" (см. справку man date)."
    echo -e "$clr_red!!! Для формирования TIMESTAMP используется всемирное координированное время (UTC). TIMESTAMP изменяется при каждом запуске !!!$clr_def"
    echo -e "Ключ и сертификат выпускаются со следующими фиксированными параметрами:"
    echo -e "    Алгоритм и длина закрытого ключа $sekey_parm (см. man openssl)."
    echo -e "    Срок действия сертификата:       $secrt_days"
    echo -e "Опции:"
    echo -e "    --certdir DIR    - Имя каталога для поиска ключа и сертификата удостоверяющего центра, и для размещения создаваемых сертификатов."
    echo -e "                       Если каталог не существует - он будет создан. Выбрано имя \"$clr_bld$certdir$clr_def\"."
    echo -e "    --host  FQDN     - Полное доменное имя (FQDN) сервера, для которого выпускается сертификат. Выбрано имя \"$clr_bld$FQDN$clr_def\"."
    echo -e "                       Если имя не задано - используется hostname текущего сервера."
    echo -e "    --cacrt FILE     - Имя файла с существующим сертификатом удостоверяющего центра. Выбрано имя \"$clr_bld$cacrt$clr_def\"."
    echo -e "    --cakey FILE     - Имя файла с существующим закрытым ключом удостоверяющего центра. Выбрано имя \"$clr_bld$cakey$clr_def\"."
    echo -e "    --sekey FILE     - Имя файла с закрытым ключом сервера. Если файл не существует - будет создан новый закрытый ключ."
    echo -e "                       Если имя не задано - ключ будет размещен в файле с именем \"FQDN.key\" в каталоге для размещения сертификатов."
    echo -e "                       Выбрано имя \"$clr_bld$sekey$clr_def\"."
    echo -e "    --sekey_parm ALG - Алгоритм и длина закрытого ключа сервера. Выбрано значение \"$clr_bld$sekey_parm$clr_def\" (см. man openssl)."
    echo -e "    --secrt_days NUM - Срок действия выпускаемого сертификата, дней. Выбрано значение \"$clr_bld$secrt_days$clr_def\" (см. man openssl)."
    echo -e "    --export         - Экспортировать сертификат в контейнер формата pkcs12 для установки нового сервера (новой реплики) FreeIPA."
    echo -e "                       Экспорт будет выполнен в файл \"$clr_bld$sep12$clr_def\"."
    echo -e "                       Не требуется для обновления сертификата уже установленного сервера."
    echo -e "    --pin PIN        - Пароль (\"пин\") для экспорта сертификата. Чтобы задать пустой пароль укажите пробел в кавычках: --pin \" \"."
    echo -e "                       Если никакой пароль не задан - он будет запрошен."
    echo -e "    --push ADMIN     - Попытаться скопировать через ssh/scp созданные файлы на сервер, указанный в параметре --host, и зарегистрировать их."
    echo -e "                       В параметре ADMIN можно задать не только имя пользователя, но и адрес целевого сервера, например: \"admin@192.168.32.11\"."
    echo -e "                       Все действия будут выполняться от имени \"$clr_bld$remote_admin$clr_def\". Все файлы будут копироваться в домашний каталог этого пользователя."
    echo -e "                       Если выполнялся экспорт сертификата для нового сервера (новой реплики), сертификат будет скопирован в файл с именем \"$clr_bld$FQDN.p12$clr_def\"."
    echo -e "                       Если создавался новый сертификат для существующего сервера, то:"
    echo -e "                           - Копия этого сертификата будет скопирована в файл с именем \"$clr_bld$FQDN.crt$clr_def\";"
    echo -e "                           - Будет сделана попытка зарегистрировать его в БД сертификатов \"$clr_bld$certdb$clr_def\"."
    echo -e "                       $clr_red!!! После регистрации нового сертификата в БД сертификатов службы FreeIPA должны быть перезапущены вручную !!!$clr_def"
    echo -e "    --46             - Создавать сертификаты для FreeIPA v4.6.x. Используется по умолчанию для Astra Linux Common Edition 2.12, Astra Linux Special Edition 1.6/8.1"
    echo -e "    --48             - Создавать сертификаты для FreeIPA v4.8.x. Используется по умолчанию для Astra Linux Special Edition x.7"
    echo -e "                       Текущая настройка версии сертификатов: \"$clr_bld$certversion$clr_def\""
    echo -e "    -y               - Выполнять действия без запроса подтверждения."
    echo -e "    --help | -h      - Вывести эту подсказку. Никакие реальные действия выполняться не будут."
    echo -e "                       Если этот ключ указан не первым, то в подсказке будут выведены значения, установленные предшествующими ключами."
    echo -e "                       ВНИМАНИЕ! Если указано несколько противоречащих друг другу опций результат может отличаться от ожидаемого."
}

function chkfile() {
    if [ ! -f "$1" ] ; then
        echo "Файл \"$1\" не найден."
        exit 1
    fi
}

##############################################################################
# main
##############################################################################
reset_fqdn
while [ $# != 0 ]; do
    case $1 in
        --certdir)
            certdir=${2:?Ошибка в задании имени каталога для размещения сертификатов.}
            reset_fqdn
            shift ;;
        --cacrt)
            cacrt=${2:?Ошибка в задании имени файла с сертификатом УЦ.}
            shift ;;
        --cakey)
            cakey=${2:?Ошибка в задании имени файла с закрытым ключом УЦ.}
            shift ;;
        --sekey)
            sekey=${2:?Ошибка в задании имени файла с закрытым ключом сервера.}
            shift ;;
        --sekey_parm)
            sekey_parm=${2:?Ошибка в задании параметров вычисления закрытого ключа.}
            shift ;;
        --secrt_days)
            secrt_days=${2:?Ошибка в задании срока действия сертификата.}
            shift ;;
        --pin)
            pin=${2:?Ошибка в задании пароля для экспорта сертификата.}
            shift ;;
        --host)
            FQDN=${2:?Ошибка в задании FQDN.}
            reset_fqdn
            shift ;;
        --export)
            export_needed="yes" ;;
        --push)
            remote_admin=${2:?Ошибка в задании имени администратора удалённого сервера.}
            push_needed="yes"
            shift ;;
        --46) certversion="4.6.x" ;;
        --48) certversion="4.8.x" ;;
        -y) execute="yes" ;;
        --help|-h)
            help
            exit 0 ;;
        *)
            help
            exit 1 ;;
    esac
    shift
done
chkfile "$cacrt"
chkfile "$cakey"
[ -d "$certdir" ] || mkdir -p "$certdir"
if [ -z "$DOMAIN" ] ; then
    echo -e "$clr_redПредупреждение: отсутствует имя домена.$clr_def"
fi

    what_to_send="сертификат"
    echo -e "Параметры закрытого ключа:"
    if [ -f "$sekey" ] ; then
        echo -e "    Будет использован существующий закрытый ключ \"$sekey\"."
    else
        echo -e "    Будет создан новый закрытый ключ: \"$sekey\". Алгоритм и длина ключа: \"$sekey_parm\"."
    fi
    echo -e "    Будет создан новый сертификат \"$secrt\" со следующими параметрами:"
    echo -e "        FQDN:                $FQDN"
    echo -e "        Срок действия, дней: $secrt_days"
    echo -e "        Сертификат УЦ:       $cacrt"
    echo -e "        Закрытый ключ УЦ:    $cakey"
    if [ "$export_needed" == "yes" ] ; then
        echo -e "    Сертификат будет экпортирован в контейнер \"$sep12\"."
        if [ "${pin}" == " " ] ; then
            echo -e "    $clr_redСертификат будет экпортирован без пароля$clr_def."
        else
            if [ "$pin" != "" ] ; then
                echo -e "    Сертификат будет экпортирован c паролeм \"\033[34m$pin$clr_def\"."
            fi
        fi
        what_to_send="контейнер"
    fi
    if [ "$push_needed" == "yes" ] ; then
        if [[ $remote_admin =~ .+@.+@.+ || $remote_admin =~ .+@.+ ]] ; then
                remote_host="${remote_admin##*@}"
                remote_admin="${remote_admin%@*}"
        else
            remote_host="$FQDN"
        fi
        echo -e "    Созданный $what_to_send для сервера \"$FQDN\" будет отправлен на сервер \"$remote_host\" от имени пользователя \"$remote_admin\" и размещён в домашнем каталоге этого пользователя."
#        user_dom="${remote_admin/*@/}"
#        host_dom="${remote_host/*./}"
#        if [ "$user_dom" != "$host_dom" ] ; then
#            echo -e "    $clr_redДомен пользователя, от имени которого отправляется сертификат, не совпадает с доменом сервера, на который отправляется сертификат.$clr_def"
#        fi
#        host_dom="${FQDN/*.//}"
#        if [ "$user_dom" != "$host_dom" ] ; then
#            echo -e "    $clr_redДомен пользователя, от имени которого отправляется сертификат, не совпадает с доменом сервера, для которого выпущен сертификат.$clr_def"
#        fi
#        if [ "$FQDN" != "$remote_host" ] ; then
#            echo -e "    $clr_redИмя сервера, для которого выпущен сертификат, не совпадает с именем сервера, на который отправляется сертификат.$clr_def"
#        fi
    fi
if [ "$execute" != "yes" ] ; then
    read -n 1 -p "Нажмите 'yYдД' или Enter для выполнения, любую другую клавишу для отказа: " reply ; echo
    case "$reply" in
        y | Y | Д | д | "") ;;
        *) exit 1 ;;
    esac
fi

sleep 2

##############################################################################
if [ ! -f "$sekey" ] ; then
    echo "Создаётся новый закрытый ключ сервера. Ключ будет размещен в файле \"$sekey\"."
    openssl req -newkey "$sekey_parm" -keyout "$sekey" -noenc -new -out "$secsr" -subj "/CN=$FQDN"
else
    echo "Используется существующий закрытый ключ сервера. Ключ размещен в файле \"$sekey\"."
fi
##############################################################################
echo "Выпуск сертификата сервера $FQDN"
##############################################################################
CONF=`mktemp`
case $certversion in
    4.6.x)
        princ2="GeneralString:${DOMAIN^^}@${DOMAIN^^}"
        ;;
    4.8.x)
        princ2="GeneralString:${DOMAIN^^}"
        ;;
    *)
        echo "Неизвестный тип сертификата \"$certversion\""
        exit 1
esac
cat <<EOT > $CONF
[kdc_cert]
basicConstraints=CA:FALSE
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
extendedKeyUsage = 1.3.6.1.5.5.7.3.1, 1.3.6.1.5.2.3.5
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
issuerAltName=issuer:copy
subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name,DNS:$FQDN

[kdc_princ_name]
realm = EXP:0, GeneralString:${DOMAIN^^}
principal_name = EXP:1, SEQUENCE:kdc_principal_seq

[kdc_principal_seq]
name_type = EXP:0, INTEGER:1
name_string = EXP:1, SEQUENCE:kdc_principals

[kdc_principals]
princ1 = GeneralString:krbtgt
princ2 = $princ2
EOT

openssl req -new -key $sekey -out $secsr -subj "/CN=$FQDN"
openssl x509 -req -in $secsr -CA $cacrt -CAkey $cakey -CAcreateserial -out $secrt -days "$secrt_days" -extfile $CONF -extensions kdc_cert
rm $CONF
# Экспорт сертификатов нужен только для установки нового сервера, и не нужен для обновления
if [ "$export_needed" == "yes" ] ; then
    [ -f sep12 ] && cp --backup=numbered $sep12{,.bak}
    if [ "$pin" == "" ] ; then
        while : ; do
            read -s -p "Введите пароль для экспорта сертификата в контейнер pkcs12:" pin ; echo
            read -s -p "Повторите пароль:" pin2 ; echo
            if [ "$pin" == "$pin2" ] ; then
                break;
            fi
            echo "Пароли не совпадают."
        done
    fi
    openssl pkcs12 -export -in $secrt -inkey $sekey -certfile $cacrt -out $sep12 -passout pass:$pin
    echo "Сертификат \"$secrt\" экспортирован в контейнер \"$sep12\"."
fi

if [ "$push_needed" == "yes" ] ; then
    if [ "`hostname`" == "$remote_host" ] ; then
        echo "Копировать локальные файлы на локальный сервер нет необходимости."
        if [ "$export_needed" == "yes" ] ; then
            echo "Регистрировать контейнер p12 в БД сертификатов нет необходимости."
        else
            echo "Регистрируем сертификат в локальной БД сертификатов."
            certutil -d \"$certdb\" -D -n \"$FQDN\" && certutil -d \"$certdb\" -A -t ,,, -n \"$FQDN\" -i \"$secrt\"
        fi
        exit 0
    else
        if [ "$export_needed" == "yes" ] ; then
            echo "Копируем контейнер \"$sep12\" на удалённый сервер \"$remote_host\" в файл \"$FQDN.p12\" в домашнем каталоге пользователя \"$remote_admin\"."
            scp -q "$sep12" "$remote_admin@$remote_host:$FQDN.p12"
            echo "Регистрировать контейнер p12 в БД сертификатов нет необходимости."
        else
            echo "Копируем сертификат \"$secrt\" на удалённый сервер \"$remote_host\" в файл \"$FQDN.crt\" в домашнем каталоге пользователя \"$remote_admin\"."
            scp -q "$secrt" "$remote_admin@$remote_host:$FQDN.crt"
            echo "Регистрируем сертификат в удалённой БД сертификатов."
            ssh "$remote_admin@$remote_host" "sudo certutil -d \"$certdb\" -D -n \"$FQDN\" && sudo certutil -d \"$certdb\" -A -t ,,, -n \"$FQDN\" -i \"$FQDN.crt\""
        fi
    fi
fi
