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

Глава 80 закончилась на «проверка подписи прошла ≠ доверенно» — эта глава добавляет недостающий третий вопрос. Три компонента выходят по порядку: путь сертификации (эстафета подписей «листовой сертификат → промежуточный CA → якорь доверия»; xrtX509PathValidate строго проверяет каждое звено, xrtX509PathBuild автоматически находит цепочку из неупорядоченных кандидатов); хранилище доверия (владеющее множество якорей — три формы загрузки: явное добавление, PEM-файл, системные корни; семейство xrtX509StoreCreate/Add/AddPem/AddSystem); проверка отзыва по CRL (двухслойный дизайн: xrtX509CrlValidate проверяет раз — xrtX509CrlCheck легко спрашивает N раз). Вместе три части составляют полную клиентскую проверку PKI — проверка сертификатов TLS-клиентом главы 82 и есть прямая комбинация этих компонентов. Глава заодно прочерчивает то, чем этот слой не занимается: сетевую выборку, кеширование, политику мягкой неудачи — это верх; API политики сохраняет однозначный результат, оставляя выбор правил выборки и отказа вам.

81.1引入

Вернитесь к атакующему главы 80: он самоподписывает сертификат, CN/SAN несут целевой домен, подпись собственной парой — проверка подписи проходит, идентичность проходит. Ему не хватает ровно одного: его публичного ключа нет в ваших якорях доверия. Якорь (anchor) — корневой сертификат, заранее признанный и локально сохранённый; ОС и браузеры предустанавливают больше сотни корней CA, а интранет вашей компании мог добавить и свой. Суть проверки: можно ли от сертификата перед глазами,вдоль линии «кто чью подпись ставил», дойти до какого-то локального якоря.

Цепочки реального мира почти всегда с промежуточными слоями: 网站证书 ← 中间CA ← 根证书 (сертификат сайта ← промежуточный CA ← корень). При рукопожатии TLS партнёр присылает лист и промежуточные вместе (порядок не гарантирован); корень не присылает — корень у вас локально. Итак, полный алгоритм проверки: разобрать все сертификаты → от листа автоматически построить цепочку до некоторого якоря → проверить подпись и ограничения каждого звена (действительно ли этот промежуточный CA вправе подписывать сертификаты? не превышен ли потолок глубины пути? разрешают ли ограничения имён?) → всё пройдено — доверяемо. И этого мало: всесторонне законный сертификат мог быть отозван издателем (приватный ключ утёк, домен перепродан) — последний вопрос берёт CRL (список отозванных) издателя и ищет серийный номер. Три образца главы соответствуют трём шагам «построить и проверить цепь, загрузить якоря, спросить отзыв».

81.2概念

Путь сертификации: эстафета подписей и её правила

Путь сертификации — массив сертификатов «цель впереди, промежуточные CA приближаются к якорю». Якорь доверия — вход проверки, в путь не входит — xrtX509Anchor(&根证书, &锚) извлекает из доверенного сертификата Subject, публичный ключ и опциональные NameConstraints; повторное появление того же якорного сертификата в пути отвергается (защита от «сунуть присланный партнёром корень в путь под видом якоря»).

xrtX509PathValidate(路径, 数量, &锚, &配置) проверяет каждое звено в точно заданное конфигурацией время; список отказов: повторные сертификаты, разрыв цепочки издателей, провал подписи, необработанные critical-расширения; промежуточный CA обязан иметь непустой Subject и critical BasicConstraints(cA=TRUE); при наличии KeyUsage тот обязан разрешать keyCertSign, а при pathLenConstraint KeyUsage обязателен явно; NameConstraints пересекаются послойно и применяются к цели и ко всем не-self-issued промежуточным под ней; ExtendedKeyUsage промежуточного CA, если присутствует, обязан разрешать запрошенное назначение. Время проверки задаётся вызывающим явно (config.Time) — тесты воспроизводимы, кеш определим, повтор проверяем; в этом размежевание с дизайном «неявно взять текущее время». X509_PATH_REQUIRE_KEY_USAGE/PURPOSE могут потребовать у целевого сертификата явного объявления назначения.

