Содержание Показать Скрыть
- Аттестация — это предложение, а не запись
- Из чего состоит грант
- Два режима верификации
- Живой пример: отдел, сотрудник, подрядчик
- Иерархия
- Настройка (корень — один раз)
- Найм (HR — автономно)
- Верификация двумя сервисами
- Увольнение — правильный способ
- Подрядчики — самоистекающий доступ
- Отзыв: одна операция, вся ветка
- Что аттестации дают — и чего не дают
- Связь с деревом ключей
- Что читать дальше
Из seed-фразы детерминированно выводится дерево ключей: Identity → Handle → SubHandle — полный разбор в статье «Me2eM: идентичность из seed-фразы». Дерево даёт каждому — пользователю, устройству, агенту — собственную пару ключей. Но пара ключей сама по себе отвечает только на вопрос «кто подписал». Она не отвечает на вопросы, которые превращают подпись в авторизацию:
- Кто разрешил этому ключу существовать?
- Что именно ему разрешено — и для кого?
- Когда его полномочия заканчиваются?
- Как это отозвать, если что-то пошло не так?
На все четыре вопроса в Me2eM отвечает один механизм — аттестация. Это родительская подпись, превращающая голый ключ в удостоверение с границами. И вся система делегирования доверия строится без единой записи в серверной базе.
Аттестация — это предложение, а не запись
Классическая система авторизации хранит разрешения в базе: user X has role Y on resource Z. Права существуют, пока жива запись, и зависят от доступности базы при каждой проверке.
Me2eM переворачивает модель. Аттестация — это подписанное родителем утверждение:
«публичный ключ
Kпринадлежит имениNи действует в границах грантаGдо моментаT».
Четыре факта, одна подпись, ноль записей на сервере. Кто угодно может проверить утверждение — имея только публичный ключ родителя. Никто не может его подделать, расширить или продлить.
flowchart LR
P["Родитель"] -->|"подписывает"| A["Аттестация:<br/>ключ K = имя N<br/>в границах гранта G"]
A -->|"предъявляет"| V["Верификатор"]
PK["Публичный ключ родителя"] -->|"проверка подписи"| V
Аттестация — это публичный артефакт: подписанное свидетельство, которое носитель предъявляет верификатору. Она не секрет — в ней нет ничего секретного: публичный ключ, имя, границы. Секрет (приватный ключ субъекта) остаётся у носителя и никогда не покидает его.
Из чего состоит грант
Грант — это границы полномочий. Четыре измерения:
interface AttestationGrant {
audiences?: string[]; // кому субъект может предъявлять сессии
scopes: string[]; // что именно он может делать
maxSessionTtl: number; // максимальный срок одной сессии
subNamePatterns?: string[]; // имена субхэндлов, которые он может создавать
}
Каждое измерение работает по принципу вычитания: то, что не в гранте, — запрещено. Грант ['charge:start'] означает «может только начать зарядку» — а не «может всё, кроме остальных действий». Пустой массив ([]) означает «запрещено всё» — fail-closed по умолчанию.
Дополнительная сила — вложенность. Субхэндл, выпущенный хэндлом, может иметь только подмножество родительских прав:
Родитель: scopes = [charge:start, charge:stop, charge:status], maxTtl = 7200
↓
Ребёнок: scopes = [charge:start], maxTtl = 1800
→ валидно: подмножество, меньше TTL
Ребёнок: scopes = [charge:start, billing:admin], maxTtl = 7200
→ ОТКЛОНЕНО верификатором: billing:admin вне родительского гранта
Вложенность проверяется на верификации, не на создании. Это принципиально: хэндл, который попытается выдать ребёнку больше прав, чем имеет сам, будет отвергнут каждым проверяющим — даже тем, кто никогда не видел родительского гранта. Делегирование становится математически ограниченным, а не политически ограниченным.
Два режима верификации
Один и тот же токен сессии можно проверить двумя способами — они отвечают на один вопрос, но для разной аудитории.
flowchart LR
T["Session Token"] -->|"Mode 1<br/>verifyStateless"| R1["Верификатор владеет<br/>приватным корнем"]
T -->|"Mode 2<br/>verifyAttested"| R2["Верификатор имеет<br/>только публичный корень"]
R1 --> O1["Ограничения субхэндла:<br/>только на создании"]
R2 --> O2["Ограничения субхэндла:<br/>принуждаются на верификации"]
Mode 1 — прямая верификация. Верификатор пересчитывает публичный ключ подписанта из пути в токене, используя экземпляр Identity. Для этого нужен приватный корень — поэтому Mode 1 доступен только владельцу или его доверенной инфраструктуре.
Mode 2 — верификация с аттестационной цепочкой. Верификатор проверяет подпись сессии против ключа, связанного родительской аттестацией, и каждую аттестацию — против подписи её родителя, восходя к публичному корневому ключу. Каждое звено цепи несёт подписанный грант, поэтому верификатор принуждает гранты на верификации: scopes сессии должны укладываться в грант, TTL-потолки и аудитории соблюдаются, дочерние гранты вкладываются в родительские.
Как выбрать. Вопрос один: готов ли верификатор держать приватный корень? Собственная инфраструктура под полным контролем — Mode 1: цепочка не нужна, всё проверяется пересчётом. Любая внешняя сторона — партнёры, клиенты, чужие сервисы — Mode 2: приватный корень не покидает владельца, а гранты принуждаются математически на каждой проверке. Ошибка в сторону Mode 1 дорога: ограничения принуждаются лишь на создании сессии, а отзыва ветки нет вовсе — это не экспромт, который чинится позже.
Живой пример: отдел, сотрудник, подрядчик
Покажем весь механизм на корпоративном сценарии. Это учебная ресторация из USE_CASES.md — там же полные коды всех сценариев.
Иерархия
Identity: Корпорация (seed директора — в сейфе)
└─ Handle: department-engineering ◀── A: аттестация от корня
│ грант: message:*, channel:engineering, maxTtl: 8ч
│ может создавать: worker-*, contractor-*
├─ SubHandle: worker-alice ◀── B: аттестация от HR
└─ SubHandle: contractor-x ◀── B с TTL две недели
Настройка (корень — один раз)
const grantA = {
audiences: ['corp-messenger.internal', 'jira.internal'],
scopes: ['message:send', 'message:receive', 'channel:engineering'],
maxSessionTtl: 28800, // сессии до 8 часов — один рабочий день
subNamePatterns: ['worker-*', 'contractor-*'],
};
const A = await corpIdentity.attestHandle('department-engineering', grantA,
{ ttlSeconds: 365 * 24 * 3600 }); // аттестация живёт год
// HR-система получает deptHandle + A. Корень спит на год:
// любые новые сотрудники, роли и TTL ≤ 8ч покрываются без корня.
HR-система получает deptHandle + аттестацию A. Корень с этого момента спит. Никакой ежедневной операции с корнем нет — аттестация A действует год.
Найм (HR — автономно)
HR не имеет корня. Он работает локально:
const { worker, B } = await hireEmployee('worker-diana', 'Diana Prince', 'Manager');
Внутри hireEmployee — две операции: деривация субхэндла + аттестация субхэндла, подписанная ключом отдела. Никакой сети, никакого корня, никакого ожидания.
И — принципиально — даже скомпрометированная HR-машина не может эскалировать: channel:management вне grantA.scopes, поэтому такая дочерняя аттестация отклоняется каждым верификатором (SCOPE_EXCEEDED на уровне корня). Права выше гранта отдела требуют отдельного хэндла, аттестованного корнем напрямую.
Верификация двумя сервисами
Мессенджер и Jira проверяют независимо, с одним публичным корнем:
// corp-messenger.internal:
const s = await Session.verifyAttested(
token, CORP_ROOT_PUB, [A.token, B.token],
'corp-messenger.internal', revocationChecker,
);
// jira.internal — тот же вызов, другая аудитория
Оба сервиса видят: чей это сотрудник, какие у него права, не отозвана ли его аттестация. Им не нужна БД отделов — вся информация в подписанной цепочке.
Увольнение — правильный способ
Одна операция — отозвать аттестацию сотрудника:
await revocationService.revokeAttestation(B.jti);
Все текущие и будущие сессии worker-alice отклоняются — независимо от TTL. В обоих сервисах одновременно. Из одного хранилища.
Mode 1 не имеет протокольного отзыва хэндлов — интерфейс
RevocationCheckerпринимает только sessionjti. Обходной путь (конвенция именованияjtiс парсингом) существует, но хрупок. Mode 2 делает отзыв ветки первоклассной операцией.
Подрядчики — самоистекающий доступ
const B = await deptHandle.attestSubHandle('contractor-external-xyz', {
audiences: ['corp-messenger.internal'],
scopes: ['message:send', 'message:receive', 'channel:project-alpha'],
maxSessionTtl: 28800,
}, { ttlSeconds: 14 * 24 * 3600 }); // через две недели доступ исчезнет сам
HR не пишет в календарь «удалить доступ подрядчику 14 октября». Аттестация истекает сама — у всех верификаторов одновременно.
Отзыв: одна операция, вся ветка
Каждый уровень цепочки имеет свой jti — и отзыв любого уровня работает каскадно:
Отзыв корневой аттестации станции (A.jti)
→ станция мертва целиком: все коннекторы, все сессии
Отзыв аттестации коннектора (B.jti)
→ мёртв только коннектор; станция и остальные — живы
Верификатор проверяет jti на каждом уровне цепочки — от корня вниз. Ревокация любой аттестации в цепочке делает всю цепь недействительной. Это позволяет отзывать с нужной гранулярностью: всю станцию при компрометации, конкретный разъём при поломке.
Что аттестации дают — и чего не дают
Честная таблица:
| Гарантируется | Не гарантируется |
|---|---|
| Сторонний верификатор без приватного корня | Защита от злоупотребления правами внутри гранта |
| Принуждение грантов офлайн (не нужен сервер) | Обнаружимость повторной аттестации (нужен transparency-лог) |
| Мгновенный отзыв ветки (jti + хранилище) | Отзыв «уже знавшего» — знающий материалы сохраняет их до истечения |
| Вложенность: ребёнок ⊆ родитель (математически) | Ревокация корня без инфраструктуры распределения |
Аттестации не защищают от компрометации корня — корень и так порождает все ключи. Они защищают всё, что ниже корня: от несанкционированного расширения, от незаметной подмены, от увольнения без возврата доступа.
Связь с деревом ключей
Аттестация не создаёт ключей. Она связывает существующий (детерминированно выведенный) ключ с именем и грантом. Возвращаясь к дереву из статьи о деривации:
Seed → Identity → Handle → SubHandle ← дерево (детерминизм)
↓
attestHandle / attestSubHandle ← свидетельство (доверие)
↓
Session (подпись) ← использование
Дерево отвечает «какой ключ у этого имени». Аттестация отвечает «кто разрешил этот ключ и в каких границах». Сессия отвечает «что этот ключ делает сейчас». Три слоя — три вопроса — одна согласованная система.
Что читать дальше
- Продажа доступа: подписанная квитанция как товар — Mode 2 в чистом виде: дроны, спутниковые окна, офлайн-проверка покупки
- Мгновенный отзыв: ревокация в stateless-мире — как устроен отзыв
jtiна каждом уровне цепочки: гранулярность, fail-open/closed, TTL записей - Stateless-сессии: авторизация без базы данных — что внутри токена и какие семь проверок проходит сессия
- Me2eM: идентичность из seed-фразы — фундамент: откуда берётся дерево