29e19c78a9
В ГОСТ-сертификатах ключ подписи действует год–15 месяцев, а сам сертификат — до 15 лет; bj показывал и проверял срок сертификата (например, 06.10.2036 вместо 2027). Теперь справочник профиля ИШ и сертификаты с флешки берут срок ключа (2.5.29.16), если он раньше; срок сертификата показывается рядом. У УЦ — по-прежнему срок сертификата. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
393 lines
26 KiB
Bash
Executable File
393 lines
26 KiB
Bash
Executable File
#!/bin/bash
|
||
# bj-keymedia-install — установка ключа Валидаты на съёмный носитель.
|
||
# Сохраняет структуру архива НРД (key.reg + spr<N>/ + vdkeys/) в профиль на
|
||
# носителе, ПЕРЕСОБИРАЕТ совместимый справочник сертификатов (архивный gdbm НРД
|
||
# несовместим — bj-validata-rebuild-gdbm чинит), и формирует профиль для ИШ.
|
||
# inspect <staging>
|
||
# install <staging> [profile_name]
|
||
# restart-ish
|
||
set -uo pipefail
|
||
DEFAULT_KEYMEDIA=/var/lib/igate/keymedia
|
||
BACKUP_DIR=/var/lib/bj/key-backups
|
||
IGATE_PROFILES=/var/lib/igate/profiles
|
||
IGATE_INI=/var/lib/igate/.Validata/zpki.ini
|
||
VDLIB=/opt/Validata/VDCSP/lib/amd64
|
||
ZPKI=/opt/Validata/VDCSP/bin/amd64/zpki1utl
|
||
REBUILD=/usr/local/sbin/bj-validata-rebuild-gdbm
|
||
ACTION="${1:-}"; STAGING="${2:-}"; PROFILE="${3:-}"
|
||
TARGET_MNT="${BJ_TARGET_MNT:-}"; TARGET_DEV="${BJ_TARGET_DEV:-}"
|
||
err(){ echo "{\"ok\":false,\"error\":\"$1\"}"; exit 1; }
|
||
|
||
# attach_keymedia DEV — ключевая флешка постоянно доступна ИШ, только чтение, под igate (как на рабочем сервере):
|
||
# строка fstab по UUID на $DEFAULT_KEYMEDIA с автомонтированием и ссылка реестра ключей Валидаты пользователя igate
|
||
# на флешку. Прежний fstab — копией /etc/fstab.bak-bj-<время>. Уже подключена эта же флешка — ничего не меняет.
|
||
attach_keymedia(){
|
||
local dev="$1" uuid fst ig_uid ig_gid opts unit
|
||
[ -b "$dev" ] || return 0
|
||
uuid=$(blkid -s UUID -o value "$dev" 2>/dev/null); fst=$(blkid -s TYPE -o value "$dev" 2>/dev/null)
|
||
[ -n "$uuid" ] && [ -n "$fst" ] || return 0
|
||
ig_uid=$(id -u igate 2>/dev/null) || return 0; ig_gid=$(id -g igate)
|
||
if ! grep -Eq "^UUID=$uuid[[:space:]]+$DEFAULT_KEYMEDIA[[:space:]]" /etc/fstab; then
|
||
cp -a /etc/fstab "/etc/fstab.bak-bj-$(date +%Y%m%d-%H%M%S)"
|
||
sed -i "\#[[:space:]]$DEFAULT_KEYMEDIA[[:space:]]#d" /etc/fstab
|
||
case "$fst" in vfat|exfat|ntfs*) opts="ro,uid=$ig_uid,gid=$ig_gid,fmask=0133,dmask=0022" ;; *) opts="ro" ;; esac
|
||
printf 'UUID=%s %s %s %s,nofail,x-systemd.automount,x-systemd.device-timeout=10 0 0\n' "$uuid" "$DEFAULT_KEYMEDIA" "$fst" "$opts" >> /etc/fstab
|
||
umount "$DEFAULT_KEYMEDIA" 2>/dev/null; mkdir -p "$DEFAULT_KEYMEDIA"
|
||
unit=$(systemd-escape -p --suffix=automount "$DEFAULT_KEYMEDIA")
|
||
systemctl daemon-reload; systemctl restart "$unit" 2>/dev/null
|
||
fi
|
||
install -d -o igate -g igate -m 700 /var/lib/igate/.Validata /var/lib/igate/.Validata/vdkeys
|
||
if [ ! -e /var/lib/igate/.Validata/vdkeys/vdkeys ]; then
|
||
ln -s "$DEFAULT_KEYMEDIA/vdkeys" /var/lib/igate/.Validata/vdkeys/vdkeys; chown -h igate:igate /var/lib/igate/.Validata/vdkeys/vdkeys
|
||
fi
|
||
ls "$DEFAULT_KEYMEDIA" >/dev/null 2>&1 # разбудить автомонтирование
|
||
KEYMEDIA_UUID="$uuid"
|
||
}
|
||
KEYMEDIA_UUID=""
|
||
|
||
# keysvc_restart — служба ключей Валидаты (vdcrysvc, init-скрипт vdmkdev): без неё ключ с носителя не
|
||
# загружается — ни для пересборки справочника (как на Астре), ни для подписи ИШ. На свежем сервере простой
|
||
# «systemctl restart vdmkdev» мог молча не сработать: юнит systemd появляется только после daemon-reload, а
|
||
# init-скрипт понимает лишь start/stop (restart — ошибка). Служба включается и при загрузке сервера.
|
||
keysvc_restart(){
|
||
systemctl daemon-reload >/dev/null 2>&1
|
||
systemctl enable vdmkdev.service >/dev/null 2>&1
|
||
if ! systemctl restart vdmkdev.service >/dev/null 2>&1; then
|
||
/etc/init.d/vdmkdev stop >/dev/null 2>&1; /etc/init.d/vdmkdev start >/dev/null 2>&1
|
||
fi
|
||
for _ in 1 2 3 4 5; do pgrep -x vdcrysvc >/dev/null && break; sleep 1; done
|
||
pgrep -x vdcrysvc >/dev/null || { /etc/init.d/vdmkdev start >/dev/null 2>&1; sleep 2; }
|
||
sleep 1
|
||
}
|
||
|
||
# ---- проверка ключа: подписать проверочный документ профилем ИШ активного контура (как подписывает ИШ) ----
|
||
KEYCHECK="skip"; KEYDIAG=""
|
||
keysign(){ # 0 — ключ подписал проверочный документ под igate
|
||
local kt ko; kt=$(mktemp -d); echo "bj key check" > "$kt/in"; chown -R igate:igate "$kt"
|
||
ko=$(timeout -k 5 60 runuser -u igate -- env HOME=/var/lib/igate LD_LIBRARY_PATH="$VDLIB" "$ZPKI" -sign -profile "$PROFILE" -registry -in "$kt/in" -out "$kt/out.p7s" 2>&1)
|
||
if [ -s "$kt/out.p7s" ]; then rm -rf "$kt"; KEYCHECK=ok; return 0; fi
|
||
rm -rf "$kt"
|
||
KEYCHECK="fail: $(printf '%s' "$ko" | grep -iE 'ошибк|результат|error' | head -1 | tr -d '"\\' | head -c 200)"; return 1
|
||
}
|
||
keydiag(){ # что видит ИШ: смонтирована ли ключевая флешка и какие ключи (*.vdk) ему доступны
|
||
local m v
|
||
m=$(findmnt -rn -o SOURCE,FSTYPE "$DEFAULT_KEYMEDIA" 2>/dev/null | grep -v autofs | head -1)
|
||
v=$(runuser -u igate -- env HOME=/var/lib/igate sh -c 'ls -1L "$HOME"/.Validata/vdkeys/*/*.vdk "$HOME"/.Validata/vdkeys/*.vdk 2>/dev/null' | xargs -r -n1 basename | tr '\n' ' ')
|
||
KEYDIAG="флешка в $DEFAULT_KEYMEDIA: ${m:-не смонтирована}; ключи, видимые ИШ: ${v:-нет}; служба ключей Валидаты (vdcrysvc): $(pgrep -x vdcrysvc >/dev/null && echo работает || echo 'не запущена')"
|
||
}
|
||
|
||
if [ "$ACTION" = "restart-ish" ]; then
|
||
systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true
|
||
echo '{"ok":true,"action":"restart-ish"}'; exit 0
|
||
fi
|
||
|
||
# ---- сертификаты НРД в справочнике активного профиля ИШ ----
|
||
# BJ_CHANNEL — начало кода канала ИШ контура (TEST3 | PROD): у тестового и боевого контуров свои ключи и профили
|
||
CHAN_PREFIX=$(printf '%s' "${BJ_CHANNEL:-}" | tr -cd 'A-Z0-9')
|
||
chan_where(){ if [ -n "$CHAN_PREFIX" ]; then echo "chanel_id in (select id from channels where code like '${CHAN_PREFIX}%')"; else echo "chanel_id is not null"; fi; }
|
||
prof_dir(){
|
||
PROFILE_NAME=$(su -s /bin/sh postgres -c "psql -d igate -tAc \"select value from parameters where name='cryptography.profile' and value<>'' and $(chan_where) order by chanel_id limit 1\"" 2>/dev/null | tr -d '[:space:]')
|
||
[ -n "$PROFILE_NAME" ] || err "профиль ИШ не задан (cryptography.profile)"
|
||
PROFDIR="$IGATE_PROFILES/$PROFILE_NAME"
|
||
[ -f "$PROFDIR/local.gdbm" ] && [ -f "$PROFDIR/local.pse" ] || err "нет справочника в $PROFDIR"
|
||
}
|
||
certs_list_json(){
|
||
python3 - "$PROFDIR/local.pse" "$PROFDIR/local.gdbm" <<'PY2'
|
||
import sys,subprocess,json,re
|
||
out=[]; seen=set()
|
||
MON=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
|
||
def key_not_after(b):
|
||
# срок закрытого ключа — расширение PrivateKeyUsagePeriod (2.5.29.16), [1] notAfter GeneralizedTime;
|
||
# у ГОСТ-сертификатов он короче срока самого сертификата (ключ подписи — год, сертификат — до 15 лет)
|
||
k=b.find(b'\x06\x03\x55\x1d\x10')
|
||
if k<0: return ''
|
||
j=k+5
|
||
try:
|
||
if b[j]==0x01: j+=3 # critical BOOLEAN
|
||
if b[j]!=0x04: return ''
|
||
j+=2 if b[j+1]<0x80 else 2+(b[j+1]&0x7f) # OCTET STRING
|
||
if b[j]!=0x30: return ''
|
||
end=j+2+b[j+1]; j+=2
|
||
while j<end:
|
||
t,n=b[j],b[j+1]; v=b[j+2:j+2+n].decode('ascii','replace'); j+=2+n
|
||
if t==0x81 and len(v)>=14:
|
||
return '%s %d %s:%s:%s %s GMT'%(MON[int(v[4:6])-1],int(v[6:8]),v[8:10],v[10:12],v[12:14],v[0:4])
|
||
except Exception: pass
|
||
return ''
|
||
for p in sys.argv[1:]:
|
||
store=p.rsplit('.',1)[-1]
|
||
try: d=open(p,'rb').read()
|
||
except Exception: continue
|
||
i=0
|
||
while True:
|
||
i=d.find(b'\x30\x82',i)
|
||
if i<0: break
|
||
L=int.from_bytes(d[i+2:i+4],'big'); b=d[i:i+4+L]
|
||
if 100<L<20000 and len(b)==4+L:
|
||
r=subprocess.run(['openssl','x509','-inform','DER','-noout','-subject','-issuer','-serial','-dates','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
||
if r.returncode==0:
|
||
o=r.stdout.decode('utf-8','replace'); g=lambda k:(re.search('^'+k+'=(.*)$',o,re.M) or [None,''])[1].strip()
|
||
cn=lambda v:(re.search(r'CN=([^,]+)',v) or [None,''])[1].strip()
|
||
sn=g('serial').upper()
|
||
if sn in seen: i+=4; continue
|
||
seen.add(sn)
|
||
out.append({'kind':'x509','store':store,'subject':g('subject'),'subject_cn':cn(g('subject')),'issuer_cn':cn(g('issuer')),'serial':sn,'not_before':g('notBefore'),'not_after':g('notAfter'),'key_not_after':key_not_after(b),'self_signed':g('subject')==g('issuer')})
|
||
else:
|
||
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-nextupdate','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
||
if r.returncode==0:
|
||
o=r.stdout.decode('utf-8','replace'); m=re.search(r'CN=([^,\n]+)',o); n=re.search(r'nextUpdate=(.*)',o)
|
||
out.append({'kind':'crl','store':store,'issuer_cn':(m.group(1).strip() if m else ''),'not_after':(n.group(1).strip() if n else '')})
|
||
i+=4
|
||
print(json.dumps(out,ensure_ascii=False))
|
||
PY2
|
||
}
|
||
if [ "$ACTION" = "keycheck" ]; then
|
||
prof_dir; PROFILE="$PROFILE_NAME"
|
||
RNG=$([ -x /usr/local/sbin/bj-validata-rng ] && /usr/local/sbin/bj-validata-rng status 2>/dev/null | grep -o '"ready":[a-z]*' | cut -d: -f2)
|
||
keysvc_up_quiet(){ pgrep -x vdcrysvc >/dev/null || keysvc_restart; }; keysvc_up_quiet
|
||
keysign || keydiag
|
||
printf '{"ok":true,"profile":"%s","rng":"%s","keycheck":"%s","keydiag":"%s"}\n' "$PROFILE" "${RNG:-}" "$KEYCHECK" "$(printf '%s' "$KEYDIAG" | tr -d '"\\')"
|
||
exit 0
|
||
fi
|
||
if [ "$ACTION" = "certs-list" ]; then
|
||
prof_dir
|
||
echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"dir\":\"$PROFDIR\",\"certs\":$(certs_list_json)}"; exit 0
|
||
fi
|
||
if [ "$ACTION" = "certs-apply" ]; then
|
||
prof_dir
|
||
case "$STAGING" in /var/lib/bj/nsd-certs-apply/*) [ -d "$STAGING" ] || err "нет папки с сертификатами" ;; *) err "staging вне зоны" ;; esac
|
||
TS=$(date +%Y%m%d-%H%M%S); BK="$BACKUP_DIR/certs-$PROFILE_NAME-$TS"; mkdir -p "$BK"
|
||
cp -a "$PROFDIR"/local.pse "$PROFDIR"/local.gdbm "$BK"/ || err "бэкап справочника не удался"
|
||
restore(){ cp -a "$BK"/local.pse "$BK"/local.gdbm "$PROFDIR"/; chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.*; }
|
||
OUT=$(EXTRA_DIR="$STAGING" EXCLUDE_SERIALS="${BJ_EXTRA:-}" "$REBUILD" "$PROFDIR" 2>"$BK/rebuild.log")
|
||
echo "$OUT" | grep -q '"ok":true' || { restore; echo "$OUT"; exit 1; }
|
||
chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.*
|
||
# оракул: подписать файл этим профилем (без GUI). Провал → откат.
|
||
T=$(mktemp -d); echo test > "$T/f"; chown -R igate:igate "$T"
|
||
SIG=$(su -s /bin/sh igate -c "HOME=/var/lib/igate LD_LIBRARY_PATH=$VDLIB $ZPKI -sign -profile '$PROFILE_NAME' -registry -in '$T/f' -out '$T/s' -sendcert" 2>&1); RC=$?
|
||
SIGOK=0; [ $RC -eq 0 ] && [ -s "$T/s" ] && SIGOK=1; echo "$SIG" | grep -qiE 'ошибк|error' && SIGOK=0
|
||
printf '%s\n' "$SIG" > "$BK/sign-check.log"; rm -rf "$T"
|
||
if [ $SIGOK -ne 1 ]; then restore; systemctl restart igate.service >/dev/null 2>&1; err "проверка подписи после пересборки не прошла — справочник откачен: $(echo "$SIG" | grep -iE 'ошибк|error|Результат' | head -1 | tr -d '"\\')"; fi
|
||
systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true
|
||
sleep 5; ACT=$(systemctl is-active igate.service 2>/dev/null || echo unknown)
|
||
echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"backup\":\"$BK\",\"igate\":\"$ACT\",\"rebuild\":$OUT,\"certs\":$(certs_list_json)}"; exit 0
|
||
fi
|
||
|
||
# ---- ключ уже на флешке: взять с неё ключ и справочник (только чтение) во временный каталог — дальше обычная
|
||
# установка (тот же носитель, то же имя профиля). Раскладка bj: <профиль>/key.reg + <профиль>/spr<N>/local.{pse,gdbm}
|
||
# + vdkeys/*.vdk; другая — ключ (*.vdk) и справочник (local.gdbm + local.pse) где угодно на носителе.
|
||
if [ "$ACTION" = "stage-media" ]; then
|
||
MNT="$TARGET_MNT"; RO_MNT=""
|
||
if [ -z "$MNT" ]; then
|
||
[ -n "$TARGET_DEV" ] || err "не выбран носитель"
|
||
RO_MNT="/run/bj-keymedia-ro-$$"; mkdir -p "$RO_MNT"
|
||
mount -o ro "$TARGET_DEV" "$RO_MNT" || { rmdir "$RO_MNT"; err "не смонтировать $TARGET_DEV"; }
|
||
MNT="$RO_MNT"
|
||
fi
|
||
done_ro(){ [ -n "$RO_MNT" ] && { umount "$RO_MNT" 2>/dev/null; rmdir "$RO_MNT" 2>/dev/null; }; }
|
||
VDKF=$(find "$MNT" -maxdepth 4 -iname '*.vdk' 2>/dev/null | sort | head -1)
|
||
GD=$(find "$MNT" -maxdepth 5 -iname 'local.gdbm' 2>/dev/null | sort | head -1)
|
||
[ -n "$VDKF" ] || { done_ro; err "на носителе нет ключа (файла *.vdk)"; }
|
||
[ -n "$GD" ] && [ -f "$(dirname "$GD")/local.pse" ] || { done_ro; err "на носителе есть ключ, но нет справочника сертификатов (local.pse и local.gdbm) — загрузите архив ключа от НРД: справочник есть в нём"; }
|
||
SPR=$(dirname "$GD"); PROFD=$(dirname "$SPR")
|
||
ID="media-$(date -u +%Y%m%d%H%M%S)"; ST="/var/lib/bj/media/iso/$ID"; mkdir -p "$ST/vdkeys"
|
||
if [ "$PROFD" != "$MNT" ] && [ -f "$PROFD/key.reg" ]; then
|
||
cp -a "$PROFD"/. "$ST"/ 2>/dev/null; PNAME=$(basename "$PROFD") # профиль bj: key.reg + spr<N>/
|
||
else
|
||
cp -a "$SPR" "$ST"/ 2>/dev/null; PNAME="" # справочник без профиля bj — имя по номеру
|
||
fi
|
||
cp -f "$VDKF" "$ST/vdkeys/"
|
||
rm -rf "$ST/vdkeys/vdkeys" 2>/dev/null
|
||
chown -R bj:bj "$ST" 2>/dev/null
|
||
done_ro
|
||
case "$PNAME" in *[!A-Za-z0-9_-]*) PNAME="" ;; esac
|
||
printf '{"ok":true,"staging":"%s","vdk":"%s","profile":"%s","spr":"%s"}\n' "$ID" "$(basename "$VDKF")" "$PNAME" "$(basename "$SPR")"
|
||
exit 0
|
||
fi
|
||
|
||
# use-media — ключ на флешке уже подготовлен (например, флешка с другого сервера bj): использовать как есть.
|
||
# На флешку НИЧЕГО не пишется: справочник пересобирается в копии на диске (staging от stage-media), из неё —
|
||
# профиль ИШ; ключ Валидата читает с флешки сама.
|
||
NOWRITE=0
|
||
if [ "$ACTION" = "use-media" ]; then NOWRITE=1; ACTION=install; fi
|
||
|
||
[ -n "$STAGING" ] && [ -d "$STAGING" ] || err "staging не найден"
|
||
case "$STAGING" in /var/lib/bj/media/iso/*) : ;; *) err "staging вне зоны" ;; esac
|
||
VDK=$(find "$STAGING" -iname '*.vdk' | head -1)
|
||
GDBM=$(find "$STAGING" -iname '*.gdbm' | head -1)
|
||
[ -n "$VDK" ] || err "в архиве нет .vdk"
|
||
VDK_NAME=$(basename "$VDK")
|
||
SPRDIR=""; [ -n "$GDBM" ] && SPRDIR=$(dirname "$GDBM") # .../spr1046
|
||
SPRNAME=""; [ -n "$SPRDIR" ] && SPRNAME=$(basename "$SPRDIR") # spr1046
|
||
USERNUM=$(echo "$SPRNAME" | grep -oE '[0-9]+' | head -1) # 1046
|
||
|
||
if [ "$ACTION" = "inspect" ]; then
|
||
printf '{"ok":true,"vdk":"%s","profile_dir":"%s","has_gdbm":%s,"has_crl":%s}\n' \
|
||
"$VDK_NAME" "$SPRNAME" "$([ -n "$GDBM" ]&&echo true||echo false)" \
|
||
"$([ -n "$(find "$STAGING" -iname '*.crl' | head -1)" ]&&echo true||echo false)"
|
||
exit 0
|
||
fi
|
||
[ "$ACTION" = "install" ] || err "неизвестное действие"
|
||
|
||
# датчик случайных чисел Валидаты: пока он не инициализирован (окно «двигайте мышью» на сервере без экрана никто
|
||
# не видит), ключ в тихом режиме не работает — ни пересборка справочника, ни подпись ИШ. Проверяем до записи на флешку.
|
||
if [ -x /usr/local/sbin/bj-validata-rng ] && /usr/local/sbin/bj-validata-rng status 2>/dev/null | grep -q '"ready":false'; then
|
||
err "датчик случайных чисел Валидаты не инициализирован — сначала пройдите «Датчик случайных чисел» на этом шаге (поводить мышью в поле)"
|
||
fi
|
||
|
||
# ---- целевая точка монтирования (флешка) ----
|
||
TMP_MNT=""; UMOUNT_AFTER=0
|
||
if [ "$NOWRITE" = 1 ]; then
|
||
[ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$(date -u +%Y%m%d-%H%M%S)}"
|
||
case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac
|
||
BACKUP=""; TARGET_MNT="флешка не менялась"
|
||
FLASH_SPR="$SPRDIR"; CRL_SRC="$STAGING" # копия на диске
|
||
finish(){ :; }
|
||
else
|
||
[ -z "$TARGET_MNT" ] && [ -z "$TARGET_DEV" ] && TARGET_MNT="$DEFAULT_KEYMEDIA"
|
||
if [ -z "$TARGET_MNT" ] && [ -n "$TARGET_DEV" ]; then
|
||
TMP_MNT="/run/bj-keymedia-$$"; mkdir -p "$TMP_MNT"
|
||
mount "$TARGET_DEV" "$TMP_MNT" || err "не смонтировать $TARGET_DEV"
|
||
TARGET_MNT="$TMP_MNT"; UMOUNT_AFTER=1
|
||
fi
|
||
[ -d "$TARGET_MNT" ] || err "целевой носитель недоступен"
|
||
|
||
TS=$(date -u +%Y%m%d-%H%M%S); mkdir -p "$BACKUP_DIR"
|
||
BACKUP="$BACKUP_DIR/keymedia-$TS.tar.gz"
|
||
tar czf "$BACKUP" -C "$TARGET_MNT" . 2>/dev/null || err "бэкап не удался"
|
||
|
||
REMOUNTED=0
|
||
mount | grep -q " $TARGET_MNT .*[(,]ro[,)]" && { mount -o remount,rw "$TARGET_MNT" || err "remount rw не удался"; REMOUNTED=1; }
|
||
finish(){ sync; [ "$REMOUNTED" = 1 ] && mount -o remount,ro "$TARGET_MNT" 2>/dev/null; [ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; }; }
|
||
trap finish EXIT
|
||
|
||
# имя профиля: задано пользователем, иначе PrUser<N>
|
||
[ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$TS}"
|
||
case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac
|
||
DEST="$TARGET_MNT/$PROFILE"
|
||
rm -rf "$DEST"; mkdir -p "$DEST" "$TARGET_MNT/vdkeys"
|
||
cp -rf "$STAGING"/. "$DEST"/ 2>/dev/null # key.reg + spr<N>/ + vdkeys/
|
||
cp -f "$VDK" "$TARGET_MNT/vdkeys/$VDK_NAME"
|
||
sync
|
||
FLASH_SPR="$DEST/$SPRNAME"; CRL_SRC="$DEST"
|
||
# записано — временное монтирование снять: дальше флешка подключается постоянно (только чтение)
|
||
[ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; UMOUNT_AFTER=0; }
|
||
fi
|
||
# ключевая флешка — постоянно для ИШ (справочник подписывается ключом с неё)
|
||
[ -n "$TARGET_DEV" ] && attach_keymedia "$TARGET_DEV"
|
||
|
||
# ---- зарегистрировать ключевой носитель (vdmkdev читает ключ с флешки) ----
|
||
keysvc_restart
|
||
sleep 2
|
||
|
||
# ---- ПЕРЕСОБРАТЬ совместимый справочник на флешке (архивный gdbm malformed) ----
|
||
REBUILD_RES="skip"
|
||
if [ -n "$SPRNAME" ] && [ -x "$REBUILD" ] && [ -f "$FLASH_SPR/local.pse" ]; then
|
||
chmod o+x /var/lib/bj 2>/dev/null; chmod -R a+rX "$FLASH_SPR" 2>/dev/null # igate (crestor) должен читать staging под /var/lib/bj
|
||
RB=$("$REBUILD" "$FLASH_SPR" 2>&1)
|
||
if echo "$RB" | grep -q '"ok":true'; then REBUILD_RES="ok"; else e=$(echo "$RB" | grep -o '"error":"[^"]*"' | head -1 | sed 's/^"error":"//;s/"$//'); [ -n "$e" ] || e=$(echo "$RB"|tr -d '\n"'|head -c160); REBUILD_RES="fail: $e"; fi
|
||
sync
|
||
fi
|
||
|
||
# флешка уже подготовлена (use-media): её справочник совместим — пересобран при первой установке; пересборка
|
||
# не удалась (ключ подписи справочника не загрузился) — взять его как есть. Совместимый gdbm начинается с cf9a5713,
|
||
# архивный НРД — с ce9a5713 (его без пересборки ИШ не читает).
|
||
if [ "$NOWRITE" = 1 ] && [ "$REBUILD_RES" != ok ] && [ -f "$FLASH_SPR/local.gdbm" ] && \
|
||
[ "$(head -c4 "$FLASH_SPR/local.gdbm" | od -An -tx1 | tr -d ' \n')" = cf9a5713 ]; then
|
||
REBUILD_RES="as-is"
|
||
fi
|
||
|
||
# ---- сформировать профиль для ИШ (стораны на диске, ключ остаётся на флешке) ----
|
||
IGATE_RES="skip"
|
||
if { [ "$REBUILD_RES" = "ok" ] || [ "$REBUILD_RES" = "as-is" ]; } && [ -d "$(dirname "$IGATE_PROFILES")" ]; then
|
||
IGP="$IGATE_PROFILES/$PROFILE"
|
||
rm -rf "$IGP"; mkdir -p "$IGP"
|
||
cp -f "$FLASH_SPR/local.pse" "$FLASH_SPR/local.gdbm" "$IGP"/ 2>/dev/null
|
||
# CRL-файлы рядом (если есть) — для офлайн-валидации
|
||
find "$CRL_SRC" -iname '*.crl' -exec cp -f {} "$IGP"/ \; 2>/dev/null
|
||
chown -R igate:igate "$IGP" 2>/dev/null
|
||
chmod 644 "$IGP"/local.* 2>/dev/null
|
||
# зарегистрировать профиль в zpki.ini ИШ (идемпотентно)
|
||
if [ -f "$IGATE_INI" ]; then
|
||
python3 - "$IGATE_INI" "$PROFILE" "$IGP" <<'PY' && IGATE_RES="ok" || IGATE_RES="ini-fail"
|
||
import sys
|
||
ini,name,igp=sys.argv[1],sys.argv[2],sys.argv[3]
|
||
L=open(ini).read().split("\n")
|
||
# убрать прежний блок профиля с тем же именем
|
||
out=[]; skip=False
|
||
i=0
|
||
while i<len(L):
|
||
l=L[i]
|
||
if l.strip().startswith("[Profile_") :
|
||
# прочитать имя блока
|
||
blk=[l]; j=i+1
|
||
while j<len(L) and not L[j].strip().startswith("["):
|
||
blk.append(L[j]); j+=1
|
||
if any(b.strip()==f"Name = {name}" for b in blk):
|
||
i=j; continue # выкинуть старый одноимённый
|
||
out+=blk; i=j; continue
|
||
out.append(l); i+=1
|
||
L=out
|
||
# найти max индекс Profile_N
|
||
idx=-1
|
||
for l in L:
|
||
s=l.strip()
|
||
if s.startswith("[Profile_") and s.endswith("]"):
|
||
try: idx=max(idx,int(s[len("[Profile_"):-1]))
|
||
except: pass
|
||
n=idx+1
|
||
L+=[f"[Profile_{n}]",f"Name = {name}","Count = 2",
|
||
f"store_0 = pse://signed//{igp}/local.pse",
|
||
f"store_1 = file://{igp}/local.gdbm"]
|
||
# Count в секции [Profile] — число блоков [Profile_N] (раньше прибавлялась единица при каждой установке,
|
||
# и после переустановки того же профиля Count расходился с числом профилей)
|
||
# блоки — подряд с нуля: Валидата читает Profile_0…Profile_{Count-1}, дыра после удалённого блока прятала профиль
|
||
cnt=0
|
||
for k,l in enumerate(L):
|
||
if l.strip().startswith("[Profile_") and l.strip().endswith("]"):
|
||
L[k]=f"[Profile_{cnt}]"; cnt+=1
|
||
firstp=next((k for k,l in enumerate(L) if l.strip().startswith("[Profile_")), len(L))
|
||
for k in range(firstp-1,-1,-1):
|
||
if L[k].strip().startswith("Count = "):
|
||
L[k]=f"Count = {cnt}"
|
||
break
|
||
open(ini,"w").write("\n".join(L))
|
||
PY
|
||
fi
|
||
# подключить профиль к ИШ: cryptography.profile в БД igate (канал 33)
|
||
nchan=1
|
||
if [ "$IGATE_RES" = "ok" ] && [ -n "$CHAN_PREFIX" ]; then
|
||
nchan=$(su -s /bin/sh postgres -c "psql -d igate -tAc \"select count(*) from channels where code like '${CHAN_PREFIX}%'\"" 2>/dev/null | tr -d '[:space:]')
|
||
fi
|
||
if [ "$IGATE_RES" = "ok" ] && [ "${nchan:-0}" = 0 ]; then
|
||
# канала этого контура в ИШ ещё нет: профиль запоминаем — bj-igate-admin подключит его при настройке канала
|
||
printf '%s\n' "$PROFILE" > "/var/lib/igate/.bj-profile-$CHAN_PREFIX"; chown igate:igate "/var/lib/igate/.bj-profile-$CHAN_PREFIX" 2>/dev/null
|
||
IGATE_RES="pending-channel"
|
||
elif [ "$IGATE_RES" = "ok" ]; then
|
||
# только канал контура этого ключа (BJ_CHANNEL); старый bj без контура — все каналы, как раньше. Канал с ключом включаем.
|
||
if su -s /bin/sh postgres -c "psql -d igate -tAc \"update parameters set value='$PROFILE' where name='cryptography.profile' and $(chan_where); update channels set enable=true where id in (select chanel_id from parameters where name='cryptography.profile' and value='$PROFILE'); update parameters set value='True' where name='enable' and chanel_id in (select chanel_id from parameters where name='cryptography.profile' and value='$PROFILE')\"" >/dev/null 2>&1; then
|
||
IGATE_RES="connected"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# ---- проверка: ключ подписывает под igate (как будет подписывать ИШ) — проверочный документ профилем ----
|
||
if [ "$IGATE_RES" = connected ] || [ "$IGATE_RES" = pending-channel ]; then
|
||
if ! keysign; then
|
||
if [ -n "$TARGET_DEV" ] && [ -b "$TARGET_DEV" ] && [ -z "$(findmnt -rn -o FSTYPE "$DEFAULT_KEYMEDIA" 2>/dev/null | grep -v autofs)" ]; then
|
||
# автомонтирование не сработало — смонтировать флешку напрямую (только чтение, под igate)
|
||
FT=$(blkid -s TYPE -o value "$TARGET_DEV" 2>/dev/null)
|
||
systemctl stop "$(systemd-escape -p --suffix=automount "$DEFAULT_KEYMEDIA")" 2>/dev/null; umount "$DEFAULT_KEYMEDIA" 2>/dev/null
|
||
case "$FT" in vfat|exfat|ntfs*) MO="ro,uid=$(id -u igate),gid=$(id -g igate),fmask=0133,dmask=0022" ;; *) MO="ro" ;; esac
|
||
mkdir -p "$DEFAULT_KEYMEDIA"; mount -o "$MO" "$TARGET_DEV" "$DEFAULT_KEYMEDIA" 2>/dev/null
|
||
fi
|
||
keysvc_restart # служба ключей заново видит носитель
|
||
keysign
|
||
fi
|
||
[ "$KEYCHECK" = ok ] || keydiag
|
||
fi
|
||
|
||
finish; trap - EXIT
|
||
printf '{"ok":true,"profile":"%s","spr":"%s","vdk":"%s","backup":"%s","target":"%s","rebuild":"%s","igate":"%s","igate_profile_dir":"%s","keymedia":"%s","nowrite":%s,"keycheck":"%s","keydiag":"%s"}\n' \
|
||
"$PROFILE" "$SPRNAME" "$VDK_NAME" "$BACKUP" "$TARGET_MNT" "$REBUILD_RES" "$IGATE_RES" "$IGATE_PROFILES/$PROFILE" "$KEYMEDIA_UUID" "$([ "$NOWRITE" = 1 ] && echo true || echo false)" "$KEYCHECK" "$(printf '%s' "$KEYDIAG" | tr -d '"\\')"
|