PathBuild: автоматический поиск цепочки из кучи сертификатов

xrtX509PathValidate требует уже выстроенного пути; xrtX509PathBuild(目标, &候选源, &配置, 输出路径, 容量, &结果) принимает неупорядоченных кандидатов и ищет автоматически: на каждом уровне сперва пытается напрямую завершиться в якорь, затем по порядку кандидатов фильтрует Issuer/Subject и AKI/SKI (xrtX509IssuerMatch), детерминированный поиск в глубину с возвратом; найденный полный путь всегда отдаётся тому же строгому PathValidate — у построителя цепи нет люфта послаблений. Выходной путь никогда не содержит якорного сертификата; нехватка ёмкости возвращает XERR_RANGE, отсутствие пути XERR_NOT_FOUND, причина неудачи сохраняется в цепочке X509_ERROR_PATH_BUILD. При ёмкости ≤16 поиск без кучевых выделений. Это стандартная поза TLS-клиента после получения цепочки партнёра.

Хранилище доверия: дом якорей

xx509store — владеющее множество якорей доверия; три формы загрузки комбинируются по надобности:

  • По одной: xrtX509StoreAdd(库, DER, 长度) копирует и строго разбирает — в хранилище хранится ровно один DER на запись, представления заимствуют его; полностью идентичный повторный сертификат возвращает X509_DONE (идемпотентно).
  • PEM-текст: xrtX509StoreAddPem обходит все блоки текста и импортирует только блоки с ярлыком CERTIFICATE — транзакционно: неудача любого блока откатывает всё, добавленное в этот раз.
  • Файл: xrtX509StoreAddFile (отдельная обрезка) автоматически распознаёт одиночный DER или много-блочный PEM.
  • Системные корни: xrtX509StoreSystem() одним движением даёт независимый снимок — Windows перечисляет логическое хранилище ROOT, macOS читает keychain, Unix читает SSL_CERT_FILE/DIR или ходовой bundle. Снимок независим от системы (последующие системные изменения не трогают построенное хранилище), настроек платформы не меняет, частичные результаты молча не принимает (неудача перечисления — откат целиком). Пр default-база доверия HTTPS-клиента и есть он.

xrtX509StoreSource(库, 外部中间证书数组, 数量, &候选源) комбинирует «якоря внутри хранилища + присланные партнёром промежуточные» во вход PathBuild — хранилище источник якорей, материал рукопожатия кандидат, каждый на своём месте. Одно важное правило инвалидации: продолжение записи может сдвинуть внутренние структуры, обнуляя существующие указатели и уже построенный xx509pathsource — загрузили всё, потом строите источник; построив источник, больше не пишете. Само хранилище не решает за вас «стоит ли доверять»: явное добавление и есть присвоение статуса якоря.

CRL: проверить раз, спросить N раз

Проверка отзыва двухслойна — под две частоты употребления. Слой тяжёлой проверки xrtX509CrlValidate(CRL, 签发者证书, 配置, &有效视图): проверяет подпись CRL, совпадение Issuer, окно времени, назначение cRLSign издателя, AKI, CRL Number, unknown critical — выдаёт заимствованное представление xx509crlvalid (CRL и издатель обязаны оставаться живы). Слой лёгких запросов xrtX509CrlCheck(&有效视图, 证书, &结论): лишь спрашивает, числится ли серийный номер в списке отзыва — «проверить раз, спрашивать многократно» и есть его проектная сцена (CRL одного CA проверили раз — опрашиваем целую пачку сертификатов). Для одноразового пути есть комбинированный вход xrtX509CrlStatus.

