antclone.lua
antclone.lua
Библиотека antclone добавляет опциональные политики защиты для идентификаторов
EM-Marine/HID 125 кГц:
reject_t55xxраспознаёт и отклоняет типовые копии, записанные на T5557/T5577;require_traceтребует точного совпадения UID, типа метки и заводского Trace ID;rotate()реализует динамический пропуск T5577 с одноразовым состоянием по принципу first-use-wins.
Если политика не задана, библиотека сохраняет прежнее поведение и разрешает
обработку UID по существующим правилам. Низкоуровневые операции описаны на
странице модуля emmarine.
Границы защиты
Это не криптографическая аутентификация метки. Пароль T5577 ограничивает запись, но не шифрует пароль, команды или данные в радиоканале. Статические политики можно обойти эмулятором, который не отвечает на Page 1, микросхемой с изменяемой Page 1 или полным наблюдением и адаптивным воспроизведением радиообмена.
Динамическая схема обеспечивает first-use-wins для двух копий одного состояния, но не защищает от целевого перехвата всего обмена или полноценного адаптивного эмулятора. Для нескольких дверей необходима единая атомарная база: независимые локальные списки не обеспечивают защиту всего объекта.
Подключение
local antclone = require("antclone")
Библиотека включена в ROMFS ODNFC-LAN и ODNFC-RS485. Пары UID/Trace, пароли и
транзакции должны храниться в защищённой локальной или серверной базе, но не в
доступных через веб-интерфейс каталогах /public или /www.
authorize(reader, uid, policy)
Проверяет статическую политику для уже прочитанного обычного UID.
Аргументы:
- reader: объект
emmarine, которым был прочитан UID; - uid: обычный UID EM-Marine/HID;
- policy: таблица параметров политики.
Если policy.mode не равен reject_t55xx или require_trace, функция
возвращает true без активной проверки T55xx. Аппаратные и внутренние ошибки
emmarine.trace() вызывают исключение, а не считаются отсутствием T55xx.
Режим reject_t55xx
local allowed, reason, chip, trace = antclone.authorize(reader, uid, {
mode = "reject_t55xx",
})
Порядок проверки:
- приложение читает обычный EM-Marine/HID UID;
authorize()выполняет активный запрос Page 1;- подтверждённый ответ T5555/T5577 приводит к отказу
false, "t55xx_detected", chip, trace; - отсутствие подтверждённого ответа разрешает дальнейшую обработку UID.
Допустимая публичная формулировка:
Распознаёт и отклоняет типовые копии EM-Marine/HID, записанные на T5557/T5577.
Нельзя утверждать, что этот режим обнаруживает любой клон.
Режим require_trace
local allowed, reason, chip, trace = antclone.authorize(reader, uid, {
mode = "require_trace",
lookup = protected_backend.lookup_uid_trace,
})
Функция lookup(uid) должна вернуть точную зарегистрированную пару:
return {
chip = "T5577",
trace = "E015123489ABCDEF",
}
Допуск возможен только при совпадении UID, типа метки и Trace ID. Отсутствие
Page 1 возвращает false, "trace_required"; переход к обычной проверке EM4100
запрещён. Неизвестная пара возвращает not_registered, несовпадение —
trace_mismatch.
rotate(reader, presented_uid, options)
Выполняет транзакционную ротацию одноразового токена и возвращает true, new_uid только после успешной записи и контрольного чтения.
local allowed, result = antclone.rotate(reader, uid, {
store = protected_backend,
reader_id = "odnfc-lan-main-door",
max_failures = 3,
token_attempts = 8,
})
if allowed == true then
-- Только здесь можно разрешить открытие двери.
end
reader_id должен быть стабильным и уникальным идентификатором считывателя.
max_failures по умолчанию равен 3, token_attempts — 8.
Контракт хранилища
load(trace) -> record, version
cas(trace, version, replacement) -> boolean
token_exists(immutable_prefix, token) -> boolean
audit(event, fields) -- необязательно
Операция cas должна быть атомарной для всех дверей и в той же атомарной
операции резервировать следующий (immutable_prefix, token) либо отклонять его
при нарушении уникальности. token_exists служит только предварительной
проверкой и не устраняет гонку. Индекс коллизий должен включать текущие,
ожидающие и погашенные токены.
Запись метки содержит:
- заводской Trace ID и тип
T5577; - текущий десятисимвольный UID;
- индивидуальный восьмисимвольный hex-пароль;
- состояние
current,pending,committedилиfailed; - поколение токена, владельца незавершённой транзакции, число попыток и, при наличии, следующий UID и токен.
Пароль не должен попадать в audit() или обычные журналы. Хранилище обязано
самостоятельно защищать пароль и записи транзакций.
Последовательность прохода
- Читаются UID и Trace ID и проверяется точное соответствие записи T5577.
- Текущий токен атомарно гасится переводом записи в
pending. - Создаётся уникальный случайный 21-битный токен и следующий UID.
emmarine.rotate()защищённой командой записывает Page 0 Block 2.- После перезапуска поля повторно проверяются UID и неизменившийся Trace ID.
- Новое состояние фиксируется как
committed. - Только точный результат
trueразрешает открытие двери.
Неверный пароль, таймаут, ошибка записи, конфликт базы или несовпадение при контрольном чтении приводят к отказу.
Восстановление и first-use-wins
При состояниях pending и failed:
- метка со старым токеном может только продолжить уже созданную транзакцию и только на считывателе-владельце;
- метка с новым ожидающим токеном подтверждает выполненную запись и завершает фиксацию;
- любой другой или уже погашенный токен считается повтором;
- незавершённая транзакция сама по себе никогда не разрешает открытие двери;
- после достижения
max_failuresновые попытки блокируются.
Если первой предъявлена одна копия, именно она получает следующее состояние. Вторая копия остаётся со старым погашенным токеном и отклоняется. Два считывателя не могут атомарно продолжить одну последовательность разными ветвями.
Для каждой метки используется отдельный постоянный 32-битный пароль. Его ротация не входит в проход: она добавляет риск необратимого рассогласования, но не создаёт криптографической защиты. Следует учитывать ресурс EEPROM ATA5577 — приблизительно 100 000 записей на блок — и меньшую дальность записи по сравнению с чтением.