本文档剖析 OTX Proxy 中完整的证书生命周期管理机制——从 PKCS#12 文件的底层解析、到环境感知的客户端证书加载、再到服务端安全常量的分级获取与缓存。系统围绕三个核心数据域运转:HTTPS 双向认证所需的客户端 PEM 证书、OTA 升级服务的 CA 与客户端证书、以及按 ECU 粒度管理的安全常量(SWDL/Immo/SAL/Common 密钥、AES 通信密钥、EOL 全量密钥)。
架构纵览:三层证书处理管线
证书管理遵循一条清晰的解析 → 加载 → 获取三层管线,每层有明确的职责边界:
flowchart TD
subgraph L1["第1层:P12 解析(algorithm.cpp)"]
A[PKCS#12 文件] --> B["parse_cert_p12()"]
B --> C["OpenSSL PKCS12_parse()"]
C --> D["X.509 证书 → PEM"]
C --> E["私钥 EVP_PKEY → PEM"]
end
subgraph L2["第2层:客户端证书加载(JiduClient)"]
F["loadEnvirment()"] --> G["certDir 定位<br/>(工厂 × 环境)"]
H["loadParam()"] --> I["pver 文件读取"]
I --> J["DES-CBC 解密 certPwd"]
J --> K["gen_pem() → L1"]
K --> L["certPem / keyPem 就绪"]
end
subgraph L3["第3层:安全常量获取(callServer → DataCenter)"]
M["PKI 服务器<br/>(mTLS 认证)"] --> N["ECU 安全常量<br/>iType=6/59"]
M --> O["EOL 全量密钥<br/>iType=19/58"]
M --> P["ECU 证书 + AES<br/>iType=20/56"]
M --> Q["AES 通信密钥<br/>iType=21/55"]
M --> R["OTA 证书<br/>iType=27/64"]
N & O & P & Q & R --> S["DataCenter 全局缓存"]
end
L2 --> L3证书处理流程的第一步是定位正确的 PKI 证书目录。JiduClient::loadEnvirment() 根据两个维度决定 certDir 路径:工厂类型(PMA/DMA/售后工程)和运行环境(Dev/Test/Staging/Prod),生成的路径形如 {appPath}pkiCertificate/PMA/Prod/。对于售后与工程模式(非 PMA 非 DMA),路径简化为 {appPath}pkiCertificate/{Env}/,且仅配置诊断服务主机而不涉及 EOL/PKI 主机。
Sources: jidu_client.cpp
第一层:P12 → PEM 的 OpenSSL 解析
parse_cert_p12() 是整个证书体系的根基函数,位于 algorithm.cpp 底层。它接受 P12 文件路径、密码短语,输出 PEM 格式的 X.509 证书和 RSA/EC 私钥。
核心流程为:通过 BIO_read_filename 将 P12 文件读入 BIO,调用 d2i_PKCS12_bio 将 DER 编码的 PKCS#12 数据反序列化为 PKCS12 结构体;随后 PKCS12_parse(p12, passwd, &pkey, &cert, &ca) 一次性提取出私钥、终端实体证书和 CA 证书链。证书通过 PEM_write_bio_X509 写入内存 BIO 后拷贝到输出缓冲区,私钥通过 PEM_write_bio_PrivateKey 以未加密的 PEM 格式写出。
| 参数 | 类型 | 说明 |
|---|---|---|
p12Path | char* | P12 证书文件的绝对路径 |
passwd | char* | P12 文件的保护密码 |
pemBuffer | char* (输出) | 接收 PEM 编码的 X.509 证书 |
keyBuffer | char* (输出) | 接收 PEM 编码的私钥 |
| 返回值 | int | EXIT_SUCCESS(0) 成功,EXIT_FAILURE(1) 失败 |
关键安全细节:解析失败时,函数会依次调用 X509_free(cert)、EVP_PKEY_free(pkey)、sk_X509_pop_free(ca, X509_free) 释放已分配的资源,防止内存泄漏。成功路径中证书和私钥分别通过独立的 BIO_s_mem() 写入,互不干扰。
Sources: algorithm.cpp
第二层:环境感知的客户端证书加载
证书目录的环境矩阵
loadEnvirment() 构建了一个 工厂 × 环境 的完整矩阵,为 certDir 和四个服务主机(eolHost、pkiHost、cfgHost、diagHost)赋值:
| 工厂 | 环境 | certDir 子路径 | PKI 主机域名模式 |
|---|---|---|---|
| PMA | Dev | pkiCertificate/PMA/Dev/ | pki-thirdparty.jidudev.com |
| PMA | Test | pkiCertificate/PMA/Test/ | pma-pki-thirdparty.jidupmatest.com |
| PMA | Staging | pkiCertificate/PMA/Staging/ | pma-pki-thirdparty.jidupmastaging.com |
| PMA | Prod | pkiCertificate/PMA/Prod/ | pma-pki-thirdparty.jidupma.com |
| DMA | Dev | pkiCertificate/DMA/Dev/ | pki-thirdparty.jidudev.com |
| DMA | Prod | pkiCertificate/DMA/Prod/ | dma-pki-thirdparty.jidudma.com |
| 售后/工程 | Dev | pkiCertificate/Dev/ | 仅 diagHost 有效 |
Staging 环境下若 Diagnosis.Entry.exe.config 中 eolEnvironment=true,EOL 主机将指向 Prod 环境,但 PKI 主机保持 Staging 不变。此外,loadEnvirment() 还会尝试从 WebApi.json 配置文件覆盖主机地址,实现灵活的部署切换。
Sources: jidu_client.cpp
P12 密码的安全解密与 gen_pem
loadParam() 负责读取 pver 文件(位于应用程序根目录),从中提取设备序列号 deviceID、按环境编码索引的 certPwd 密文和 certName 文件名。证书密码的保护采用 Base64 编码 + DES-CBC 解密 的双层机制:
pver 中的 certPwd 字段 → base64_decode() → des_cbc_pkcs5_decrypt(data, key="siuhuali") → 明文密码环境编码 environmentCode 在 DMA 工厂下偏移 +6(environmentCode += 6),这是因为 DMA 工厂的密码在 certPwd JSON 对象中使用 7–10 的键名(对应于 DMA Dev/Test/Staging/Prod),而 PMA 使用 1–4。解密后的密码取前 16 个字符传入 gen_pem()。
gen_pem() 作为薄封装层,分配两个 1024 字节的栈缓冲区,调用 parse_cert_p12() 完成 P12 到 PEM 的转换,结果存入 JiduClient 的成员变量 certPem 和 keyPem。加载成功后,deviceSn 会在 JiduClient 和 DataCenter 之间双向同步。
Sources: jidu_client.cpp, jidu_client.cpp
PEM 证书在 HTTPS 请求中的应用
加载完成的 certPem 和 keyPem 在 callServer 中被注入到 HTTP 客户端,具体策略取决于目标主机类型:
PKI 主机(
host == "pki"):直接调用setClientCert(certPem)和setClientKey(keyPem)启用 mTLS 双向认证。HttpClient::setClientCert/setClientKey将 PEM 字符串包装为curl_blob结构体(设置CURL_BLOB_COPY标志确保 libcurl 内部拷贝),并通过CURLOPT_SSLCERT_BLOB/CURLOPT_SSLKEY_BLOB传递给 libcurl。DIAG/CFG 主机(
host == "diag"或"cfg"):不使用客户端证书。取而代之的是通过ecdsa_signature()对请求内容进行 ECDSA 签名——将inParams + url + method + serviceName + timestamp拼接,用keyPem中的 ECDSA 私钥签名后放入请求的signature字段。keyPem在此处承担双重角色:同时作为 mTLS 私钥和 API 签名私钥。EOL 主机:不使用客户端证书,直接发送请求。
Sources: jidu_client.cpp, HttpClient.cpp
第三层:安全常量的分类获取与缓存
DataCenter 维护五类安全相关数据,均以静态成员变量的形式全局共享。callServer_preHandler 在每次请求前清空对应的缓存字段,callServer_affterHandler 在响应成功后解析并填充。
ECU 安全常量(iType=6 PKI / iType=59 DIAG)
JD_ECU_SAFETY_CONSTANT_t 结构体包含四类密钥,按 ECU 名称索引:
| 字段 | 含义 | 典型用途 |
|---|---|---|
keySwdl | Software Download 密钥 | 刷写文件下载认证 |
keyImmo | Immobilizer 密钥 | 防盗认证 |
keySal | Security Access Level 密钥 | 诊断安全访问(SeedToKey) |
keyCommon | 通用密钥 | 通用安全操作 |
响应中的四个 JSON 数组(swdl、immo、sal、common)以 ECU 名为键、密钥值为值,affterHandler 遍历 swdl 数组,以 ECU 名称为索引将四类密钥组装成 JD_ECU_SAFETY_CONSTANT_t 存入 DataCenter::m_EcuSafetyConstant map。
getEcuSafetyConstant(ecuName) 实现了智能 ECU 名称回退:当精确名称未命中时,依次尝试前缀匹配(如 IEM → 匹配任意 IEM*)、特定别名(BECM → BECM1,WPC → WPC 但排除 WPC3)、以及去除电压后缀(BECM1_400V → BECM1)。
Sources: jidu_dataCenter.cpp, jidu_client.cpp, jidu_macro.h
EOL 全量密钥(iType=19/58 PKI, iType=11/57 DIAG)
JD_EOL_KEY_t 聚集了产线终端(End-of-Line)所需的全部 immobilizer 和域控密钥:
| 字段 | 说明 |
|---|---|
immoKeyEcm | 发动机控制模块 Immo 密钥 |
immoKeyIem | 逆变器控制模块 Immo 密钥 |
immoKeyMgm | 电机控制模块 Immo 密钥 |
immoKeyRemote | 遥控钥匙 Immo 密钥 |
dkBncmBgm | BNCM/BGM 域控密钥 |
powerRootKey | 高压根密钥 |
powerRootKeyId | 高压根密钥 ID |
这些密钥在 PKI 服务器端创建后(createEolAllKey),通过 getEolAllKey 获取并缓存在 DataCenter::m_EolKey 中。
Sources: jidu_macro.h, jidu_client.cpp
ECU 证书信息(iType=20/56 PKI, iType=23/65 DIAG)
JD_EcuCertInfo_t 是最高粒度的 ECU 证书容器,包含三个层次:
flowchart LR
subgraph JD_EcuCertInfo_t
A["cls_strVid<br/>车辆 VID"]
B["cls_strEcuCa<br/>ECU CA 证书"]
C["ecuCertInfo<br/>map<ecuType, JD_EcuCertItem_t>"]
end
subgraph JD_EcuCertItem_t
D["cls_strDvcKey<br/>域控设备密钥"]
E["cls_strP12<br/>ECU P12 证书内容"]
F["cls_strP12Key<br/>P12 密码"]
G["cls_strAESKey<br/>AES 密钥"]
H["cls_strSOAKey<br/>SOA 密钥"]
end
C --> D & E & F & G & HaffterHandler 解析逻辑首先构建 dvcMap(dvcKey 数组以 ECU 类型为键),然后遍历 certList 数组,为每个 ECU 类型组装 JD_EcuCertItem_t——其中 cls_strDvcKey 来自 dvcMap 的查找结果,cls_strP12、cls_strAESKey、cls_strP12Key、cls_strSOAKey 直接从服务器响应对应字段提取。
Sources: jidu_macro.h, jidu_client.cpp
AES 通信密钥(iType=21 PKI / iType=55 DIAG)
DataCenter::m_aesKey 是一个 map<string, string>,以 ECU 名称为键存储 Base64 编码的 AES 密钥。服务器响应为一个 JSON 对象数组,每个元素的键为 ECU 名称、值为密钥字符串。
特殊处理:当 iType=21 或 iType=55 的响应返回错误码 9002/9004/9005(表示 PKI 服务异常)时,系统会静默降级——使用硬编码的固定密钥 \x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF 经 Base64 编码后为 ACU、BGM、CDC、TCAM 四个关键 ECU 赋值,确保刷写流程不因 PKI 不可用而完全中断。
Sources: jidu_client.cpp, jidu_client.cpp
OTA 证书(iType=27 PKI / iType=64 DIAG)
JD_OTA_CERT_t 结构最为精简,仅含两个字段:
| 字段 | 含义 |
|---|---|
caCert | OTA 服务的 CA 证书(服务端验证) |
clientCert | OTA 服务的客户端证书(mTLS 认证) |
响应中 serviceCa 映射到 caCert,otaCert 映射到 clientCert。OTA 证书独立于 PKI 通信证书——前者用于 OTA 升级包下载时的安全验证,后者用于与 PKI 服务器本身的通信。
Sources: jidu_macro.h, jidu_client.cpp
安全常量获取的完整请求路由
以下表格汇总了所有证书与安全常量相关的 iType 及其路由目标:
| iType | 方法 | 主机 | URL 路径 | 获取内容 | DataCenter 存储 |
|---|---|---|---|---|---|
| 5 | POST | pki | /api/dataMappingApi/queryConstantsByEcuName | 按 ECU 名查询常量 | — |
| 6 | POST | pki | /api/dataMappingApi/queryConstantsByKeyType | 按密钥类型查询全部 ECU 安全常量 | m_EcuSafetyConstant |
| 11 | POST | pki | /api/dataMappingApi/createEolAllKey | 创建 EOL 全量密钥 | m_EolKey |
| 19 | GET | pki | /api/dataMappingApi/getEolAllKey | 查询 EOL 全量密钥 | m_EolKey |
| 20 | POST | pki | /api/ca/vehicle/suit | ECU 证书套件 | m_EcuCertInfo |
| 21 | POST | pki | /api/ca/vehicle/getAesKey | AES 通信密钥 | m_aesKey |
| 23 | POST | pki | /api/ca/vehicle/factoryAndReturnDummy | 工厂 Dummy 证书 | m_EcuCertInfo |
| 27 | GET | pki | /api/ca/service/otaCert | OTA 服务证书 | m_OtaCert |
| 55 | POST | diag | /vehicle/cert/seed | AES 通信密钥(售后) | m_aesKey |
| 56 | POST | diag | /vehicle/cert/suit | ECU 证书套件(售后) | m_EcuCertInfo |
| 57 | POST | diag | /vehicle/cert/createEolAllKey | 创建 EOL 密钥(售后) | m_EolKey |
| 58 | GET | diag | /vehicle/cert/getEolAllKey | 查询 EOL 密钥(售后) | m_EolKey |
| 59 | POST | diag | /vehicle/cert/queryConstantsByKeyType | ECU 安全常量(售后) | m_EcuSafetyConstant |
| 64 | GET | diag | /vehicle/cert/otaCert | OTA 证书(售后) | m_OtaCert |
| 65 | POST | diag | /vehicle/cert/factoryAndReturnDummy | Dummy 证书(售后) | m_EcuCertInfo |
对称性设计:PKI 主机(iType 5–27)服务于工厂产线场景,DIAG 主机(iType 55–65)服务于售后诊断场景。两者在接口语义上完全对称——例如 iType=6(PKI)与 iType=59(DIAG)获取相同结构的 ECU 安全常量,但经由不同的网关和服务实例。
Sources: jidu_uri.h
HttpClient 中的证书应用机制
HttpClient 类作为 HTTP 通信基类,通过 curl_blob 结构体管理 PEM 证书数据:
struct curl_blob blob_clientCert; // 文件作用域全局变量
struct curl_blob blob_clientKey;setClientCert(string& cert) 和 setClientKey(string& key) 将 string 内容的指针和长度赋值给 curl_blob,并设置 flags = CURL_BLOB_COPY 确保 libcurl 内部复制数据。在 post() 和 get() 方法中,仅当 clientCert/clientKey 非空时才设置对应的 CURL 选项:
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob_clientCert);
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob_clientKey);值得注意的是,每次请求完成后(curl_easy_perform 返回后),clientCert 和 clientKey 会被 clear() 清空,确保证书数据不会在内存中残留超过单次请求的生命周期。此外,SSL 验证选项被显式关闭(CURLOPT_SSL_VERIFYPEER = false,CURLOPT_SSL_VERIFYHOST = false),因为 PKI 通信完全依赖客户端证书的 mTLS 双向认证,不再需要 CA 证书链验证。
Sources: HttpClient.cpp, HttpClient.cpp, HttpClient.cpp
数据流全景:从 P12 到安全刷写
sequenceDiagram
participant App as OTX 运行时
participant JC as JiduClient
participant DC as DataCenter
participant PKI as PKI 服务器
App->>JC: init()
JC->>JC: loadEnvirment() → certDir, hosts
JC->>JC: loadParam()
Note over JC: 读取 pver → DES-CBC 解密密码
JC->>JC: gen_pem(certPath, certPwd)
Note over JC: parse_cert_p12() → certPem, keyPem
App->>JC: callServer(iType=6)
JC->>JC: callServer_preHandler → 清空 m_EcuSafetyConstant
JC->>PKI: POST + mTLS(certPem, keyPem)
PKI-->>JC: swdl/immo/sal/common JSON
JC->>JC: callServer_affterHandler
JC->>DC: setEcuSafetyConstant(ecuName, constants)
Note over DC: m_EcuSafetyConstant 缓存就绪
App->>DC: getEcuSafetyConstant("IEM_SIC")
DC-->>App: keySwdl, keyImmo, keySal, keyCommon后续阅读建议
本文档聚焦于证书与安全常量的获取与缓存。以下页面提供了上下游的衔接:
- 加密算法集:AES、DES-CBC、SHA256、ECDSA 签名与 Base64 编解码:本文中使用的 DES-CBC 密码解密、ECDSA API 签名、Base64 编解码的底层实现。
- SeedToKey 与 CRC16:诊断安全访问的密钥计算:ECU 安全常量中
keySal在诊断安全访问中的实际应用。 - JiduClient:与集度服务器的 HTTPS 双向认证通信客户端:
JiduClient的完整类设计、单例模式与callServer请求分发机制。 - DataCenter:车辆订单、安全常量、证书信息的统一缓存中心:DataCenter 中其他缓存域(车辆订单、DTC 数据库、诊断信息)的完整说明。
- HttpClient:基于 libcurl 的 HTTP 请求基类与证书管理:
HttpClient中证书 blob 管理的更多实现细节。