Ориентация главы
В TLS две стороны «доказывают себя»: клиент доказывает «кому я доверяю» (верификатор, глава 85), сервер доказывает «кто я» (идентичность, эта глава). xtlsidentity упаковывает одну DER-цепочку сертификатов и один приватный ключ в неизменяемый разделяемый объект: при создании глубоко копирует весь материал и выполняет перекрёстную проверку (действительно ли приватный ключ соответствует публичному в сертификате? какие схемы TLS эта цепочка может подписывать?), после чего объект конкурентно разделяется потоками и конфигурациями серверов, а рукопожатие пользуется им для производства подписи CertificateVerify. Четыре строго типизированных конструктора (RSA/P-256/P-384/Ed25519) покрывают магистральные серверы; расширительный интерфейс xrtTlsIdentityCreate отдаёт подписную способность на аутсорс HSM, системному хранилищу ключей или удалённому подписчику. Аутентификация клиентскими сертификатами сейчас не реализована (контракт главы 82), поэтому идентичность сегодня — понятие серверное; сам объект от роли отвязан.
83.1引入
Что готовят для развёртывания TLS-сервера? Цепочку сертификатов (листовой + промежуточные CA, PEM-файл) и приватный ключ. Неприятности начинаются с загрузки: DER-форматов приватного ключа несколько (PKCS#1, PKCS#8, голый скаляр SEC1, семя Ed25519); алгоритм публичного ключа сертификата должен совпасть с приватным; у RSA-PSS-сертификатов ещё и параметрические ограничения (дайджест, MGF1, нижний предел соли) — при любом несовпадении подпись провалится только в рукопожатии, и встречать вы будете тревогу трёх часов ночи, а не ясную ошибку при старте.
Ответ xtlsidentity — полная проверка на этапе создания: конструктор разбирает приватный ключ, сверяет SPKI сертификата, проверяет общие подписные схемы для версий TLS — «у идентичности нет ни одной общей TLS-схемы — отказ прямо при конструировании». Упасть при старте в десять тысяч раз лучше, чем в рукопожатии: опыт развёртывания и опыт безопасности — одно и то же. Прошедшая проверку идентичность неизменяема: цепочка сертификатов глубоко копируется в один компактный блок (перед освобождением безопасно обнуляется), заимствованные представления действуют до освобождения последней ссылки — много Worker-ов разделяют один объект, ноль блокировок.
83.2概念
Четыре конструктора и их диалекты приватных ключей
| Конструктор | Входная форма приватного ключа | Проверка на этапе создания |
|---|---|---|
xrtTlsIdentityRsa | PKCS#1 или незашифрованный PKCS#8 DER | модуль, экспонента, все CRT-параметры, листовой SPKI, ограничения RSA-PSS |
xrtTlsIdentityP256 | 32-байтовый скаляр / SEC1 / незашифрованный PKCS#8 | диапазон скаляра, OID кривой, опциональный публичный ключ SEC1, листовой P-256 SPKI |
xrtTlsIdentityP384 | 48-байтовый скаляр / SEC1 / незашифрованный PKCS#8 | то же (P-384) |
xrtTlsIdentityEd25519 | 32-байтовое семя / одно-двухслойный DER OCTET / RFC 8410 PKCS#8 | умолчания параметров алгоритма, производный публичный ключ, листовой Ed25519 SPKI |
Три общности: первый сертификат цепочки обязан быть листовым; приватный ключ не заимствуется (глубоко копируется в идентичность; буфер вызывающего можно переиспользовать немедленно); RSA сохраняет полную CRT-вид, Ed25519 — однократно развёрнутый подписной ключ — путь рукопожатия не повторяет деривацию. PEM/файл/системное хранилище не входят в ядро идентичности: сперва возьмите DER через PEM-декодирование главы 78, затем отдайте конструктору — между слоями ходит только DER.
CanSign: тройной запрос способности
xrtTlsIdentityCanSign(身份, 版本, 方案) отвечает «может ли эта идентичность подписать этой схемой» — проверяя одновременно ограничения версии TLS, тип идентичности сертификата, стандартные схемы и влитые бэкенды. Суть правил: TLS 1.3 не принимает RSA-PKCS#1 в CertificateVerify (жёсткое правило современного протокола); схемы ECDSA обязаны совпадать с кривой сертификата; сертификаты rsaEncryption и RSASSA-PSS входят только в пути rsa_pss_rsae_* и rsa_pss_pss_* соответственно; у ограниченного PSS-ключа параметры обязаны одновременно удовлетворять обеим сторонам — сертификату и приватному ключу. Белый список подписей политики (глава 85) пересекается с CanSign — это и есть фактическая схема рукопожатия; exampleSignature образца главы демонстрирует выбор ходовой схемы по типу идентичности.
Sign: двухшаговая подпись и определённость
xrtTlsIdentitySign(身份, 版本, 方案, 内容, 输出, 容量, &长度) принимает полное TLS-содержимое под подпись (протокольный контекстный заголовок строит вызывающий — 64-пробельный ролевой префикс TLS 1.3 обрабатывается на уровне открытого примитива xrtTls13CertificateVerifySignature). Двухшаговость по обычаю всей библиотеки: пустой выход запрашивает точную длину, нехватка ёмкости не зовёт подписчика, неудача не публикует частичной подписи. Стратегии случайности трёх бэкендов: RSA-PSS берёт криптографически безопасную случайную соль; ECDSA — детерминированная подпись RFC 6979 low-S (глава 76 объясняла, почему определённость — достоинство); чистый режим Ed25519 определён сам по себе.
Create: аппаратура и удалённые подписчики
В реальной корпоративной среде приватный ключ часто живёт в HSM или системном хранилище ключей и не экспортируется в DER. xrtTlsIdentityCreate — расширительный интерфейс: цепочка сертификатов всё так же глубоко копируется XRT, подписная способность отдаётся на аутсорс колбэкам Supports/Sign — оба обязаны допускать конкурентные вызовы; Supports — предикат способности без ошибки, Sign при неудаче не меняет выход. Без аппаратных потребностей берите встроенные строго типизированные конструкторы — колбэчный порт приготовлен для втыкаемости, это не ежедневный путь.
Жизненный цикл: от загрузки к разделению
Retain/Release — подсчёт ссылок; при освобождении последней весь ключевой материал безопасно обнуляется и вызывается колбэк Release (форма Create). Неизменяемость идентичности значит: разделяемость конфигурациями многих серверов, конкурентная подпись многими Worker-ами, комбинирование с политиками/контекстами — собрали раз, разделяем везде; та же философия «разделяемое только-чтение дешевле всего», что у пула объектов главы 22, но здесь разделяется неизменяемое значение, а не переиспользуемый слот.
Retain/Release — тот же подсчёт ссылок; последнее освобождение обнуляет материал и зовёт колбэк Release — см. диаграмму выше.
83.3示例
Первая полная программа: конструкция четырёх форм ключей и запрос типа
Программа ниже из examples/tls/identity/main.c; без параметров самопроверяет четыре конструктора идентичности на встроенном материале:
#include "../common.h" #include "../embedded_identity.h" /* 选择每种内置身份在 TLS 1.3 中的常用签名方案。 */ static xtlssignature exampleSignature(const xtlsidentity* pIdentity) { switch ( xrtTlsIdentityType(pIdentity) ) { case XTLS_IDENTITY_RSA: return XTLS_SIGNATURE_RSA_PSS_RSAE_SHA256; case XTLS_IDENTITY_RSA_PSS: return XTLS_SIGNATURE_RSA_PSS_PSS_SHA256; case XTLS_IDENTITY_ECDSA_P256: return XTLS_SIGNATURE_ECDSA_SECP256R1_SHA256; case XTLS_IDENTITY_ECDSA_P384: return XTLS_SIGNATURE_ECDSA_SECP384R1_SHA384; case XTLS_IDENTITY_ED25519: return XTLS_SIGNATURE_ED25519; default: return (xtlssignature)0; } } */ /* 无参数时用嵌入材料自检四种密钥形态构造器(材料见 embedded_identity.h)。 */ static int exampleEmbeddedCheck(void) { struct { const uint8* pCertificate; size_t iCertificateSize; const uint8* pPrivateKey; size_t iPrivateKeySize; cstr sName; } arrForms[4]; xtlsidentity* pIdentity; size_t i; arrForms[0].pCertificate = g_ExampleRsaCertDer; arrForms[0].iCertificateSize = sizeof(g_ExampleRsaCertDer); arrForms[0].pPrivateKey = g_ExampleRsaKeyDer; arrForms[0].iPrivateKeySize = sizeof(g_ExampleRsaKeyDer); arrForms[0].sName = "rsa"; arrForms[1].pCertificate = g_ExampleCertDer; arrForms[1].iCertificateSize = sizeof(g_ExampleCertDer); arrForms[1].pPrivateKey = g_ExampleKeyDer; arrForms[1].iPrivateKeySize = sizeof(g_ExampleKeyDer); arrForms[1].sName = "p256"; arrForms[2].pCertificate = g_ExampleP384CertDer; arrForms[2].iCertificateSize = sizeof(g_ExampleP384CertDer); arrForms[2].pPrivateKey = g_ExampleP384KeyDer; arrForms[2].iPrivateKeySize = sizeof(g_ExampleP384KeyDer); arrForms[2].sName = "p384"; arrForms[3].pCertificate = g_ExampleEd25519CertDer; arrForms[3].iCertificateSize = sizeof(g_ExampleEd25519CertDer); arrForms[3].pPrivateKey = g_ExampleEd25519KeyDer; arrForms[3].iPrivateKeySize = sizeof(g_ExampleEd25519KeyDer); arrForms[3].sName = "ed25519"; for ( i = 0; i < 4u; i++ ) { const xbytesview arrCertificates[1] = { (xbytesview){ arrForms[i].pCertificate, arrForms[i].iCertificateSize } }; xbytesview PrivateKey = { arrForms[i].pPrivateKey, arrForms[i].iPrivateKeySize }; if ( strcmp(arrForms[i].sName, "rsa") == 0 ) { pIdentity = xrtTlsIdentityRsa(arrCertificates, 1u, PrivateKey); } else if ( strcmp(arrForms[i].sName, "p256") == 0 ) { pIdentity = xrtTlsIdentityP256(arrCertificates, 1u, PrivateKey); } else if ( strcmp(arrForms[i].sName, "p384") == 0 ) { pIdentity = xrtTlsIdentityP384(arrCertificates, 1u, PrivateKey); } else { pIdentity = xrtTlsIdentityEd25519(arrCertificates, 1u, PrivateKey); } if ( (pIdentity == NULL) || (xrtTlsIdentityCertificateCount(pIdentity) != 1u) ) { fprintf(stderr, "embedded %s identity failed\n", arrForms[i].sName); return 1; } printf("embedded %s identity=%d certificates=1\n", arrForms[i].sName, (int)xrtTlsIdentityType(pIdentity)); xrtTlsIdentityRelease(pIdentity); } return 0; } /* 从 DER 证书与私钥创建身份,并演示精确长度查询和实际签名。 */ int main(int argc, char** argv) { static const uint8 Message[] = "xrt TLS identity example"; uint8* pCertificateData = NULL; uint8* pPrivateData = NULL; uint8* pSignature = NULL; size_t iCertificateSize = 0; size_t iPrivateSize = 0; size_t iSignatureSize = 0; xbytesview Certificate; xtlsidentity* pIdentity = NULL; xtlssignature Signature; int iResult = 1; if ( argc == 1 ) { return exampleEmbeddedCheck(); } if ( argc != 4 ) { printf("usage: identity <rsa|p256|p384|ed25519> <certificate.der> <private.der>\n"); printf(" identity (无参数:嵌入材料自检四种形态)\n"); return 0; } if ( !exampleTlsReadFile( argv[2], &pCertificateData, &iCertificateSize ) || !exampleTlsReadFile( argv[3], &pPrivateData, &iPrivateSize ) ) { fprintf(stderr, "failed to read DER input\n"); goto Cleanup; } Certificate = (xbytesview) { pCertificateData, iCertificateSize }; pIdentity = exampleTlsIdentity( argv[1], &Certificate, 1u, (xbytesview) { pPrivateData, iPrivateSize } ); if ( pIdentity == NULL ) { fprintf(stderr, "failed to create TLS identity\n"); goto Cleanup; } Signature = exampleSignature(pIdentity); if ( (Signature == 0) || !xrtTlsIdentitySign( pIdentity, XTLS_VERSION_13, Signature, (xbytesview) { Message, sizeof(Message) - 1u }, NULL, 0, &iSignatureSize ) ) { fprintf(stderr, "identity cannot sign with the selected TLS scheme\n"); goto Cleanup; } pSignature = (uint8*)malloc(iSignatureSize); if ( (pSignature == NULL) || !xrtTlsIdentitySign( pIdentity, XTLS_VERSION_13, Signature, (xbytesview) { Message, sizeof(Message) - 1u }, pSignature, iSignatureSize, &iSignatureSize ) ) { fprintf(stderr, "TLS identity signing failed\n"); goto Cleanup; } printf( "identity=%d certificates=%zu signature=%zu bytes\n", (int)xrtTlsIdentityType(pIdentity), xrtTlsIdentityCertificateCount(pIdentity), iSignatureSize ); iResult = 0; Cleanup: free(pSignature); xrtTlsIdentityRelease(pIdentity); free(pPrivateData); free(pCertificateData); return iResult; }
Что только что произошло. (1) Четыре формы — каждая своим конструктором: RSA (1), P-256 (3), P-384 (4), Ed25519 (5) — значения перечисления IdentityType соответствуют xtlsidentitytype, стабильны кроссплатформенно. (2) Каждая конструкция и есть полная проверка: разбор приватного ключа, перекрёстная сверка SPKI, проверка общих схем — встроенный материал весь законен, конструкция проходит; подсуньте пару с несовпадающими сертификатом и ключом — конструкция падает сразу со структурной ошибкой (код ошибки identity + цепочка причин DER/X.509/crypto). (3) CertificateCount подтверждает длину цепи (здесь по одному листу на форму); реальное развёртывание передаёт полный массив промежуточных CA. (4) С параметрами программа переключается на полный путь «прочитать DER-файлы → построить → двухшаговый Sign» — exampleSignature выбирает схему по типу (RSA через PSS, Ed25519 напрямую), пустой выход запрашивает длину, затем выделение и подпись, печать числа байтов подписи (у Ed25519 всегда 64, у RSA — длина модуля). (5) Каждая идентичность после использования — Release: управлением жизненным циклом неизменяемого разделяемого объекта ведает только эта пара вызовов.
Вторая полная программа: нижний дом подписных схем
Вторая программа из examples/crypto/sign_tour/main.c — демонстрация нижнего семейства схем, на которые опирается подпись идентичности: три домена Ed25519 и DER-представление ECDSA:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> int main(void) { static const uint8 arrSeed[32] = { 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 26u, 27u, 28u, 29u, 30u, 31u, 32u }; static const uint8 arrPriv256[32] = { 0xA1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 26u, 27u, 28u, 29u, 30u, 31u, 0xB0u }; static const uint8 arrPriv384[48] = { 0xC1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u, 12u, 13u, 14u, 15u, 16u, 17u, 18u, 19u, 20u, 21u, 22u, 23u, 24u, 25u, 26u, 27u, 28u, 29u, 30u, 31u, 32u, 33u, 34u, 35u, 36u, 37u, 38u, 39u, 40u, 41u, 42u, 43u, 44u, 45u, 46u, 47u, 0xD0u }; static const uint8 arrContext[4] = { 0x63u, 0x74u, 0x78u, 0x31u }; uint8 arrMessage[13] = { 0x70u, 0x61u, 0x79u, 0x6Cu, 0x6Fu, 0x61u, 0x64u, 0x2Du, 0x62u, 0x79u, 0x74u, 0x65u, 0x73u }; uint8 arrPublic[64]; uint8 arrSignature[96]; uint8 arrSignature2[128]; /* 第二次 DER 编码输出(约 102 字节)。 */ uint8 arrDer[128]; uint8 arrRaw2[96]; uint8 arrDigest[64]; uint8 arrPub256[65]; uint8 arrPub384[97]; size_t iDerSize = 0; xed25519key Key; int iResult = 1; /* ---- Ed25519 纯消息:种子签 → 公钥验。 ---- */ if ( !xrtEd25519Public(arrSeed, arrPublic) || !xrtEd25519Sign(arrSeed, arrMessage, sizeof(arrMessage) - 1u, arrSignature) || !xrtEd25519Verify(arrPublic, arrMessage, sizeof(arrMessage) - 1u, arrSignature) ) { goto Cleanup; } arrSignature[0] ^= 1u; if ( xrtEd25519Verify(arrPublic, arrMessage, sizeof(arrMessage) - 1u, arrSignature) ) { goto Cleanup; } printf("sign: ed25519 pure sign->verify ok\n"); /* ---- Ed25519 CONTEXT / PREHASH 域:展开密钥一次复用。 ---- */ if ( !xrtEd25519KeyInit(&Key, arrSeed) || !xrtEd25519SignMode(&Key, XED25519_CONTEXT, arrContext, sizeof(arrContext), arrMessage, sizeof(arrMessage) - 1u, arrSignature) || !xrtEd25519VerifyMode(arrPublic, XED25519_CONTEXT, arrContext, sizeof(arrContext), arrMessage, sizeof(arrMessage) - 1u, arrSignature) ) { goto Cleanup; } /* PREHASH:消息须先取 64 字节 SHA-512。 */ if ( !xrtSha512(arrMessage, sizeof(arrMessage) - 1u, arrDigest) || !xrtEd25519SignMode(&Key, XED25519_PREHASH, NULL, 0u, arrDigest, XRT_ED25519_PREHASH_SIZE, arrSignature) || !xrtEd25519VerifyMode(arrPublic, XED25519_PREHASH, NULL, 0u, arrDigest, XRT_ED25519_PREHASH_SIZE, arrSignature) ) { goto Cleanup; } xrtEd25519KeyClear(&Key); printf("sign: ed25519 context + prehash modes ok\n"); /* ---- ECDSA-P256:签摘要 → raw 验证 → DER 验证。 ---- */ if ( !xrtSha256(arrMessage, sizeof(arrMessage) - 1u, arrDigest) || !xrtP256Public(arrPriv256, arrPub256) || !xrtEcdsaP256Sign(XCRYPTO_HASH_SHA256, arrDigest, arrPriv256, arrSignature) || !xrtEcdsaP256Verify(arrDigest, XRT_SHA256_SIZE, arrSignature, arrPub256) ) { goto Cleanup; } /* DerEncode:空输出先查询所需长度,再实际编码。 */ if ( !xrtEcdsaDerEncode(arrSignature, 32u, NULL, 0u, &iDerSize) || (iDerSize == 0u) || (iDerSize > sizeof(arrDer)) || !xrtEcdsaDerEncode(arrSignature, 32u, arrDer, iDerSize, &iDerSize) || !xrtEcdsaP256VerifyDer(arrDigest, XRT_SHA256_SIZE, arrDer, iDerSize, arrPub256) ) { goto Cleanup; } printf("sign: ecdsa-p256 sign->verify(der) roundtrip ok\n"); /* ---- ECDSA-P384:SHA-384 摘要同构闭环。 ---- */ if ( !xrtSha384(arrMessage, sizeof(arrMessage) - 1u, arrDigest) || !xrtP384Public(arrPriv384, arrPub384) || !xrtEcdsaP384Sign(XCRYPTO_HASH_SHA384, arrDigest, arrPriv384, arrSignature) || !xrtEcdsaP384Verify(arrDigest, XRT_SHA384_SIZE, arrSignature, arrPub384) || !xrtEcdsaDerEncode(arrSignature, 48u, arrDer, sizeof(arrDer), &iDerSize) || !xrtEcdsaP384VerifyDer(arrDigest, XRT_SHA384_SIZE, arrDer, iDerSize, arrPub384) ) { goto Cleanup; } printf("sign: ecdsa-p384 sign->verify(der) roundtrip ok\n"); /* ---- DER 往返:DerDecode(Encode(x)) == x(P-384 签名)。 ---- */ if ( !xrtEcdsaDerDecode(arrDer, iDerSize, arrRaw2, 48u) || (memcmp(arrRaw2, arrSignature, 96u) != 0) || /* DER(约 102 字节)比 96 字节 raw 更长——独立缓冲。 */ !xrtEcdsaDerEncode(arrRaw2, 48u, arrSignature2, sizeof(arrDer), &iDerSize) || !xrtEcdsaDerDecode(arrSignature2, iDerSize, arrRaw2, 48u) || (memcmp(arrRaw2, arrSignature, 96u) != 0) ) { goto Cleanup; } printf("sign: der r||s <-> der roundtrip ok\n"); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) Три домена Ed25519 взаимно несовместимы: PURE (подпись сообщения напрямую), CONTEXT (со контекстной строкой — именно эта идея разделения доменов работает в CertificateVerify TLS 1.3), PREHASH (подпись 64-байтового предхеша SHA-512). Подпись междоменно непроверяема — это криптомеханизм «один алгоритм, разные назначения не смешиваются»; ролевой префикс TLS («TLS 1.3, server CertificateVerify») и защищает от переноса TLS-подписи сайта в другой протокол. (2) ECDSA работает по интерфейсу «принимает дайджест, а не сообщение» (алгоритм дайджеста объявляет перечисление Hash); raw r||s фиксированной ширины и проволочный DER взаимоконвертируемы — круговорот двойного представления ECDSA из главы 76. (3) Сверьте со слоем идентичности: внутри IdentitySign ровно эти нижние примитивы плюс TLS-контекстная обёртка — понимание нижнего слоя помогает диагностировать межслойные вопросы вида «CanSign разрешил, а рукопожатие падает» (обычно белый список политики не открыл схему).
83.4契约
- Неизменяемое разделение: после создания неизменяем; разделение подсчётом ссылок; заимствованные представления (сертификаты/публичные ключи) действуют до освобождения последней ссылки; многопоточная подпись без блокировок.
- Проверка на этапе создания: разбор приватного ключа + перекрёстная сверка листового SPKI + проверка общих схем TLS; нет общей схемы — прямой отказ; первый сертификат цепи обязан быть листовым.
- Глубокая копия приватного ключа: конструкторы не заимствуют вход; один компактный блок, безопасное обнуление перед освобождением; RSA хранит CRT-вид, Ed25519 — развёрнутый ключ.
- Семантика CanSign: четырёхкратная проверка версия + тип + схема + бэкенд; 1.3 отвергает PKCS#1; схема ECDSA следует кривой сертификата; ограниченный PSS-ключ — двусторонние параметрические ограничения.
- Контракт Sign: двухшаговость (пустой выход запрашивает длину); нехватка ёмкости не зовёт подписчика; неудача не публикует частичную подпись; случайная соль PSS / детерминированный low-S у ECDSA / чистый режим Ed25519.
- Расширение Create: предикат
Supportsбез ошибки;Signконкурентен, при неудаче выход неизменен;Releaseровно один раз; цепочка сертификатов всё равно глубоко копируется. - Модель ошибок: коды ошибок identity в
xrt.tls; провалы DER/X.509/crypto сохраняются как Cause — верхний слой может отображать по фазам TLS, C-вызывающий читает нижнюю причину. - Обрезка: ядро
TLS_IDENTITY+_RSA/_EC/_P256/_P384/_ED25519независимо — сервер только на Ed25519 не втягивает код RSA.
Типичные ошибки
Симптом: сервис стартует нормально, клиентские соединения падают на этапе CertificateVerify — в журнале лишь «signature invalid», не видно, что перепутан материал.
Причина: многие пути загрузки впервые пользуются приватным ключом только в рукопожатии; перепутанный сертификат (скажем, после продления сменили сертификат, а ключ остался старым) вскрылся первым же реальным соединением.
/* только читать файлы, не строить идентичность: ошибка отложена до рукопожатия */ load_file("cert.pem", &Cert, &CertSize); load_file("key.pem", &Key, &KeySize); /* сохранить, при рукопожатии использовать напрямую - несовпадение никто не заметит */
/* строить идентичность при старте: несовпадение падает на месте */ xtlsidentity* pIdentity = xrtTlsIdentityP256( &(xbytesview){ Cert, CertSize }, 1u, (xbytesview){ Key, KeySize } ); if ( pIdentity == NULL ) { fprintf(stderr, "identity: %s\n", xrtErrorMessage(xrtGetError())); return EXIT_FAILURE; /* вскрытие на этапе старта, с цепочкой причин */ }
Симптом: идентичности Ed25519 назначили схему RSA-PSS или P-256-сертификату — схему P-384: IdentitySign возвращает неудачу, рукопожатие висит.
Причина: схемы TLS жёстко связаны с типом ключа (RFC): у схем ECDSA есть имя кривой, RSA делится на семейства rsae/pss, у Ed25519 только чистая схема. Это не «попробовать посмотреть» пункт конфигурации.
/* идентичность Ed25519 + схема RSA: неизбежная неудача */ xrtTlsIdentitySign(pIdentity, XTLS_VERSION_13, XTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, ...);
/* схема следует типу идентичности: спросить CanSign заранее */ xtlssignature Scheme = pick_scheme_for(xrtTlsIdentityType(pIdentity)); if ( !xrtTlsIdentityCanSign(pIdentity, XTLS_VERSION_13, Scheme) ) { /* ошибка конфигурации: доложить на старте, не в рукопожатии */ }
Симптом: при конкурентных рукопожатиях перемежающиеся неудачи подписи либо перепутанный выход — документация Create прямым текстом требует конкурентоспособности Supports/Sign, но реализация колбэка воспользовалась нереентерабельным дескриптором.
Причина: объект идентичности конкурентно разделяется многими Worker-ами; подписные колбэки одновременно бегут в нескольких рукопожатиях. Колбэк — контракт без блокировок; выстроить очередь или погресионные сессии для доступа к устройству с состоянием — ваша ответственность.
static bool hsmSign(..., void* pOutput, size_t* pSize) { hsm_begin(pHsmSession); /* разделяемая сессия: конкурентные рукопожатия топчут друг друга */ hsm_op(...); hsm_end(pHsmSession); }
/* на каждый вызов своя сессия (или пул сессий); неудача не меняет выход и длину */ static bool hsmSign(const void* pContent, size_t iSize, void* pOutput, size_t iCapacity, size_t* pSize) { hsmsession* pS = hsm_session_acquire(); /* через пул */ bool bOk = hsm_sign(pS, pContent, iSize, pOutput, iCapacity, pSize); hsm_session_release(pS); return bOk; }
Практические задания
Прогоните без параметров образец identity и сверьте значения перечисления в четырёх строках с xtlsidentitytype. Затем сгенерируйте OpenSSL самоподписанную пару Ed25519 (сертификат и ключ, экспорт DER) и пройдите параметрический путь с проверкой 64-байтовой подписи. Критерии приёмки: самопроверка четырёх форм вся зелёная; путь на собственном материале выводит identity=5 certificates=1 signature=64 bytes.
Намеренно перепутайте две пары (сертификат RSA + ключ EC; сертификат P-256 + ключ P-384), постройте каждую идентичность и напечатайте цепочку причин ошибки (xrtErrorMessage + xrtErrorCause). Критерии приёмки: обе пары валятся на конструировании, не на подписи; цепочка ошибок различает «провал DER-разбора» и «несовпадение SPKI».
Реализуйте через xrtTlsIdentityCreate «имитируемый HSM»: Supports признаёт только Ed25519; Sign внутри — настоящая реализация (семя хранится в «аппаратной» структуре, имитируя неэкспортируемость) плюс атомарный счётчик вызовов. Подключите его к серверу главы 86 (или протестируйте единично), конкурентно подписывая из 8 потоков. Критерии приёмки: все подписи успешны и проверяемы; счётчик равен числу подписей (доказательство конкурентности); «аппаратное» семя ни разу не покидает имитируемую структуру (подтверждение код-ревью).
Итоги
| Тема | Кратко |
|---|---|
| Объект идентичности | упаковка цепочки + ключа; неизменяем, подсчёт ссылок, многопоточное разделение без блокировок |
| Четыре конструктора | Rsa/P256/P384/Ed25519; диалекты ключей различны (PKCS#1/#8/SEC1/семя) |
| Проверка на создании | разбор ключа + перекрёстная сверка SPKI + проверка общих схем; нет схем — отказ |
| Обработка ключа | глубокая копия без заимствования; один блок с обнулением перед освобождением; CRT/развёрнутый ключ остаются |
| CanSign | версия + тип + схема + бэкенд; 1.3 отвергает PKCS#1; ECDSA следует кривой; PSS — двусторонние ограничения |
| Sign | двухшаговый запрос/подпись; неудача не публикует частичное; случайная соль PSS / low-S у ECDSA / чистый Ed25519 |
| Привязка схем | схема следует типу ключа; белый список политики ∩ CanSign — схема рукопожатия |
| Расширение Create | HSM/системное хранилище/удалённый подписчик; предикат Supports + конкурентный Sign + ровно один Release |
| Входная граница | PEM/файл/системное хранилище вне ядра идентичности — сперва DER, потом конструкция |
| Обрезка | _RSA/_EC/_P256/_P384/_ED25519 независимо; одноалгоритмный сервер не несёт остальных |
📖 Справочники API в этой главе:tls (201 записей)tls_identity (18 записей)crypto (140 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки