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

# BT-82842
shopt -s expand_aliases
alias GETTEXT="gettext ${0##*/}"

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

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

##############################################################################
# main
##############################################################################
reset_fqdn
while [ $# != 0 ]; do
    case $1 in
        --certdir)
            certdir=${2:?$(GETTEXT "Ошибка в задании имени каталога для размещения сертификатов.")}
            reset_fqdn
            shift ;;
        --cacrt)
            cacrt=${2:?$(GETTEXT "Ошибка в задании имени файла с сертификатом УЦ.")}
            shift ;;
        --cakey)
            cakey=${2:?$(GETTEXT "Ошибка в задании имени файла с закрытым ключом УЦ.")}
            shift ;;
        --sekey)
            sekey=${2:?$(GETTEXT "Ошибка в задании имени файла с закрытым ключом сервера.")}
            shift ;;
        --sekey_parm)
            sekey_parm=${2:?$(GETTEXT "Ошибка в задании параметров вычисления закрытого ключа.")}
            shift ;;
        --secrt_days)
            secrt_days=${2:?$(GETTEXT "Ошибка в задании срока действия сертификата.")}
            shift ;;
        --pin)
            pin=${2:?$(GETTEXT "Ошибка в задании пароля для экспорта сертификата.")}
            shift ;;
        --host)
            FQDN=${2:?$(GETTEXT "Ошибка в задании FQDN.")}
            reset_fqdn
            shift ;;
        --export)
            export_needed="yes" ;;
        --push)
            remote_admin=${2:?$(GETTEXT "Ошибка в задании имени администратора удалённого сервера.")}
            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 $(GETTEXT "Предупреждение: отсутствует имя домена.") $clr_def"
fi

    what_to_send="сертификат"
    echo -e "$(GETTEXT "Параметры закрытого ключа:")"
    if [ -f "$sekey" ] ; then
        echo -e "    $(GETTEXT "Будет использован существующий закрытый ключ:") $sekey."
    else
        echo -e "    $(GETTEXT "Будет создан новый закрытый ключ:") $sekey. $(GETTEXT "Алгоритм и длина ключа:") \"$sekey_parm\"."
    fi
    echo -e "    $(GETTEXT "Будет создан новый сертификат") $secrt $(GETTEXT "со следующими параметрами:")"
    echo -e "        FQDN:                $FQDN"
    echo -e "        $(GETTEXT "Срок действия, дней:") $secrt_days"
    echo -e "        $(GETTEXT "Сертификат УЦ:")       $cacrt"
    echo -e "        $(GETTEXT "Закрытый ключ УЦ:")    $cakey"
    if [ "$export_needed" == "yes" ] ; then
        echo -e "    $(GETTEXT "Сертификат будет экспортирован в контейнер") $sep12."
        if [ "${pin}" == " " ] ; then
            echo -e "    $clr_red $(GETTEXT "Сертификат будет экспортирован без пароля") $clr_def."
        else
            if [ "$pin" != "" ] ; then
                echo -e "    $(GETTEXT "Сертификат будет экспортирован c паролeм") \"$pin\"."
            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 "    $(GETTEXT "Созданный") $what_to_send $(GETTEXT "для сервера") $FQDN $(GETTEXT "будет отправлен на сервер") $remote_host $(GETTEXT "от имени пользователя") $remote_admin $(GETTEXT "и размещён в домашнем каталоге пользователя")."
#        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 "$(GETTEXT "Нажмите 'yYдД' или Enter для выполнения, любую другую клавишу для отказа:") " reply ; echo
    case "$reply" in
        y | Y | Д | д | "") ;;
        *) exit 1 ;;
    esac
fi

sleep 2

##############################################################################
if [ ! -f "$sekey" ] ; then
    echo "$(GETTEXT "Создаётся новый закрытый ключ сервера. Ключ будет размещен в файле") $sekey."
    openssl req -newkey "$sekey_parm" -keyout "$sekey" -noenc -new -out "$secsr" -subj "/CN=$FQDN"
else
    echo "$(GETTEXT "Используется существующий закрытый ключ сервера. Ключ размещен в файле") $sekey."
fi
##############################################################################
echo "$(GETTEXT "Выпуск сертификата сервера") $FQDN"
##############################################################################
CONF=`mktemp`
case $certversion in
    4.6.x)
        princ2="GeneralString:${DOMAIN^^}@${DOMAIN^^}"
        ;;
    4.8.x)
        princ2="GeneralString:${DOMAIN^^}"
        ;;
    *)
        echo "$(GETTEXT "Неизвестный тип сертификата") \"$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 "$(GETTEXT "Введите пароль для экспорта сертификата в контейнер pkcs12:")" pin ; echo
            read -s -p "$(GETTEXT "Повторите пароль:")" pin2 ; echo
            if [ "$pin" == "$pin2" ] ; then
                break;
            fi
            echo "$(GETTEXT "Пароли не совпадают")."
        done
    fi
    openssl pkcs12 -export -in $secrt -inkey $sekey -certfile $cacrt -out $sep12 -passout pass:$pin
    printf "$(GETTEXT "Сертификат %s экспортирован в контейнер %s")" $secrt $sep12
fi

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