Три состояния запроса надо понимать точно: X509_ERROR — ошибка входа/протокола; X509_DONE — этот CRL неприменим к сертификату (или delta-CRL без новых записей) — это не «не отозван»; X509_VALUE единственный публикует вердикт (X509_REVOCATION_GOOD/REVOKED и др.). Есть тонкий подводный камень: у reason-segmented CRL GOOD доказывает лишь неотозванность причин, покрытых CoveredReasons — несколько сегментированных CRL следует агрегировать поочерёдно xrtX509RevocationUpdate; окончательный вердикт есть лишь при полном покрытии множества или подтверждении отзыва любым фрагментом. Комбинация complete/delta — через xrtX509CrlSetInit/Check. Чем этот слой не занимается: сетевой выборкой CRL, кешированием, мягкой неудачей (пропускать ли при недоступности CRL) — решения верхнего уровня; API политики сохраняет однозначный результат именно затем, чтобы право выбора осталось за вами.

Сборка: форма полной клиентской проверки

1ЗагрузкаStoreSystem (или явный Add) строит хранилище якорей, материал рукопожатия разбирается в кандидаты
2Построение цепиStoreSource комбинирует источник -> PathBuild находит путь до якоря
3Проверка цепиPathValidate уже исполнен внутри Build (время/ограничения/подпись по каждому звену)
4ИдентичностьMatchHost сопоставляет хост (глава 80)
5ОтзывCrlValidate + CrlCheck ищут серийный номер (либо верхняя политика решает мягкую неудачу)

Глава 82 покажет TLS-клиента, инкапсулировавшего эти шаги в конфигурацию контекста — но поведенческая граница каждого шага определена именно здесь.

81.3示例

Первая полная программа: автоматическое построение цепочки из неупорядоченных кандидатов

Программа ниже из examples/x509/path_build/main.c — три сертификата (лист/промежуточный/корень) разбираются, и из «кучи кандидатов + одного якоря» автоматически строится и проверяется путь:

examples/x509/path_build/main.c源码 ↗
#include <xrt.h>

#include "../../../tests/fixtures/x509_path_legacy.h"

#include <stdio.h>
#include <string.h>





















 */


