本章导读

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概念

四种构造器与它们的私钥方言

构造器私钥输入形态创建期验证
xrtTlsIdentityRsaPKCS#1 或未加密 PKCS#8 DER模数、指数、全部 CRT 参数、叶 SPKI、RSA-PSS 限制
xrtTlsIdentityP25632 字节标量 / SEC1 / 未加密 PKCS#8标量范围、曲线 OID、可选 SEC1 公钥、叶 P-256 SPKI
xrtTlsIdentityP38448 字节标量 / SEC1 / 未加密 PKCS#8同上(P-384)
xrtTlsIdentityEd2551932 字节种子 / 单双层 DER OCTET / RFC 8410 PKCS#8算法参数缺省、派生公钥、叶 Ed25519 SPKI

三个共性:证书链第一张必须是叶;私钥不被借用(深复制进身份,调用方缓冲可立即复用);RSA 保留完整 CRT 视图、Ed25519 保留一次展开的签名密钥——握手路径不再重复派生。PEM/文件/系统库不属于身份核心:先用第 78 章的 PEM 解码拿 DER,再交给构造器——层与层之间只传 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 待签内容(协议上下文头由调用方构造——TLS 1.3 的 64 空格角色前缀在公开原语 xrtTls13CertificateVerifySignature 层处理)。两段式与全库惯例一致:空输出查询精确长度、容量不足不调签名器、失败不发布部分签名。三种后端的随机性策略:RSA-PSS 用密码安全随机盐;ECDSA 用 RFC 6979 确定性 low-S 签名(第 76 章讲过为什么确定性是优点);Ed25519 纯模式本身确定。

Create:硬件与远程签名器

真实企业环境里私钥常在 HSM 或系统密钥库里,不能导出成 DER。xrtTlsIdentityCreate 是扩展接口:证书链仍由 XRT 深复制,签名能力通过 Supports/Sign 回调外包——两个回调必须允许并发调用,Supports 是不设错误的能力谓词,Sign 失败保持输出不变。没有硬件需求就用内置强类型构造器——回调口是为可插拔准备的,不是日常路径。

生命周期:从装载到共享

1装载PEM/文件 → DER(第 78 章),证书链 + 私钥交给构造器
2验证私钥解析 + SPKI 交叉核对 + 共同方案检查(失败即拒绝)
3共享不可变对象被服务端配置/多 Worker 并发引用(Retain/Release)
4销毁最后一个引用释放 → 全部密钥材料安全清零 → Release 回调恰好一次

Retain/Release 引用计数;最后一个引用释放时安全清零全部密钥材料并调用 Release 回调(Create 形态)。身份不可变意味着:多服务端配置共享、多 Worker 并发签名、与策略/上下文任意组合——装配一次,处处共享,与第 22 章对象池的"只读共享最便宜"同一哲学,但这里共享的是不可变值而非可复用槽。

Retain/Release 引用计数;最后一个引用释放时安全清零全部密钥材料并调用 Release 回调(Create 形态)。身份不可变意味着:多服务端配置共享、多 Worker 并发签名、与策略/上下文任意组合——装配一次,处处共享,与第 22 章对象池的"只读共享最便宜"同一哲学,但这里共享的是不可变值而非可复用槽。

83.3示例

第一个完整程序:四种密钥形态构造与类型查询

下面的程序来自 examples/tls/identity/main.c,无参数时用嵌入材料自检四种身份构造器:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/tls/identity/main.c -lws2_32 -liphlpapiembedded rsa identity=1 certificates=1embedded p256 identity=3 certificates=1embedded p384 identity=4 certificates=1embedded ed25519 identity=5 certificates=1

刚才发生了什么。 ① 四种形态各走一个构造器:RSA(1)、P-256(3)、P-384(4)、Ed25519(5)——IdentityType 的枚举值与 xtlsidentitytype 对应,跨平台稳定。② 每次构造即全量验证:私钥解析、SPKI 交叉核对、共同方案检查——嵌入材料全部合法所以构造通过;换成证书私钥不匹配的一对,构造直接失败并给出结构化错误(identity 错误码 + DER/X.509/crypto 原因链)。③ CertificateCount 确认链长(这里每形态一张叶);真实部署传中间 CA 的完整数组。④ 有参数时程序切换到"读 DER 文件 → 构造 → 两段式 Sign"的完整路径——exampleSignature 按类型选方案(RSA 走 PSS、Ed25519 直签),空输出查询长度、分配、再签,打印签名字节数(Ed25519 恒 64,RSA 等于模长)。⑤ 每个身份用完 Release——不可变共享对象的生命周期管理只有这一对调用。

第二个完整程序:签名方案的底层域

第二个程序来自 examples/crypto/sign_tour/main.c,演示身份签名所依赖的底层方案族——Ed25519 三域与 ECDSA 的 DER 表示:

examples/crypto/sign_tour/main.c源码 ↗

























 */

#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/crypto/sign_tour/main.c -lws2_32 -liphlpapisign: ed25519 pure sign->verify oksign: ed25519 context + prehash modes oksign: ecdsa-p256 sign->verify(der) roundtrip oksign: ecdsa-p384 sign->verify(der) roundtrip oksign: der r||s <-> der roundtrip ok

刚才发生了什么。 ① Ed25519 三域互不兼容:PURE(直接签消息)、CONTEXT(带上下文字符串——TLS 1.3 的 CertificateVerify 用的正是这种域分隔思想)、PREHASH(签 64 字节 SHA-512 预哈希)。签名跨域不可验证——这是"同一算法不同用途互不混淆"的密码学机制,TLS 的角色前缀("TLS 1.3, server CertificateVerify")防的就是把站点的 TLS 签名挪用到别的协议。② ECDSA 按"收摘要而非消息"的接口工作(Hash 枚举声明摘要算法),raw r||s 定宽与 DER 线格式互转——第 76 章 ECDSA 双表示的往返验证。③ 对照身份层:IdentitySign 内部就是这些底层原语加上 TLS 上下文封装——理解底层有助于诊断"CanSign 说行但握手失败"这类层间问题(通常是策略白名单没放开方案)。

83.4契约

  • 不可变共享:创建后不可变;引用计数共享;借用视图(证书/公钥)有效到最后引用释放;多线程签名无锁。
  • 创建期验证:私钥解析 + 叶 SPKI 交叉核对 + TLS 共同方案检查;无共同方案直接拒绝;证书链第一张必须是叶。
  • 私钥深复制:构造器不借用私钥输入;单块紧凑分配,释放前安全清零;RSA 留 CRT 视图、Ed25519 留展开密钥。
  • CanSign 语义:版本 + 类型 + 方案 + 后端四重检查;1.3 拒绝 PKCS#1;ECDSA 方案随证书曲线;PSS 受限密钥双侧参数约束。
  • Sign 契约:两段式(空输出查询长度);容量不足不调签名器;失败不发布部分签名;PSS 随机盐 / ECDSA 确定 low-S / Ed25519 纯模式。
  • Create 扩展:Supports 谓词不设错误;Sign 可并发、失败不变输出;Release 恰好一次;证书链仍深复制。
  • 错误模型:xrt.tls 的 identity 错误码;DER/X.509/crypto 失败保留为 Cause——上层可按 TLS 阶段映射,C 调用方可读底层原因。
  • 裁剪:TLS_IDENTITY 核心 + _RSA/_EC/_P256/_P384/_ED25519 各自独立——只跑 Ed25519 服务端不编入 RSA 代码。

常见错误

坑 1:证书与私钥不匹配,上线当晚才发现

症状:服务启动正常,客户端连接在 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;   /* 启动期暴露,附带原因链 */
}
坑 2:自造签名方案组合(CanSign 没过的硬用)

症状:给 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) ) {
	/* 配置错误:启动期报出,而不是握手期 */
}
坑 3:HSM 扩展里 Sign 回调带了共享可变状态

症状:并发握手时间歇性签名失败或输出错乱——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 不匹配"两类原因。

挑战:HSM 模拟签名器

用 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 随机盐/ECDSA low-S/Ed25519 纯
方案绑定方案随密钥类型;白名单策略与 CanSign 交集才是握手方案
Create 扩展HSM/系统库/远程签名;Supports 谓词+可并发 Sign+恰好一次 Release
输入边界PEM/文件/系统库不属身份核心——先拿 DER 再构造
裁剪_RSA/_EC/_P256/_P384/_ED25519 独立;单算法服务端不携带其他

📖 本章涉及的 API 参考手册:tls(201 个条目)tls_identity(18 个条目)crypto(140 个条目)

点击查看完整签名、参数约束、返回值与错误说明