Набор скриптов для подписания и проверки ЭЦП КриптоПро на Linux

По соображениям безопасности:

  • рекомендуется вынести взаимодействие с ЭЦП на отдельный сервер
  • на сервере ЭЦП завести отдельного пользователя
  • подключаться к серверу по ssh по ключу

Получение информации об установленных сертификатах

certdigest.sh
#!/usr/bin/env bash
 
# Получение информации об установленных сертификатах
 
 
function digest()
{
  echo '-------BEGIN CERTMGR-------'
  certmgr -list
  echo RESULT: $?
  echo '-------END CERTMGR-------'
}
 
digest

На выходе получаем текст, предназначенный для парсинга приложением. Для удобства отдельные секции отделены --BEGIN-- и --END--. Если скрипт создает файл, то путь к нему пишется в FILE, и приложение должно самостоятельно его получить (например, используя scp) и удалить.

Проверка подписей файла ЭЦП + Хеш MD5

На STDIN подается файл, первый параметр - команда check - проверка подписи ЭЦП + хеш md5, md5 - только вычисление md5 суммы от оригинального файла (можно передать как подписанный, так и не подписанный - результат будет одинаков)

check.sh
#!/usr/bin/env bash
 
# Проверка подписей файла ЭЦП + Хеш MD5
# Вход: файл на STDIN, ...
# Выход: результаты выполнения предназначены для парсинга
 
SRC_FILE=`mktemp`
ORIGIN_FILE=`mktemp`
COMMAND=$1
 
cat - > $SRC_FILE
 
# Получаем список сертификатов
function certmgr_list()
{
  CERTMGR_RESP=`certmgr -list -f "$SRC_FILE"`
  CERTMGR_CODE=$?
  if [ "$CERTMGR_CODE" -eq "0" ]; then
    SHAS=`echo "$CERTMGR_RESP" | grep "SHA1 Hash" | cut -f 2 -d "x"`
  fi
}
 
# Получаем данные сертификатов
function certmgr_review()
{
  echo --------- CERTIFICATE SECTION BEGIN ---------
  for SHA in $SHAS
  do
    CERT_FILE=`mktemp`
    cryptcp -nochain -copycert -thumbprint "$SHA" -f "$SRC_FILE" -df "$CERT_FILE" -der
    echo --------- CERTIFICATE BEGIN ---------
    echo CERTIFICATE SHA1: "$SHA"
    openssl x509 -in "$CERT_FILE" -inform der -text -noout -nameopt oneline,-esc_msb,-space_eq,sep_comma_plus
    echo --------- CERTIFICATE END ---------
    rm "$CERT_FILE"
  done
  echo --------- CERTIFICATE SECTION END ---------
}
 
# Отчет
function summary()
{
  echo --------- SUMMARY BEGIN ---------
  echo CERTMGR CODE: $CERTMGR_CODE
  echo CRYPTCP CODE: $CRYPTCP_CODE
  echo MD5SUM: $MD5
  echo --------- SUMMARY END ---------
}
 
# Хеш файла в контейнере
function md5_check_origin()
{
  if [ -f "$ORIGIN_FILE" ]; then
    MD5=`md5sum "$ORIGIN_FILE" | cut -f 1 -d " "` # файл есть, вычисляем md5 оригинального файла
  else
    MD5='0' # ошибка проверки
  fi
}
 
# Хеш переданного файла
function md5_check_src()
{
  MD5=`md5sum "$SRC_FILE" | cut -f 1 -d " "`
}
 
# Проверка подписей
function cryptcp_check()
{
  echo --------- CRYPTCP BEGIN ---------
  case "$COMMAND" in
    "check" ) # Проверка подписи
      timeout 5s echo 'NNNNNNNNNNNNNNNNNNNN' | cryptcp -verify -nochain -norev -f "$SRC_FILE" "$SRC_FILE" "$ORIGIN_FILE"
    ;;
    "md5" ) # Только вычисление md5
      timeout 5s echo 'NNNNNNNNNNNNNNNNNNNN' | cryptcp -verify -nochain -norev -f "$SRC_FILE" "$SRC_FILE" "$ORIGIN_FILE"
    ;;
    * )
      echo 'No such command'
      exit 1
    ;;
  esac
  CRYPTCP_CODE=$?
  echo --------- CRYPTCP END ---------
}
 
# *************** RUN ****************
 
certmgr_list
 
if [ "$CERTMGR_CODE" -eq "0" ]; then
  certmgr_review
  cryptcp_check
  md5_check_origin
else
  md5_check_src
fi
 
summary
 
rm "$SRC_FILE"
rm "$ORIGIN_FILE"

Подписание файла ЭЦП

Используются хитрые приемы:

  • timeout 10s не дает скрипту повиснуть более 10 сек (иногда КрипроПро ожидает ввода с клавиатуры, так мы обезопасимся)
  • используется утилита expect которая ждет, когда запускаемая программа попросит ввода пароля с клавиатуры, и шлет этот пароль
  • echo NNNNNNNNNNNNNNNNNNNN - отвечает 'No' на все возможные вопросы программы
sign.sh
#!/usr/bin/env bash
 
# Подписание файла ЭЦП
# Вход: файл на STDIN, `SHA1 HASH` и `PIN` как параметры
# Выход: результаты выполнения предназначены для парсинга
# Для удаленного вызова необходимо настроить на сервере авторизацию по ключу
 
SHA_HASH=$1
PIN=$2
SRC_FILE=`mktemp`
DEST_FILE=$SRC_FILE.sig
 
cat - > $SRC_FILE
 
echo --------- CRYPTCP BEGIN ---------
timeout 10s /home/sign/interface/expect-sign.sh $SHA_HASH $PIN $SRC_FILE $DEST_FILE
 
 
CRYPTCP_CODE=$?
echo --------- CRYPTCP END ---------
 
if [ "$CRYPTCP_CODE" -eq "0" ]; then
 
  echo --------- CERTIFICATE SECTION BEGIN ---------
  CERT_FILE=`mktemp`
  cryptcp -nochain -copycert -thumbprint "$SHA_HASH" -f "$DEST_FILE" -df "$CERT_FILE" -der
  echo --------- CERTIFICATE BEGIN ---------
  echo CERTIFICATE SHA1: "$SHA_HASH"
  openssl x509 -in "$CERT_FILE" -inform der -text -noout -nameopt oneline,-esc_msb,-space_eq,sep_comma_plus
  echo --------- CERTIFICATE END ---------
  rm "$CERT_FILE"
  echo --------- CERTIFICATE SECTION END ---------
 
fi
 
 
echo --------- SUMMARY BEGIN ---------
echo CRYPTCP CODE: $CRYPTCP_CODE
echo FILE: $DEST_FILE
echo --------- SUMMARY END ---------
 
rm $SRC_FILE
expect-sign.sh
#!/usr/bin/expect
 
#
# Обход проблемы с интерактивным вводом пароля для подписания
#
 
set timeout 3
 
set thumbprint [lindex $argv 0]
set password [lindex $argv 1]
set src_file [lindex $argv 2]
set dest_file [lindex $argv 3]
 
spawn cryptcp -sign -thumbprint $thumbprint -nochain -der $src_file $dest_file
 
expect "Password:" { send "$password\r" }
 
expect eof

Получение неподписанного исходного файла

unsigned.sh
#!/usr/bin/env bash
 
# Получение неподписанного исходного файла
 
BASE_FILE=`mktemp`
SRC_FILE=$BASE_FILE.src
ORIGIN_FILE=$BASE_FILE.orig
FINAL_FILE=""
 
cat - > $SRC_FILE
 
# Получаем список сертификатов
function certmgr_list()
{
  CERTMGR_RESP=`certmgr -list -f "$SRC_FILE"`
  CERTMGR_CODE=$?
}
 
function digest()
{
  echo '-------BEGIN UNSIGNED-------'
  echo RESULT: $RESULT
  echo FILE: $UNSIGNED_FILE
  echo '-------END UNSIGNED-------'
}
 
# *************** RUN ****************
 
certmgr_list
 
