Кратко

  • DPoP меняет одно свойство bearer-токена: одной строки токена уже недостаточно. Предъявитель должен создать новое доказательство закрытым ключом, публичный отпечаток которого связан с токеном.
  • Доказательство связывает HTTP-метод, целевой URI без query и fragment, время создания, уникальный идентификатор, хеш токена и при необходимости серверный nonce. Оно не подписывает весь запрос и не разрешает деловое действие.
  • Resource server по-прежнему обязан проверить issuer, audience, срок, состояние и привилегии токена, согласовать replay-state, восстановить правильный внешний URI и авторизовать субъект, клиент, ресурс, действие и текущее состояние объекта.

Кража, при которой одной строки уже мало

Инженер обнаруживает OAuth access token в диагностическом архиве. В Bearer-модели эта строка и есть способность: любой, кто её получил и может обратиться к принимающему серверу, пользуется представленной властью до истечения срока или отзыва.

Но утёкший токен привязан посредством DPoP. Злоумышленник копирует его в другой клиент и отправляет запрос. Resource server требует не только токен в схеме DPoP, но и отдельно подписанное доказательство. Закрытого ключа, соответствующего сохранённому thumbprint, у атакующего нет. Доказательство с другим ключом не совпадёт; старое перехваченное доказательство будет отвергнуто по методу, URI, времени, nonce или записи повтора.

Это существенный выигрыш: похищенное значение токена без ключа или доступного интерфейса подписи больше не образует полноценное удостоверение вне исходной среды.

Затем запрос делает настоящий клиент. Подпись верна, htm и htu совпадают, ath соответствует токену, доказательство свежее, jti ещё не встречался. И всё же сервер отказывает. Токен мог быть выпущен для другого audience, содержать лишь чтение, относиться к уже заблокированной учётной записи либо пытаться изменить объект после завершения одноразовой операции.

DPoP здесь не сломался. Ограничение отправителя ответило, использован ли связанный ключ для данного запроса. Авторизация ответила, вправе ли этот контекст вызвать данный результат сейчас. Положительный первый ответ и отрицательный второй — признак сохранённых границ, а не противоречие.

От владения строкой к подтверждению ключа

RFC 6750 определяет bearer-свойство прямо: обладатель токена может использовать его без криптографического подтверждения владения ключом. TLS, защищённое хранение, короткий срок и ограничение audience снижают вероятность и цену утечки, но не устраняют переносимость скопированного значения внутри поверхности приёма.

RFC 9449 добавляет доказательство на прикладном уровне. Клиент выбирает асимметричную пару ключей и на token endpoint посылает подписанный DPoP JWT. Authorization server может связать выданный access или refresh token с отпечатком JWK публичного ключа. При обращении к защищённому ресурсу клиент предъявляет токен и новое доказательство, а сервер проверяет совпадение ключей.

RFC 9700 рекомендует sender-constrained tokens, audience restriction и least privilege отдельно. Это принципиально: привязка к ключу не делает токен для сервиса A годным в сервисе B, не превращает read в write, не создаёт согласие и не замораживает полномочие, изменившееся после выдачи.

JWK thumbprint — детерминированный SHA-256 digest обязательных канонических членов публичного ключа. Он точно обозначает ключевой материал, но не человека, компанию, исправность устройства, юридическое намерение или мандат. Криптографическая точность не разрешает незаметно повысить технический идентификатор до личности.

Что именно находится в доказательстве

DPoP proof — это JWT, подписанный как JWS. Protected header явно содержит typ: dpop+jwt, разрешённый локальной политикой асимметричный alg и публичный jwk без закрытого материала. Получатель тем самым выбирает профиль проверки DPoP, а не отправляет любой JWT в универсальный путь «подпись верна». RFC 8725 формулирует общий принцип: типы, алгоритмы, issuer, audience и правила валидации задаёт приложение.

Базовый payload содержит htm — HTTP-метод, htu — целевой URI без query и fragment, iat — время создания и jti — идентификатор доказательства, созданный с пренебрежимо малой вероятностью коллизии.

При access token доказательство также содержит ath, SHA-256 от точного значения предъявленного токена. Сервер вычисляет его заново. Перехваченное рядом с одним токеном доказательство нельзя переставить к другому, даже если оба выпущены под один proof key.

Если сервер выдал challenge DPoP-Nonce, следующий proof несёт этот nonce. Свежая непредсказуемая величина уменьшает ценность заранее заготовленного набора подписей и показывает, что возможность подписи ответила на недавно выбранное сервером значение.

Поля не выполняют проверку сами. Получатель должен отвергнуть несколько DPoP headers, повреждённый JWT, отсутствие обязательных claims, неверный тип, запрещённый алгоритм, плохую подпись и JWK с приватными данными. Затем он сравнивает метод и URI, применяет временное окно, пересчитывает ath, сопоставляет связанный с токеном отпечаток и реализует собственное правило nonce.

Три связи нельзя свести к одному флагу

Первая — token-to-key binding. Authorization server записывает JWK thumbprint, обычно в cnf.jkt. Resource server сравнивает его с отпечатком публичного ключа, проверившего текущий proof.

Вторая — proof-to-token binding. ath связывает доказательство с точным access-token этого запроса. Это не даёт заменить токен, однако ничего не утверждает об audience, scope, сроке или отзыве.

Третья может начаться до выдачи токена. Необязательный параметр dpop_jkt связывает authorization code с предполагаемым proof key. Без него атакующий, получивший код и прочие материалы обмена, способен попытаться предъявить код со своим ключом и получить технически правильно DPoP-привязанный токен — привязанный к злоумышленнику. Такая связь дополняет PKCE, client authentication и разрешение resource owner, а не заменяет их.

Система может правильно реализовать одну связь и пропустить другую. Для аудита нужны название стадии, сравниваемые значения и результат, а не общий индикатор «PoP включён».

Доказанная часть запроса меньше деловой команды

htm мешает переиграть proof для GET как DELETE; htu мешает перенести его на другую цель. Но в RFC 9449 htu не включает query и fragment. Базовый DPoP также не подписывает тело и произвольные HTTP headers.

Если POST /transfer?account=A и POST /transfer?account=B дают одинаковый htu, proof не различает счета. Если в JSON меняется сумма или получатель, подпись автоматически не начинает покрывать эти данные.

Это не скрытый изъян, а намеренное разделение функций. DPoP ограничивает отправителя; он не заменяет HTTP Message Signatures, transaction authorization или business idempotency. RFC 9110 определяет семантику HTTP, а приложение отвечает за смысл query, body и текущего состояния и должно разрешать исполняемое последствие.

Прокси делают границу особенно заметной. Клиент доказывает внешние scheme, authority и path, которые видит. Gateway завершает TLS, переписывает путь и передаёт внутренний host. Если backend восстанавливает htu с неверной стороны, он откажет правильным клиентам; если начинает принимать рыхлый набор вариантов, расширит поверхность replay. Внешний request context, доверенная forwarding chain и нормализация требуют явного владельца.

Свежесть — это распределённое состояние

iat и jti дают материал для replay detection, но не отклоняют повтор самостоятельно. Сервер выбирает допустимый возраст, clock skew, replay key, срок хранения и топологию обмена seen-state.

Если один регион записал jti, а другой ещё не получил запись, один proof может пройти дважды. Если check и write неатомарны, параллельные копии обе увидят «не использован». Если nonce постоянен, слишком долговечен или разделяется слишком широко, он превращается из текущего challenge в церемонию.

Nonce от authorization server и resource server не взаимозаменяемы. Клиент с несколькими серверами обязан хранить вызовы по издателю, иначе поле свежести создаёт ошибки между границами.

Отклонение повторного proof не гарантирует exactly-once business execution. Клиент может создать два свежих proofs для двух retries. Оба криптографически уникальны. Если первый платёж зафиксирован, но ответ потерян, поведение второго определяют idempotency, commit record и reconciliation, а не DPoP replay-store.

Владение ключом не равно личности или согласию

Публичный JWK показывает, каким ключом проверена подпись. Он не сообщает, кто создал ключ, кто управляет процессом, принадлежит ли ключ зарегистрированному клиенту и одобрил ли пользователь именно это действие. Confidential-client authentication может сосуществовать с DPoP. Токен даёт контекст от issuer, proof — ограничение отправителя, приложение — окончательное решение.

В браузере различие критично. RFC 10017 объясняет, как non-extractable key мешает скопировать токен и ключевой материал для поздней работы в другой среде. Но вредоносный JavaScript, уже исполняющийся с правами origin приложения, способен вызывать доступный интерфейс подписи, отправлять запросы в живой сессии либо запускать новый authorization flow. Неизвлекаемый ключ всё ещё может использовать неправильный код в правильном месте.

Если атакующий получил и токен, и ключ либо устойчиво контролирует signing interface, sender constraint подорван. Hardware-backed key улучшает хранение, но не превращает скомпрометированный клиент в доверенного principal.

Свидетельства по всему пути принятия

IANA зарегистрировала DPoP и DPoP-Nonce как постоянные HTTP fields и ведёт связанные OAuth parameters. Общая лексика даёт совместимость, но не доказывает, что конкретное развёртывание сравнивает один URI, координирует replay и сохраняет решение ресурса.

След должен связывать выдачу с последствием без записи raw token и private key: безопасный хеш proof, thumbprint, token type, результат ath, нормализованные внешний и внутренний URI, возраст proof, issuer nonce, итог replay-store, issuer и audience токена, решение scope, версия resource policy и финальный commit identifier.

Отрицательные тесты пересекают компоненты: скопированный токен без ключа; правильный ключ с другим токеном; старый proof; повторный jti; неверный метод; rewrite между внешним и внутренним адресом; отсутствующий или чужой nonce; wrong audience; insufficient scope; заблокированный субъект; уже завершённая одноразовая операция. Объяснимая точка каждого отказа сильнее зелёного значка DPoP.