#!/bin/bash # bj-validata-rebuild-gdbm SPRDIR # Пересобирает СОВМЕСТИМЫЙ справочник сертификатов (local.gdbm) из сертификатов, # вшитых в архивные local.pse + local.gdbm НРД. Формат НРД-gdbm не читается # gdbm-библиотекой («malformed»), но DER-сертификаты внутри — стандартный X.509. # Порядок критичен: leaf-серты → CA → CRL (иначе E070003A «нет доверенного»). # Справочник подписывается ключом с НОСИТЕЛЯ (нужен примонтированный ключевой # носитель + vdmkdev). Сборка во временной папке — провал не портит оригинал. set -uo pipefail VDLIB=/opt/Validata/VDCSP/lib/amd64 ZPKI=/opt/Validata/VDCSP/bin/amd64/zpki1utl SPR="${1:?usage: bj-validata-rebuild-gdbm SPRDIR}" PSE="$SPR/local.pse"; GDBM="$SPR/local.gdbm" [ -f "$PSE" ] || { echo "{\"ok\":false,\"error\":\"нет $PSE\"}"; exit 1; } WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT cp "$PSE" "$WORK/local.pse" # 1) извлечь серты (leaf=subject!=issuer первыми, CA=self-signed потом) + CRL EX=$(python3 - "$PSE" "$GDBM" "$WORK" <<'PY' import sys,subprocess,hashlib pse,gdbm,work=sys.argv[1],sys.argv[2],sys.argv[3] leaves={}; cas={}; crls={} for src in (pse,gdbm): try: data=open(src,'rb').read() except: continue i=0 while ibest[iss][0]: best[iss]=(t,h) keep={h for _,h in best.values()} for h in list(crls): if h not in keep: print('dropped older CRL',file=sys.stderr); del crls[h] n=0 for h,b in crls.items(): open(f"{work}/3crl_{n}.der","wb").write(b); n+=1 print(f"{len(leaves)} {len(cas)} {len(crls)}") PY ) || { echo "{\"ok\":false,\"error\":\"извлечение сертов не удалось\"}"; exit 1; } CERTARGS=""; for c in "$WORK"/1leaf_*.der "$WORK"/2ca_*.der; do [ -f "$c" ] && CERTARGS="$CERTARGS -dercer $c"; done CRLARGS=""; for c in "$WORK"/3crl_*.der; do [ -f "$c" ] && CRLARGS="$CRLARGS -dercrl $c"; done [ -n "$CERTARGS" ] || { echo "{\"ok\":false,\"error\":\"сертификаты не извлечены ($EX)\"}"; exit 1; } # 2) пересобрать gdbm во временной папке. ПСП подписывается ключом с НОСИТЕЛЯ. # crestor по умолчанию выбирает сертификат подписанта ПО ИМЕНИ владельца. Если у # владельца в справочнике несколько сертификатов (перевыпуск ключа — у клиента так # бывает), crestor берёт первый по имени и может попасть на СТАРЫЙ с истёкшим сроком # закрытого ключа → E070004C, хотя на носителе свежий действующий ключ. Тогда # перебираем сертификаты с НЕ истёкшим сроком закрытого ключа (свежие — первыми) и # указываем подписанта явно (-signer). Правильный (чей закрытый ключ на носителе) # даёт «Результат: OK», остальные — ошибку, и мы идём дальше. run_crestor() { # $1 — DER-файл сертификата подписанта (необязательно) local sgn=""; [ -n "${1:-}" ] && sgn="-signer -dercer $1" LD_LIBRARY_PATH="$VDLIB" "$ZPKI" -minimal -tools -crestor \ -uripse "pse://signed//$WORK/local.pse" -uriloc "file://$WORK/local.gdbm" \ $sgn $CERTARGS $CRLARGS 2>&1 } OUT=$(run_crestor "") if echo "$OUT" | grep -qiE 'E070004C|срок действия ключа ЭП'; then NOW=$(date -u +%s) CANDS=$(for c in "$WORK"/1leaf_*.der; do [ -f "$c" ] || continue pk=$(openssl x509 -inform DER -in "$c" -noout -text 2>/dev/null | awk '/Private Key Usage Period/{f=1} f&&/Not After/{sub(/.*Not After: /,"");print;exit}') [ -n "$pk" ] || continue ex=$(date -u -d "$pk" +%s 2>/dev/null) || continue [ "$ex" -gt "$NOW" ] || continue nb=$(openssl x509 -inform DER -in "$c" -noout -startdate 2>/dev/null | sed 's/notBefore=//') nbs=$(date -u -d "$nb" +%s 2>/dev/null || echo 0) printf '%s %s\n' "$nbs" "$c" done | sort -rn | awk '{print $2}' | head -25) for c in $CANDS; do OUT=$(run_crestor "$c") if echo "$OUT" | grep -qi 'Результат: OK'; then echo "signer: $(basename "$c")" >&2; break; fi done fi if ! echo "$OUT" | grep -qi 'Результат: OK'; then ERR=$(echo "$OUT" | grep -iE 'ошибк|Результат' | head -1 | tr -d '"') echo "{\"ok\":false,\"error\":\"crestor: $ERR\"}"; exit 1 fi [ -f "$WORK/local.gdbm" ] || { echo "{\"ok\":false,\"error\":\"gdbm не создан\"}"; exit 1; } # 3) положить пересобранные на место (атомарно) cp "$WORK/local.gdbm" "$GDBM.new" && mv "$GDBM.new" "$GDBM" cp "$WORK/local.pse" "$PSE.new" && mv "$PSE.new" "$PSE" echo "{\"ok\":true,\"leaf\":${EX% * *},\"crl\":${EX##* }}"