Files

103 lines
6.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 во временной папке (ключ подписи стора — с носителя)
OUT=$(LD_LIBRARY_PATH="$VDLIB" "$ZPKI" -minimal -tools -crestor \
-uripse "pse://signed//$WORK/local.pse" -uriloc "file://$WORK/local.gdbm" \
$CERTARGS $CRLARGS 2>&1)
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##* }}"