/* 从无序候选证书中构建并验证一条到独立信任锚的认证路径。 */
int main(void)
{
	xx509cert Leaf;
	xx509cert Intermediate;
	xx509cert Root;
	xx509anchor Anchor;
	xx509pathsource Source;
	xx509pathconfig Config;
	xx509pathresult Result;
	const xx509cert* Issuers[2];
	const xx509cert* Path[3];

	if ( !xrtX509Parse(
		X509_PATH_LEAF, sizeof(X509_PATH_LEAF), &Leaf
	) || !xrtX509Parse(
		X509_PATH_INTERMEDIATE, sizeof(X509_PATH_INTERMEDIATE), &Intermediate
	) || !xrtX509Parse(
		X509_PATH_ROOT, sizeof(X509_PATH_ROOT), &Root
	) || !xrtX509Anchor(&Root, &Anchor) ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		return 1;
	}
	memset(&Config, 0, sizeof(Config));
	Config.Time = Leaf.NotBefore;
	Issuers[0] = &Root;
	Issuers[1] = &Intermediate;
	Source.Issuers = Issuers;
	Source.IssuerCount = 2u;
	Source.Anchors = &Anchor;
	Source.AnchorCount = 1u;
	if ( !xrtX509PathBuild(
		&Leaf, &Source, &Config, Path, 3u, &Result
	) ) {
		fprintf(stderr, "%s\n", xrtErrorMessage(xrtGetError()));
		return 1;
	}
	printf("certificate path contains %zu certificate(s)\n", Result.Count);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -I . -include xrt.h impl.c examples/x509/path_build/main.c -lws2_32 -liphlpapicertificate path contains 2 certificate(s)

Что только что произошло. (1) Три сертификата каждый xrtX509Parse в представление; только корень переведён xrtX509Anchor в якорь — якорь происходит из локального суждения (здесь задан вручную); это точка впрыска «доверия». (2) Источник кандидатов xx509pathsource перечисляет и корень, и промежуточный как Issuers, якорь отдельно — заметьте: корень и в кандидатах, и в якорях; Build на каждом уровне сперва пробует завершение в якорь, затем фильтрует кандидатов, роли не смешиваются. (3) PathBuild выводит 2 — путь содержит лист и промежуточный CA, якорь не считается; выходной путь уже прошёл все поуровневые проверки (подпись, время явно задано Config.Time = Leaf.NotBefore, BasicConstraints, KeyUsage). (4) Выходной буфер Path предоставляет вызывающий; ёмкость 3 заодно ограничивает максимальную глубину — при построении цепи из недоверенного материала заниженная ёмкость есть защитная поза (при большом пуле кандидатов это ограничение глубины/ширины). Сверьте разделение труда: образец examples/x509/path демонстрирует прямую форму — вызывающий сам выстраивает цепь и зовёт PathValidate; оба входа делят один строгий верификатор.

Вторая полная программа: загрузка системного хранилища доверия одним движением

Вторая программа из examples/x509/store_system/main.c — одна строка даёт независимый снимок якорей текущей платформы:

examples/x509/store_system/main.c源码 ↗
#include <xrt.h>

#include <stdio.h>



















 */


/* 创建当前平台的独立系统信任库快照。 */
int main(void)
{
	xx509store* pStore = xrtX509StoreSystem();

	if ( pStore == NULL ) {
		return 1;
	}
	printf("system anchors=%llu\n",
		(unsigned long long)xrtX509StoreCount(pStore));
	xrtX509StoreFree(pStore);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/x509/store_system/main.c -lws2_32 -liphlpapisystem anchors=33

Что только что произошло. (1) xrtX509StoreSystem() возвращает независимый снимок — число якорей меняется с количеством корней платформы (здесь 33; у вас, скорее всего, другое); снимок не пользуется глобальным кешем, и последующие системные добавления/удаления корней не трогают построенное хранилище. (2) После пересчёта StoreFree освобождает — снимок владеющий объект, та же форма «одно выделение, возврат целиком», что у снapshot интерфейсов главы 71. (3) Настоящее употребление — StoreSource, превращающий его в источник построения цепи: default-база доверия HTTPS-клиента и есть комбинация этих двух функций. Хотите сузить поверхность доверия (верить только конкретным CA) — берите StoreCreate + Add/AddFile с явной загрузкой якорей: хранилище не решает за вас, кому верить; добавили — значит доверились.

Третья полная программа: лёгкий путь запроса отзыва

Третья программа из examples/x509/crl_policy/main.c — рукотворно собранное проверенное представление с фокусом на семантике запросов CrlCheck:

examples/x509/crl_policy/main.c源码 ↗
#include <stdio.h>
#include <string.h>

#include <xrt.h>




















 */


/* 展示复用已验证 CRL 视图查询多张证书的轻量路径。 */
int main(void)
{
	static const uint8 Name[] = {
		0x30, 0x0E, 0x31, 0x0C, 0x30, 0x0A, 0x06, 0x03, 0x55, 0x04,
		0x03, 0x0C, 0x03, 0x43, 0x41, 0x31
	};
	static const uint8 Entries[] = {
		0x30, 0x14, 0x30, 0x12, 0x02, 0x01, 0x2A, 0x17, 0x0D, 0x32,
		0x36, 0x30, 0x34, 0x30, 0x38, 0x30, 0x30, 0x30, 0x30, 0x30,
		0x30, 0x5A
	};
	static const uint8 Serial[] = { 0x2A };
	xx509cert Issuer;
	xx509cert Certificate;
	xx509crl Crl;
	xx509crlvalid Valid;
	xx509revocation Revocation;
	xx509result Result;

	/* 实际程序应由 xrtX509CrlValidate 产生 Valid;这里聚焦查询接口。 */
	memset(&Issuer, 0, sizeof(Issuer));
	memset(&Crl, 0, sizeof(Crl));
	memset(&Valid, 0, sizeof(Valid));
	Issuer.Subject = (xbytesview) { Name, sizeof(Name) };
	Crl.Issuer = Issuer.Subject;
	Crl.Version = X509_CRL_VERSION_1;
	Crl.Revoked = (xbytesview) { Entries, sizeof(Entries) };
	Valid.Crl = &Crl;
	Valid.Issuer = &Issuer;

	memset(&Certificate, 0, sizeof(Certificate));
	Certificate.Version = X509_VERSION_3;
	Certificate.Issuer = Issuer.Subject;
	Certificate.Serial = (xbytesview) { Serial, sizeof(Serial) };
	Result = xrtX509CrlCheck(&Valid, &Certificate, &Revocation);
	if ( Result != X509_VALUE ) {
		return 1;
	}
	printf("revocation state: %d\n", (int)Revocation.State);
	return Revocation.State == X509_REVOCATION_REVOKED ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/x509/crl_policy/main.c -lws2_32 -liphlpapirevocation state: 1

Что только что произошло. (1) xx509crlvalid в настоящей программе обязан порождаться только xrtX509CrlValidate — здесь он собран руками, чтобы сфокусироваться на интерфейсе запроса (представление CRL с записями отзыва, издатель с Subject). (2) В записях отзыва лежит серийный номер 0x2A; Serial проверяемого сертификата ровно 0x2A, Issuer совпадает — CrlCheck возвращает X509_VALUE и публикует State=1 (X509_REVOCATION_REVOKED). (3) Серийный номер на всём пути — представление байтов произвольной точности (семантика Serial-как-есть главы 79 выровнена на стороне CRL — точное байтовое совпадение, без усекающей конвертации). Парный образец examples/x509/crl демонстрирует CrlParse + курсорный обход записей: у пустого списка Read сразу DONE — «структура целиком пройдена до конца» и «прервано ошибкой» два разных финала, в коде их различайте.

81.4契约

  • Семантика якоря: якорь — вход проверки, в путь не входит; xrtX509Anchor извлекает Subject/публичный ключ/ограничения; появление якорного сертификата в пути — отказ; сертификаты партнёра всегда только кандидаты, не якоря.
  • Время проверки: config.Time задаётся явно; проверка исследует каждый сертификат в данный момент — воспроизводимо, кешируемо, проверяемо на повтор.
  • Правила пути: промежуточному CA нужны непустой Subject + critical BasicConstraints(cA=TRUE); KeyUsage/pathLen/NameConstraints/EKU проверяются по звеньям; длина считает только не-self-issued промежуточные CA; NameConstraints у самой цели отвергаются.
  • Границы построителя: не читает файлы, системные хранилища, сеть, AIA и не расширяет кандидатов бесконечно; полный путь обязан пройти PathValidate; ёмкость ≤16 — ноль кучевых выделений; XERR_NOT_FOUND нет пути, XERR_RANGE нехватка ёмкости, цепочка причин в X509_ERROR_PATH_BUILD.
  • Транзакционность хранилища: AddPem/AddFile/AddSystem — неудача любого блока откатывает всё; повторный сертификат идемпотентен (DONE); на каждую запись один DER, представления заимствуют внутрибиблиотечное хранение.
  • Правило инвалидации: запись может сдвинуть структуры — сперва вся загрузка, потом указатели/источник; после построения — только чтение, конкурентно допустимо; запись и освобождение не конкурентны с чтением.
  • Двухслойность CRL: Validate (тяжёлый) даёт заимствованное представление xx509crlvalid (CRL и издатель обязаны жить); Check (лёгкий) опрашивает многие; одноразово — CrlStatus; конфигурация строга по умолчанию (требует nextUpdate/CRL Number/AKI/KeyUsage).
  • Три состояния отзыва: ERROR плохой вход / DONE неприменимо (не «не отозван») / VALUE единственный с вердиктом; GOOD сегментированного CRL ограничен CoveredReasons, агрегатор нескольких — xrtX509RevocationUpdate.
  • Принадлежность политики: сетевая выборка, кеширование, мягкая/жёсткая неудача, комбинирование OCSP — верхнему слою; этот слой сохраняет однозначный результат.
  • Обрезка: store_file, store_system, crl_policy — независимые макросы возможностей; слой пути — независимые бэкенды RSA/ECDSA/Ed25519.

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

Ловушка 1: «корневой сертификат», присланный партнёром, в роли якоря доверия:Ловушка 1: «корневой сертификат», присланный партнёром, в роли якоря доверия

Симптом: посредник строит себе всю цепь «корень + промежуточный + лист» и шлёт клиенту; клиент берёт вершину цепи за якорь — проверка проходит; доверие целиком назначено партнёром.

Причина: статус якоря доверия приходит только из локальной загрузки (системное хранилище, конфигурационный файл, явный Add). Каждый присланный сертификат — кандидатный материал на проверку, включая тот самоподписанный, что похож на корень. Дизайн PathBuild и разводит их: якорь передаётся отдельно и никогда не входит в путь.

错误写法
/* последнюю карту материала рукопожатия прямо в якоря */
xrtX509Anchor(&PeerChain[n - 1], &Anchor);
xrtX509PathValidate(Path, n, &Anchor, &Config);
/* партнёр контролирует содержимое цепи = партнёр контролирует доверие */
正确写法
/* якорь только из локального хранилища; вся цепь партнёра уходит в Build как кандидаты */
xx509pathsource Source;
xrtX509StoreSource(pLocalStore, Issuers, n, &Source);
if ( !xrtX509PathBuild(&Leaf, &Source, &Config,
		Path, 4u, &Result) ) {
	reject();   /* не дойти до локального якоря: не доверяем */
}
Ловушка 2: DONE у CrlCheck в роли GOOD:Ловушка 2: DONE у CrlCheck в роли GOOD

Симптом: сертификат мог быть отозван, но код пропустил его из-за «CrlCheck вернул не ERROR» — DONE (этот CRL неприменим к сертификату) и GOOD (спрошено, не отозван) смешались.

Причина: в трёх состояниях DONE — «вердикта нет»: несовпадение издателя CRL с издателем сертификата, delta-CRL без записей — всё возвращает его. Принять «не нашлось» за «всё в порядке» значит обнулить проверку отзыва.

错误写法
if ( xrtX509CrlCheck(&Valid, &Cert, &Status) != X509_ERROR ) {
	accept();   /* DONE тоже вошёл: неприменимость принята за неотозванность */
}
正确写法
switch ( xrtX509CrlCheck(&Valid, &Cert, &Status) ) {
case X509_VALUE:
	if ( Status.State == X509_REVOCATION_REVOKED ) {
		reject("revoked");
	} else {
		accept();   /* проверено и не отозвано - единственный пропуск */
	}
	break;
default:
	
	   строгая - отказ, мягкая - записать и пропустить */
	handle_no_verdict();
	break;
}
Ловушка 3: старые указатели/старый source после записи в хранилище доверия:Ловушка 3: старые указатели/старый source после записи в хранилище доверия

Симптом: изредка висячие представления или перепутанные результаты построения цепи — сперва StoreSource построил источник, а midway ещё StoreAdd добавил сертификат.

Причина: внутри хранилище — массивное хранение; запись может сдвинуть сертификаты и якорные структуры — существующие указатели представлений и уже построенный xx509pathsource инвалидируются. Это стандартная ловушка комбинации «владеющее множество + заимствованные представления» (то же явление разобрано в главе 18).

错误写法
xrtX509StoreSource(pStore, Issuers, n, &Source);
xrtX509StoreAdd(pStore, NewDer, iSize);  /* может сдвинуть внутренние структуры */
xrtX509PathBuild(&Leaf, &Source, ...);   /* Source повис */
正确写法
/* Фаза загрузки: все Add/AddPem/AddFile/AddSystem завершены */
load_all_anchors(pStore);
/* Фаза использования: брать указатели, строить источник, строить цепь - после этого не писать */
xrtX509StoreSource(pStore, Issuers, n, &Source);
xrtX509PathBuild(&Leaf, &Source, &Config, Path, 4u, &Result);

Практические задания

Базовое: два прохода цепочки из трёх сертификатов

Постройте OpenSSL собственную цепь «корень → промежуточный → лист» (по одному) и прогоните обе дороги — PathValidate (путь выстроен вами) и PathBuild (неупорядоченные кандидаты). Критерии приёмки: выводы совпадают; нарочно перепутанный порядок кандидатов не мешает Build найти ту же цепь; смена якоря на неустановленный локально корень валит обе дороги.

Продвинутое: HTTPS-верификатор с суженной поверхностью доверия

StoreCreate + AddFile — загрузите только ваш собственный корень и прогоните для листового сертификата полную проверку «построение цепи + MatchHost + ValidAt». Затем сравните результат той же карты под снимком StoreSystem. Критерии приёмки: в суженном хранилище проверка проходит (ваш корень в нём есть); цепь с нев_loaded корнем немедленно валится; вы можете назвать сцены развёртывания, подходящие каждому из двух хранилищ.

Вызов: массовый аудитор отзыва

Дана группа сертификатов и файл CRL их издателя: CrlValidate один раз, цикл CrlCheck с выводом вердикта по каждой (три класса REVOKED/GOOD/неприменимо); для «неприменимо» доложите причину (несовпадение издателя и пр.). Затем оберните цикл запросов хронометром и сравните траты «CrlStatus с перепроверкой на каждую» и «проверили раз — спрашиваем N раз». Критерии приёмки: три класса сходятся с инструментом openssl crl; разница времён двух режимов объяснима (амортизация стоимости проверки).

Итоги

ТемаКратко
Пазл трёх вопросовподпись (79) + идентичность (79) + цепь до локального якоря (эта глава) + отзыв (эта глава) = полная проверка PKI
Модель путилист → промежуточные → …; якорь — вход проверки, в путь не входит; якорь в пути — отказ
PathValidateцепь выстроена вызывающим: время явно, по звеньям подпись/BasicConstraints/KeyUsage/NameConstraints
PathBuildавтоматическое построение из неупорядоченных кандидатов: сперва якорь, фильтры AKI/SKI, возвраты; полный путь обязан пройти строгую проверку
Семантика результатовXERR_NOT_FOUND нет пути / XERR_RANGE нехватка ёмкости / цепочка причин X509_ERROR_PATH_BUILD
Хранилище доверияAdd/AddPem/AddFile/AddSystem транзакционно; на запись один DER; явное добавление = присвоение доверия
Системный снимокStoreSystem независим: неудача перечисления платформы откатывает всё; настройки системы не меняются
Правило инвалидациисперва вся загрузка, потом источник; запись инвалидирует указатели/источник; после построения — только чтение, конкурентно
Два слоя CRLValidate проверяет раз (подпись/время/назначение) → crlvalid; Check легко опрашивает N серийных номеров
Три состояния отзываERROR плохо / DONE неприменимо (не GOOD) / VALUE есть вердикт; сегментированные CRL — агрегатором
Принадлежность политикивыборка/кеш/мягкая неудача — верхнему слою; этот слой сохраняет однозначный результат для ваших решений

📖 Справочники API в этой главе:x509 (140 записей)crypto (140 записей)

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