如何通过OpenSSL API获取电子护照SOD文件中证书的SKI与AKI
用OpenSSL API提取电子护照SOD文件中证书的SKI/AKI
嘿,我刚好处理过类似的电子护照SOD文件解析需求,用OpenSSL API提取SKI(SubjectKeyIdentifier)和AKI(AuthorityKeyIdentifier)其实分几个关键步骤,我给你拆解清楚:
核心前提:SOD文件的结构
首先要明确,电子护照的SOD文件是CMS(Cryptographic Message Syntax)格式的签名数据,里面包含了一组证书(包括签名者证书)。所以第一步得先解析CMS对象,再从中提取证书集合。
步骤1:加载并解析SOD文件的CMS结构
用OpenSSL的CMS相关API读取SOD文件,解析出CMS内容对象,再提取里面的证书栈:
#include <openssl/cms.h> #include <openssl/x509.h> #include <openssl/bio.h> #include <openssl/err.h> #include <stdio.h> #include <stdlib.h> int main() { // 初始化OpenSSL错误处理 ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); // 打开SOD文件 BIO *sod_bio = BIO_new_file("passport.sod", "rb"); if (!sod_bio) { ERR_print_errors_fp(stderr); return 1; } // 解析CMS对象 CMS_ContentInfo *cms = d2i_CMS_bio(sod_bio, NULL); BIO_free(sod_bio); if (!cms) { ERR_print_errors_fp(stderr); return 1; } // 提取证书栈 STACK_OF(X509) *cert_stack = CMS_get1_certs(cms); if (!cert_stack || sk_X509_num(cert_stack) == 0) { printf("SOD文件中未找到任何证书\n"); CMS_ContentInfo_free(cms); return 1; }
步骤2:提取SubjectKeyIdentifier(SKI)
SKI是X509证书的扩展字段,OID为1.3.6.1.4.1.311.2.1.14。我们可以用OpenSSL的X509_get_ext_d2i()直接提取,再转成易读的十六进制字符串:
// 辅助函数:提取SKI并转为十六进制字符串 char *extract_ski(X509 *cert) { // 用预定义的NID获取SKI扩展(比直接写OID更方便) ASN1_OCTET_STRING *ski_data = X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL); if (!ski_data) return NULL; // 分配内存存储十六进制字符串 char *ski_hex = malloc(2 * ski_data->length + 1); if (!ski_hex) { ASN1_OCTET_STRING_free(ski_data); return NULL; } // 把字节数组转成十六进制 for (int i = 0; i < ski_data->length; i++) { sprintf(ski_hex + 2*i, "%02X", ski_data->data[i]); } ski_hex[2 * ski_data->length] = '\0'; ASN1_OCTET_STRING_free(ski_data); return ski_hex; }
步骤3:提取AuthorityKeyIdentifier(AKI)
AKI的OID是2.5.29.35,它的结构更复杂,包含了签发者的Key ID、证书 issuer 和 serial number 等字段。这里我们提取最常用的keyIdentifier部分:
// 辅助函数:提取AKI的keyIdentifier部分(十六进制) char *extract_aki_keyid(X509 *cert) { AUTHORITY_KEYID *aki_data = X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); if (!aki_data || !aki_data->keyid) { AUTHORITY_KEYID_free(aki_data); return NULL; } ASN1_OCTET_STRING *keyid = aki_data->keyid; char *aki_hex = malloc(2 * keyid->length + 1); if (!aki_hex) { AUTHORITY_KEYID_free(aki_data); return NULL; } for (int i = 0; i < keyid->length; i++) { sprintf(aki_hex + 2*i, "%02X", keyid->data[i]); } aki_hex[2 * keyid->length] = '\0'; AUTHORITY_KEYID_free(aki_data); return aki_hex; }
步骤4:遍历证书栈,提取目标证书的SKI/AKI
SOD文件里可能有多个证书,你需要根据实际情况筛选签名者证书(比如通过签发者名称、序列号匹配)。这里我们先遍历所有证书演示:
// 遍历证书栈 for (int i = 0; i < sk_X509_num(cert_stack); i++) { X509 *cert = sk_X509_value(cert_stack, i); printf("=== 证书 %d ===\n", i+1); // 提取并打印SKI char *ski = extract_ski(cert); if (ski) { printf("Subject Key Identifier: %s\n", ski); free(ski); } else { printf("未找到Subject Key Identifier\n"); } // 提取并打印AKI的keyID char *aki = extract_aki_keyid(cert); if (aki) { printf("Authority Key Identifier (Key ID): %s\n", aki); free(aki); } else { printf("未找到Authority Key Identifier的Key ID部分\n"); } // 可选:筛选签名者证书(示例:检查签发者和序列号) // X509_NAME *issuer = X509_get_issuer_name(cert); // ASN1_INTEGER *serial = X509_get_serialNumber(cert); // 这里可以添加自定义逻辑匹配目标证书 } // 释放资源,避免内存泄漏 sk_X509_pop_free(cert_stack, X509_free); CMS_ContentInfo_free(cms); ERR_free_strings(); EVP_cleanup(); return 0; }
注意事项
- 版本兼容性:上面的代码兼容OpenSSL 1.1.1系列,如果你用的是3.0+版本,部分API可能有调整(比如可以用
OSSL_DECODER替代d2i_CMS_bio),但核心逻辑一致。 - 错误处理:代码里的错误处理比较基础,实际项目中建议更严谨地处理OpenSSL的错误栈(比如用
ERR_get_error()获取具体错误码)。 - 编译链接:编译时要链接OpenSSL的crypto库,比如:
gcc sod_parser.c -o sod_parser -lcrypto - 证书筛选:如果SOD里有多个证书,一定要根据电子护照的规范(比如ICAO 9303)找到对应的签名者证书,不要直接用第一个证书。
内容的提问来源于stack exchange,提问作者tzippy
相关产品推荐
相关产品推荐

