Подробный гайд по программной блокировке Рутокенов в Linux - Часть 2
5. Почему возникает проблема при нескольких токенах
Если токен один, схема простая:
- Появилось событие udev о подключении Рутокена.
- В системе появился ровно один новый токен в PKCS#11.
- Мы читаем его серийный номер.
- Если он запрещен — блокируем текущее USB-устройство.
Если токены подключаются последовательно, это тоже часто можно разобрать по времени:
- событие USB
add; - появление нового слота/токена;
- совмещаем по моменту времени.
Но если:
- несколько токенов уже подключены до загрузки ОС;
- несколько токенов подключили одновременно;
- несколько токенов появились до старта вашего сервиса;
то прямой однозначной привязки «вот это событие USB → вот этот слот PKCS#11» уже нет.
Особенно если все они имеют одинаковый USB iSerial, например:
ID_SERIAL=Aktiv_Rutoken_ECP
В этом случае на уровне USB-дескриптора устройства одинаковы. Различаются они только внутренним серийным номером токена, который виден уже через смарткарточный интерфейс / PKCS#11 / SDK.
6. Можно ли заблокировать Рутокен через PKCS#11?
Стандартно — нет
PKCS#11 не предоставляет механизм отключения устройства.
Можно:
C_CloseAllSessions(...);
C_Logout(...);
Но это не запретит другим процессам открыть токен заново.
Можно попытаться испортить состояние токена, например, заблокировать PIN, но это:
- деструктивно;
- нестабильно;
- требует знания PIN;
- может сделать токен временно или постоянно неработоспособным;
- не является штатным механизмом администрирования.
Поэтому для блокировки нужно использовать уровень ОС:
sysfs;udev;USBGuard;- аппаратное управление питанием портов, если оно есть.
Возможное исключение
У вендора могут быть собственные нестандартные функции, например:
- административные библиотеки;
- утилиты управления Рутокенами;
- расширенные функции в
librtpkcs11ecp.so; - SDK Aktiv, где можно получить больше данных об устройстве.
Если в документации Aktiv есть функция вида:
- получить список токенов;
- получить серийный номер;
- получить USB bus/port;
- отключить токен;
- извлечь токен;
то лучше использовать её. Но если опираться только на стандартный PKCS#11, то универсального способа нет.
7. Рабочий универсальный способ: сопоставление через временное отключение
Идея простая:
- Получаем список всех подключенных Рутокенов как USB-устройств.
- Получаем список серийных номеров токенов через PKCS#11.
- Временно отключаем одно USB-устройство.
- Снова читаем список токенов через PKCS#11.
- Смотрим, какой серийный номер исчез.
- Если исчез разрешенный токен — включаем устройство обратно.
- Если запрещенный — оставляем отключенным.
Это позволяет установить соответствие:
PKCS#11 serialNumber <-> USB sysfs path
Даже если:
- токены подключены до загрузки;
- токены подключены одновременно;
- у всех одинаковый USB
iSerial.
8. Подробный алгоритм
8.1. Подготовка
Нужно выполнять от root.
Также важно:
- не выполнять это одновременно с активными пользовательскими сеансами, если токены уже используются;
- остановить или не запускать приложения, которые держат открытые сессии с токенами;
- если используется
pcscd, он должен быть запущен и корректно обрабатывать hotplug; - библиотека PKCS#11 должна уметь перечитывать токены после смены состояния устройств.
8.2. Получить текущие серийные номера токенов
Например:
before=$(list_pkcs11_serials)
или через собственный код:
C_Initialize(NULL);
C_GetSlotList(CK_TRUE, slots, &slotCount);
for each slot:
C_GetTokenInfo(slot, &tokenInfo);
serial = trim(tokenInfo.serialNumber);
8.3. Отключить одно устройство
Например:
DEV="/sys/bus/usb/devices/1-2"
echo 0 > "$DEV/authorized"
udevadm settle --timeout=5
sleep 1
8.4. Снова получить список токенов
after=$(list_pkcs11_serials)
8.5. Найти исчезнувший серийный номер
gone=$(comm -23 <(echo "$before") <(echo "$after"))
Если gone непустой, значит отключенное USB-устройство соответствовало исчезнувшему токену.
Например:
gone = 1234567890ABCDEF
Тогда:
/sys/bus/usb/devices/1-2 <-> 1234567890ABCDEF
8.6. Принять решение
Если серийный номер разрешен:
echo 1 > "$DEV/authorized"
udevadm settle --timeout=5
Если запрещен:
# Оставить:
echo 0 > "$DEV/authorized"
или, если хотите жестче:
echo 1 > "$DEV/remove"
Но remove сложнее возвращать, поэтому обычно лучше authorized=0.
9. Пример тестового скрипта
Для боевого использования лучше писать полноценный демон на C/Python/Go и использовать нативный PKCS#11 API, но для проверки идеи можно начать с такого.
#!/usr/bin/env bash
set -u
PKCS11_MODULE="/usr/lib/librtpkcs11ecp.so"
ALLOWED_FILE="/etc/rutoken/allowed_serials"
mkdir -p /etc/rutoken
touch "$ALLOWED_FILE"
list_rutoken_usb_devices() {
local d base v
for d in /sys/bus/usb/devices/*; do
base="${d##*/}"
[[ "$base" == usb* ]] && continue
[[ "$base" == *:* ]] && continue
[[ -f "$d/idVendor" ]] || continue
[[ -f "$d/idProduct" ]] || continue
v=$(cat "$d/idVendor")
# Проверьте реальный idVendor/ID_PRODUCT ваших устройств через lsusb.
if [[ "$v" == "0a89" ]]; then
echo "$d"
fi
done
}
list_pkcs11_serials() {
pkcs11-tool --module "$PKCS11_MODULE" --list-slots 2>/dev/null |
sed -n 's/.*Serial Number:[[:space:]]*//p' |
sed 's/[[:space:]]*$//' |
sort -u
}
is_allowed_serial() {
local serial="$1"
grep -qxF "$serial" "$ALLOWED_FILE"
}
all_allowed() {
local s
for s in $1; do
if ! is_allowed_serial "$s"; then
return 1
fi
done
return 0
}
wait_pkcs11_change() {
local old="$1"
local i new
for i in {1..20}; do
new=$(list_pkcs11_serials)
if [[ "$new" != "$old" ]]; then
echo "$new"
return 0
fi
sleep 0.3
done
echo "$old"
return 1
}
disable_device() {
local dev="$1"
echo "[-] Disable: $dev" >&2
echo 0 > "$dev/authorized" 2>/dev/null || true
udevadm settle --timeout=5 || true
sleep 1
}
enable_device() {
local dev="$1"
echo "[+] Enable: $dev" >&2
echo 1 > "$dev/authorized" 2>/dev/null || true
udevadm settle --timeout=5 || true
sleep 1
}
echo "=== Rutoken devices ==="
list_rutoken_usb_devices
echo
echo "=== Current PKCS#11 serials ==="
list_pkcs11_serials
before=$(list_pkcs11_serials)
for dev in $(list_rutoken_usb_devices); do
echo
echo "Probing device: $dev"
disable_device "$dev"
now=$(wait_pkcs11_change "$before")
gone=$(comm -23 <(printf '%s\n' "$before") <(printf '%s\n' "$now") | grep -v '^$' || true)
if [[ -n "$gone" ]]; then
echo "Device $dev corresponds to token serial(s):"
echo "$gone"
if all_allowed "$gone"; then
echo "Allowed token(s), re-enabling device."
enable_device "$dev"
else
echo "Forbidden token(s), leaving device disabled."
# Оставляем authorized=0
fi
else
echo "No PKCS#11 serial disappeared for $dev."
echo "Если политика строгая, можно оставить устройство заблокированным."
echo "В тестовом режиме включаем обратно."
enable_device "$dev"
fi
before=$(list_pkcs11_serials)
done
echo
echo "=== Final PKCS#11 serials ==="
list_pkcs11_serials
Запуск:
sudo ./rutoken-probe.sh
Файл разрешенных серийных номеров:
sudo mkdir -p /etc/rutoken
sudo nano /etc/rutoken/allowed_serials
Формат:
1234567890ABCDEF
FEDCBA0987654321
Информация предоставлена в ознакомительных целях. Применение описанных настроек в системах, должно осуществляться только после согласования с ответственными за информационную безопасность и в соответствии с требованиями ФСТЭК, ФСБ и иных уполномоченных органов.