ed2d0ce4e5
crestor выбирал сертификат подписанта ПСП по имени владельца. Если у владельца в справочнике несколько сертификатов (перевыпуск ключа — у клиента так бывает), crestor брал первый по имени и мог попасть на старый с истёкшим сроком закрытого ключа → E070004C «Ключ ЭП/закрытый ключ шифрования уже истёк», хотя на носителе свежий действующий ключ. bj-validata-rebuild-gdbm: при E070004C перебирает leaf-сертификаты с НЕ истёкшим сроком закрытого ключа (свежие первыми, до 25) и указывает подписанта явно (-signer -dercer). Правильный (чей закрытый ключ на носителе) даёт Результат: OK, остальные — ошибку, идём дальше. Проверено на живой флешке клиента: справочник пересобрался (cf9a5713), signer выбран верный. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
131 lines
8.3 KiB
Bash
Executable File
131 lines
8.3 KiB
Bash
Executable File
#!/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 i<len(data)-4:
|
|
if data[i]==0x30 and data[i+1]==0x82:
|
|
ln=(data[i+2]<<8)|data[i+3]; blob=data[i:i+4+ln]
|
|
if len(blob)==4+ln:
|
|
x=subprocess.run(['openssl','x509','-inform','DER','-noout','-subject','-issuer'],input=blob,capture_output=True)
|
|
if x.returncode==0:
|
|
o=x.stdout.decode()
|
|
subj=[l for l in o.split(chr(10)) if l.startswith('subject')][0][7:]
|
|
iss=[l for l in o.split(chr(10)) if l.startswith('issuer')][0][6:]
|
|
(cas if subj==iss else leaves)[hashlib.md5(blob).hexdigest()]=blob
|
|
elif subprocess.run(['openssl','crl','-inform','DER','-noout'],input=blob,capture_output=True).returncode==0:
|
|
crls[hashlib.md5(blob).hexdigest()]=blob
|
|
i+=1
|
|
# внешние сертификаты НРД (напр. новый серт крипто-сервера) — берём только те, чей издатель уже есть среди CA хранилища
|
|
import os,glob
|
|
casubj=set()
|
|
for b in cas.values():
|
|
r=subprocess.run(['openssl','x509','-inform','DER','-noout','-subject'],input=b,capture_output=True); casubj.add(r.stdout.decode().strip()[8:])
|
|
for f in sorted(glob.glob(os.environ.get('EXTRA_DIR','/var/lib/bj/.bj/nsd-certs')+'/*.cer')):
|
|
b=open(f,'rb').read()
|
|
if b[:2]!=b'\x30\x82':
|
|
r=subprocess.run(['openssl','x509','-in',f,'-outform','DER'],capture_output=True); b=r.stdout
|
|
x=subprocess.run(['openssl','x509','-inform','DER','-noout','-issuer'],input=b,capture_output=True)
|
|
if x.returncode!=0: continue
|
|
iss=x.stdout.decode().strip()[7:]
|
|
if iss in casubj: leaves[hashlib.md5(b).hexdigest()]=b; print('extra:',os.path.basename(f),file=sys.stderr)
|
|
else: print('extra skip (нет CA):',os.path.basename(f),file=sys.stderr)
|
|
# исключить сертификаты по серийному номеру (напр. старый серт крипто-сервера НРД после смены ключа)
|
|
excl={x.strip().upper().lstrip('0') for x in os.environ.get('EXCLUDE_SERIALS','').split(',') if x.strip()}
|
|
if excl:
|
|
for h in list(leaves):
|
|
r=subprocess.run(['openssl','x509','-inform','DER','-noout','-serial'],input=leaves[h],capture_output=True)
|
|
sn=r.stdout.decode().strip().split('=')[-1].upper().lstrip('0')
|
|
if sn in excl: del leaves[h]; print('excluded serial:',sn,file=sys.stderr)
|
|
n=0
|
|
for h,b in leaves.items(): open(f"{work}/1leaf_{n}.der","wb").write(b); n+=1
|
|
n=0
|
|
for h,b in cas.items(): open(f"{work}/2ca_{n}.der","wb").write(b); n+=1
|
|
# newest CRL: на каждого издателя оставить только самый свежий CRL — crestor падает
|
|
# «имеется более новый САС» (E0700087), если после нового добавить старый
|
|
import datetime
|
|
best={}
|
|
for h,b in crls.items():
|
|
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-lastupdate'],input=b,capture_output=True)
|
|
o=r.stdout.decode(errors='replace'); iss=[l for l in o.splitlines() if l.startswith('issuer')][0]
|
|
lu=[l for l in o.splitlines() if l.startswith('lastUpdate')][0].split('=',1)[1].strip()
|
|
try: t=datetime.datetime.strptime(lu,'%b %d %H:%M:%S %Y %Z')
|
|
except Exception: t=datetime.datetime.min
|
|
if iss not in best or t>best[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##* }}"
|