if [ "$CERTMGR_CODE" -eq "0" ]; then
  timeout 5s echo 'NNNNNNNNNNNNNNNNNNNN' | cryptcp -verify -nochain -f "$SRC_FILE" "$SRC_FILE" "$ORIGIN_FILE"
  UNSIGNED_FILE=$ORIGIN_FILE
  rm "$SRC_FILE" > /dev/null 2>&1
else
  UNSIGNED_FILE=$SRC_FILE
  rm "$ORIGIN_FILE" > /dev/null 2>&1
fi
 
digest
 
rm "$BASE_FILE" > /dev/null 2>&1

Получение тумбы файла

Скрипт получает картинку для предпросмотра (thumbnail) как из подписанного (самостоятельно извлекает), так и не подписанного файла. Необходимо дополнительно поставить unoconv

thumbnail.sh
#!/usr/bin/env bash
 
# Получение тумбы файла
 
BASE_FILE=`mktemp`
SRC_FILE=$BASE_FILE.src
ORIGIN_FILE=$BASE_FILE.orig
FINAL_FILE=""
 
cat - > $SRC_FILE
 
# Получаем список сертификатов
function certmgr_list()
{
  CERTMGR_RESP=`certmgr -list -f "$SRC_FILE" > /dev/null 2>&1`
  CERTMGR_CODE=$?
}
 
function thumbnail()
{
  if [ -f "$STEP_1_FILE" ]; then
    timeout 10s unoconv "$STEP_1_FILE"
    RET_CODE=$?
    rm "$STEP_1_FILE"
    if [ "$RET_CODE" -eq "0" ]; then
      STEP_2_FILE=$BASE_FILE.pdf
      if [ -f "$STEP_2_FILE" ]; then
        timeout 10s unoconv -f gif "$STEP_2_FILE"
        RET_CODE=$?
        rm "$STEP_2_FILE"
        if [ "$RET_CODE" -eq "0" ]; then
          FINAL_FILE=$BASE_FILE.gif
          if [ -f "$FINAL_FILE" ]; then
            RESULT='0'
          else
            RESULT='1'
          fi
        else
          RESULT='1'
        fi
      else
        RESULT='1'
      fi
    else
      RESULT='1'
    fi
  else
    RESULT='1' # ошибка
  fi
}
 
function digest()
{
  echo '-------BEGIN THUMBNAIL-------'
  echo RESULT: $RESULT
  echo FILE: $FINAL_FILE
  echo '-------END THUMBNAIL-------'
}
 
# *************** RUN ****************
 
certmgr_list
 
if [ "$CERTMGR_CODE" -eq "0" ]; then
  timeout 5s echo 'NNNNNNNNNNNNNNNNNNNN' | cryptcp -verify -nochain -f "$SRC_FILE" "$SRC_FILE" "$ORIGIN_FILE"
  STEP_1_FILE=$ORIGIN_FILE
else
  STEP_1_FILE=$SRC_FILE
fi
 
thumbnail
digest
 
rm "$BASE_FILE" > /dev/null 2>&1
rm "$SRC_FILE" > /dev/null 2>&1
rm "$ORIGIN_FILE" > /dev/null 2>&1

Получение PDF файла для печати

Также необходима программа unoconv

pdf.sh
#!/usr/bin/env bash
 
# pdf для печати
 
BASE_FILE=`mktemp`
SRC_FILE=$BASE_FILE.src
FINAL_FILE=""
 
cat - > $SRC_FILE
 
function convertToPdf {
  timeout 10s unoconv "$SRC_FILE"
  RET_CODE=$?
  if [ "$RET_CODE" -eq "0" ]; then
    FINAL_FILE=$BASE_FILE.pdf
    RESULT='0'
  else
    RESULT='1'
  fi
}
 
function digest()
{
  echo '-------BEGIN PDF-------'
  echo RESULT: $RESULT
  echo FILE: $FINAL_FILE
  echo '-------END PDF-------'
}
 
convertToPdf
digest
 
rm "$BASE_FILE" > /dev/null 2>&1
rm "$SRC_FILE" > /dev/null 2>&1