Ориентация главы

Все решения безопасности 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-константы только-чтения (нуль выделений); сужение — подменить собственными массивами (жизненный цикл покрывает создание контекста):

1База по умолчаниюxrtTlsPolicyInit -> только-читающие константы (2 версии / 9 suites / 4 группы / 14 схем, все действующие)
2Сужениезаменить указатели массивов и счётчики Versions/Ciphers/Groups/Signatures
3Самосогласованная проверкаxrtTlsPolicyValid - пустые множества / рассогласование версий и suites / схема 1.2 в 1.3 - отказ
4РазделениеContext/конфигурации клиента/сервера делят один и тот же снимок политики

Правила проверки (перехват ошибок конфигурации на старте): версии и 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) + ролевая проверка назначения (digitalSignature KeyUsage, serverAuth/clientAuth EKU) + серверная роль сопоставляет запрошенное имя по 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):

1Политикаxtlspolicy сужена до целевой базы -> проверка PolicyValid -> навесить на конфигурацию разделяемого контекста
2ДовериеStoreSystem (или явный Add) -> VerifierCreate глубоко копирует -> Store можно освобождать
3Расширениеколбэки Verify/Policy/Time вливаются по надобности (пиннинг, CRL, определённое время)
4СессияClientConfig{Verifier, ServerName, Protocols} -> сессия держит ссылку -> свою можно Release

85.3示例

Первая полная программа: базовая линия политики и проверка сужения

Программа ниже из examples/tls/policy/main.c — печать базы по умолчанию и перекрытие режима key-share:

examples/tls/policy/main.c源码 ↗
#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/tls/policy/main.c -lws2_32 -liphlpapiversions=2 ciphers=9 groups=4 signatures=14

Что только что произошло. (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 — колбэк отдаёт решение по сертификатам приложению:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/tls/verify/main.c -lws2_32 -liphlpapiTLS verifier is ready

Что только что произошло. (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.

Типичные ошибки

Ловушка 1: политика как заявление способностей (не влито — а «настроено»):Ловушка 1: политика как заявление способностей (не влито — а «настроено»)

Симптом: политика назначила 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-смок целевой сборки - проверять политику и способности вместе */
Ловушка 2: повторение криптопроверок в Verify-колбэке:Ловушка 2: повторение криптопроверок в Verify-колбэке

Симптом: колбэк сам проверяет подписи и сравнивает 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 */
}
Ловушка 3: удержание заимствованных представлений из Policy-колбэка:Ловушка 3: удержание заимствованных представлений из Policy-колбэка

Симптом: доступ к представлениям 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

Соберите верификатор «системное доверие + 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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки