Ориентация главы
Все решения безопасности TLS сгущены в двух конфигурационных объектах. Политика (xtlspolicy): какие версии, suites, группы и подписные схемы разрешены — протокольный белый список; negociación главы 84 берёт её за базу; умолчание и есть действующая линия безопасности (2 версии / 9 suites / 4 группы / 14 схем, без слабых алгоритмов), сужение — лишь сменить массив. Верификатор (xtlsverifier): решение о доверии — путь по умолчанию идёт «построение цепи из хранилища доверия + проверка назначения + сопоставление имён» (комбинация примитивов глав 80/81); пользовательские колбэки полностью отдают право решения приложению (пиннинг сертификатов, встроенный приватный CA, аудит-журнал), поверх можно наслаивать policy-колбэк, комбинируя CRL/OCSP. Оба неизменяемы, со подсчётом ссылок и конкурентно разделяемы многими сессиями — «определили раз, цитируем везде». Конфигурация клиента главы 82 обретает здесь полную картину.
85.1引入
Три реальных потребности. Первая: комплаенс-аудит требует от сервиса «только TLS 1.3 и конкретных двух-трёх suites» — нужно одно место правки, действующее на все соединения, а не разрозненные захардкоженные значения по каждой конфигурации клиента/сервера. Вторая: ваш клиент ходит только на собственный бэкенд, поверхность доверия публичных CA слишком велика — хочется зафиксировать конкретный сертификат (pinning); хранилище доверия главы 81 не умеет «одиночный сертификат как якорь + сравнение отпечатков + аудит-журнал». Третья: экспериментальная среда хочет временно добавить «проверку отзыва» (запрос CRL главы 81), не переписывая логику построения цепи.
Три потребности соответствуют двум слоям главы: объект политики ведает протокольной гранью (первая), три расширительных точки верификатора — гранью доверия (вторая и третья). Ключевой дизайн — разделение решения и исполнения: верификатор ведает только «решением о доверии»; подписная проверка протокольного слоя и контроль Finished по-прежнему исполняет XRT, и никакой колбэк их не обходит — вы принимаете «верить или нет», а не «правильный ли протокол».
85.2概念
Объект политики: единственный источник истины протокольных предпочтений
xtlspolicy — заимствующая конфигурация: инициализация по умолчанию указывает на процесса- lifetime-константы только-чтения (нуль выделений); сужение — подменить собственными массивами (жизненный цикл покрывает создание контекста):
Правила проверки (перехват ошибок конфигурации на старте): версии и suites обязаны быть непусты; указатели списков и счётчики согласованы, элементы уникальны и принадлежат встроенным способностям; каждый suite соответствует включённой версии, у каждой версии хотя бы один suite; непустой список подписей обязан годиться хотя бы для одной включённой версии — PKCS#1-схема 1.2 не входит в чисто 1.3-политику. Две границы: политика описывает лишь предпочтения и не обещает, что текущая обрезанная сборка содержит соответствующий бэкенд (фактический путь исполнения генерируется при создании сессии вместе с xrtTlsGroupAvailable и др.); KeySharePolicy может переопределить локальный режим выбора key-share (например XTLS_KEY_SHARE_PREFER_GROUP) — публичный ключ какой группы несёт ClientHello, решает он.
Верификатор: три слоя решения о доверии
xrtTlsVerifierCreate(配置) глубоко копирует опциональное хранилище доверия — после создания исходный store можно освобождать или менять; без пользовательских колбэков обязан быть предоставлен хотя бы один якорь доверия, пустой store — провал на создании (того же корня, что «не проверять не опция» главы 82). Трёхслойная структура решений:
- Путь по умолчанию (
xrtTlsPeerVerify): проверка пути в явное время (построение цепи главы 81) + ролевая проверка назначения (digitalSignatureKeyUsage,serverAuth/clientAuthEKU) + серверная роль сопоставляет запрошенное имя по RFC 9525 (глава 80). Проволочная цепь от листа к корню; якорь из store, присутствия в цепи партнёра не требуется. - Verify-колбэк (полное принятие):
XTLS_VERIFY_ACCEPTпринимает доверие /REJECTотвергает /DEFAULTоткат на неизменяемый store /ERRORструктурная ошибка. Колбэк получает структуру партнёра, уже прошедшую криптографическую проверку (подпись и Finished гарантированы протокольным слоем) — вы решаете только, верить ли. Принятие всё равно не пропускает CertificateVerify и Finished — «принять доверие» не значит «принять протокол». - Policy-колбэк (наслоение проверок): срабатывает после успеха всех проверек по умолчанию, читает
xtlsverifiedpeer(Pathот листа к корню без якоря; все представления заимствованы лишь на время колбэка) — комбинируйте CRL (глава 81), OCSP, CT, пиннинг, корпоративные правила, не строя цепь заново. Возврат false может ставить ошибку — она обёрнется cause дляXTLS_ERROR_VERIFY.
Источник времени инъектируется (xtlsverifytimeproc): путь и присоединённая политика делят одно и то же определённое время — тесты воспроизводимы, кеш определим; без него xrtNow().
Граница: ядро TLS тайно не ходит в сеть
Ядро TLS не загружает молча системные корни, не скачивает CRL, не заводит OCSP и не подаёт CT-запросы — загрузка данных и сеть принадлежат отдельному комбинированному слою. Базовый клиент внезапно не заблокируется на файловом или системном API во время проверки (превентивный дизайн «провала системного слоя» главы 77). Нужен CRL — зовите API главы 81 сами в Policy-колбэке; мягкая или жёсткая неудача — ваше политическое решение, верификатор сохраняет однозначный результат для вашего выбора.
Форма сборки: политика + верификатор + сессия
Полная сборка клиента (декомпозиционный вид образца главы 82):
85.3示例
Первая полная программа: базовая линия политики и проверка сужения
Программа ниже из examples/tls/policy/main.c — печать базы по умолчанию и перекрытие режима key-share:
#include <xrt/tls.h> #include <stdio.h> */ /* 展示默认策略和调用方覆盖本地 key-share 选择模式。 */ int main(void) { xtlspolicy Policy; xrtTlsPolicyInit(&Policy); Policy.KeySharePolicy = XTLS_KEY_SHARE_PREFER_GROUP; if ( !xrtTlsPolicyValid(&Policy) ) { return 1; } printf( "versions=%zu ciphers=%zu groups=%zu signatures=%zu\n", Policy.VersionCount, Policy.CipherCount, Policy.GroupCount, Policy.SignatureCount ); return 0; }
Что только что произошло. (1) База по умолчанию — вывод как доказательство: 2 версии (1.2/1.3), 9 suites, 4 именованные группы, 14 подписных схем — без слабых алгоритмов; вот линия «безопасно и без конфигурации». (2) KeySharePolicy = XTLS_KEY_SHARE_PREFER_GROUP перекрывает локальный выбор key-share — ClientHello предпочитает нести публичный ключ предпочтительной группы, сокращая круги HRR (глава 84). (3) PolicyValid — привратник конфигурации: замените Versions на 1.3-only, не тронув Ciphers (с 1.2-специальными suite) — он отвергнет; сужение образца (только 1.3 + два suite ChaCha20/AES-GCM) — смените массив и счётчик разом и пройдите проверку ещё раз. Снимок политики после привязки контекстом больше не модифицируется — одна конфигурация обслуживает разные аппаратные способности (влитие бэкендов решается при создании сессии).
Вторая полная программа: пользовательский верификатор принимает доверие
Вторая программа из examples/tls/verify/main.c — колбэк отдаёт решение по сертификатам приложению:
#include <xrt/tls_verify.h> #include <stdio.h> */ /* 示例回调以应用自有的证书固定策略接管信任决策。 */ static xtlsverifydecision exampleTlsVerify( const xtlspeer* pPeer, ptr pContext ) { (void)pContext; return pPeer->CertificateCount != 0 ? XTLS_VERIFY_ACCEPT : XTLS_VERIFY_REJECT; } /* 创建可供多个 TLS 客户端共享的自定义验证器。 */ int main(void) { xtlsverifierconfig Config; xtlsverifier* pVerifier; xrtTlsVerifierConfigInit(&Config); Config.Verify = exampleTlsVerify; pVerifier = xrtTlsVerifierCreate(&Config); if ( pVerifier == NULL ) { return 1; } printf("TLS verifier is ready\n"); xrtTlsVerifierRelease(pVerifier); return 0; }
Что только что произошло. (1) VerifyConfig.Verify = exampleTlsVerify вливает колбэк — этой форме store не нужен (решение целиком в колбэке). (2) Колбэк получает xtlspeer (информация о партнёре: представления цепи сертификатов, параметры negociación) и возвращает перечисление решения — здесь минимальная политика «есть сертификат — принимаем» (только демонстрация интерфейса, не копируемая продакшен-стратегия). (3) Верификатор разделяем многими клиентами — создали раз, влили в каждый ClientConfig.Verifier, свою ссылку отпустили Release. Формы продакшена: пиннинг сертификатов (сравнение SHA-256 листа с предустановленными отпечатками, несовпадение REJECT), встроенный приватный CA (store только со своим корнем + откат DEFAULT), аудит-журнал (Policy-колбэк записывает партнёра и путь каждой проверки, затем пропуск) — в трёхслойной структуре решений у каждой своё место.
85.4契约
- Проверка политики: непустые версии и suites; согласованность указателей/счётчиков, уникальность элементов, принадлежность встроенным способностям; двустороннее покрытие suite↔версия; список подписей годится хотя бы для одной включённой версии; проверка не модифицирует вход, нуль выделений.
- Границы политики: описывает предпочтения, не обещает влитие бэкендов; создание сессии генерирует фактический путь вместе с
GroupAvailable/AEAD/идентичностью/бэкендами проверки; исходный снимок политики неизменен. - KeySharePolicy: локальный режим выбора key-share (например
XTLS_KEY_SHARE_PREFER_GROUP) — влияет на группу в ClientHello, сокращает HRR. - Принудительность верификатора: без Verify-колбэка обязателен хотя бы один якорь доверия; пустой store — провал на создании; после глубокого копирования исходный store можно освобождать/менять.
- Трёхслойность решений: путь по умолчанию (цепь + назначение + имя) / Verify-колбэк (полное принятие, четырёхзначное решение) / Policy-колбэк (наслоение после полного успеха, представления заимствованы лишь на время колбэка).
- Протокол не обходится: решение о принятии не пропускает CertificateVerify и Finished — подпись и целостность гарантированы протокольным слоем.
- Инъекция времени:
xtlsverifytimeprocдаёт пути и политике одно определённое время; без негоxrtNow(). - Конкурентность: верификатор неизменяем, подсчёт ссылок; Verify/Policy/Time-колбэки вызываются конкурентно — не полагаться на локальные для потока изменяемые разделения.
- Без неявной сети: не грузить системные корни, не качать CRL, не слать OCSP, не спрашивать CT — дело комбинированного слоя и policy-колбэка.
- Обёртка ошибок: колбэк false с ошибкой →
XTLS_ERROR_VERIFY+ cause; без ошибки → явный отказXERR_PERMISSION.
Типичные ошибки
Симптом: политика назначила suite ChaCha20, а сборка прошивки не влита с бэкендом ChaCha20 — negociación в рантайме не находит, соединение падает или деградирует в другой suite, вопреки ожиданию конфигурации.
Причина: политика — предпочтение, не обещание: проверка смотрит лишь «принадлежит встроенным способностям сессии», не влитие бэкендов текущей сборки. Фактический путь исполнения генерируется при создании сессии с GroupAvailable и др.
/* предположили: назначили - работает */ Policy.Ciphers = ChaChaFirst; Policy.CipherCount = 1; /* в сборке --feature нет CHACHA20: в целевой среде весь negociación провален */
/* сверить способности до развёртывания: группы через GroupAvailable, suites по манифесту сборки */ if ( !xrtTlsGroupAvailable(XTLS_GROUP_X25519) ) { /* доложить о разрыве способностей на старте, не провалом соединения в бою */ } /* в CI гонять negociación-смок целевой сборки - проверять политику и способности вместе */
Симптом: колбэк сам проверяет подписи и сравнивает Finished — код раздут и легко ошибиться; либо наоборот, решив «приняли проверку», протокольный контроль сочли пропущенным и потеряли бдительность.
Причина: не понято «разделение решения и исполнения»: колбэк получает структуру партнёра, уже прошедшую криптопроверку (подпись и Finished гарантированы протокольным слоем и не обходятся); ваша обязанность — «верить или нет» (цепь доверия, пиннинг, аудит), а не «перепроверить протокол».
static xtlsverifydecision myVerify(const xtlspeer* pPeer, ptr pCtx) { /* повторная проверка подписи: дублирование, ошибкоопасно и уже гарантировано слоем протокола */ if ( !self_verify_signature(pPeer) ) { return XTLS_VERIFY_REJECT; } return XTLS_VERIFY_ACCEPT; }
static xtlsverifydecision myVerify(const xtlspeer* pPeer, ptr pCtx) { /* только решение о доверии: пример - пиннинг сертификата */ if ( !pin_matches(pPeer /* 叶证书摘要比对 */ ) ) { return XTLS_VERIFY_REJECT; } return XTLS_VERIFY_ACCEPT; /* протокольную проверку гарантирует XRT */ }
Симптом: доступ к представлениям xtlsverifiedpeer.Path после возврата колбэка — падение или мусор.
Причина: все представления Policy-колбэка (путь, якорь, сертификаты) заимствованы только на время колбэка — возврат инвалидирует их. Для долгого хранения обязательна глубокая копия (копирование байтов DER).
static bool myPolicy(const xtlsverifiedpeer* pPeer, ptr pCtx) { g_SavedCert = pPeer->Path[0]; /* сохранили заимствованное представление: возврат - и оно висит */ return true; }
static bool myPolicy(const xtlsverifiedpeer* pPeer, ptr pCtx) { audit_log(pPeer); /* чтение и запись завершить внутри колбэка */ /* для долгого хранения: скопировать байты DER здесь, жизненным циклом ведать самому */ return true; }
Практические задания
Прогоните образец policy и зафиксируйте базу по умолчанию; затем сузьте до «только TLS 1.3 + два suite» (замена массивов + PolicyValid) и напечатайте новые счётчики. Критерии приёмки: после сужения Valid проходит; подсунув 1.2-специальную подписную схему в 1.3-only политику, получаете явный отказ Valid — сообщение указывает проблемный список.
Реализуйте pinning-верификатор: Verify-колбэк сравнивает SHA-256-дайджест листового сертификата с предустановленным массивом отпечатков (потоковый дайджест главы 74 + ConstTimeEqual); попадание ACCEPT, промах REJECT. Проверьте локальным вариантом dial главы 82 на самоподписанном сертификате. Критерии приёмки: верный сертификат проходит, смена сертификата немедленно отвергается; сравнение отпечатков постоянного времени (объясните почему).
Соберите верификатор «системное доверие + CRL-проверка в Policy-колбэке»: путь по умолчанию строит цепь (StoreSystem), Policy-колбэк прогоняет для листа CrlValidate + CrlCheck главы 81; вердикт REVOKED — отказ, DONE — мягкая неудача (журнал и пропуск), GOOD — пропуск. Сгенерируйте тестовым CA отозванный сертификат для проверки. Критерии приёмки: три вывода отзыва каждый идёт своим путём; CRL читается только при срабатывании колбэка (проверка «без неявной сети» — GOOD/REVOKED определимы без единого хоста); после инъекции фиксированного времени результат воспроизводим.
Итоги
| Тема | Кратко |
|---|---|
| Объект политики | четыре списка версии/suites/группы/схемы + KeySharePolicy; умолчание — линия безопасности (2/9/4/14) |
| Проверка политики | PolicyValid: непустота, уникальность, двустороннее покрытие suite↔версия; схема 1.2 не входит в чистую 1.3 |
| Границы политики | предпочтение ≠ способность; влитие бэкендов решается при создании сессии; снимок неизменен и разделяем кроссплатформенно |
| Создание верификатора | глубокая копия store (затем можно освобождать); без колбэка обязателен якорь; пустой store — отказ |
| Три слоя решений | путь по умолчанию (цепь + назначение + имя) / Verify-колбэк (четырёхзначное принятие) / Policy-колбэк (наслоение CRL/OCSP/пиннинга) |
| Разделение решения и исполнения | колбэк ведает лишь доверием; CertificateVerify/Finished гарантированы протокольным слоем и не обходятся |
| Четырёхзначное решение | ACCEPT принять / REJECT отвергнуть / DEFAULT откат на store / ERROR со структурной ошибкой |
| Инъекция времени | timeproc — единое определённое время (воспроизводимость тестов/кеши); без него xrtNow() |
| Заимствованная граница | представления Policy-колбэка действуют только внутри колбэка; долгое хранение — глубокая копия |
| Без неявной сети | автоматической загрузки системных корней/CRL/OCSP/CT нет — выборка и мягкая неудача ваши |
📖 Справочники API в этой главе:tls (201 записей)tls_verify (17 записей)x509 (140 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки