如何用OpenSSL 1.0.2p可靠验证X509证书的extendedKeyUsage字段?
如何正确验证X509证书的Extended Key Usage字段
嘿,我一眼就看出你第一个代码出问题的核心原因了——类型不匹配!你把extendedKeyUsage当成了和keyUsage一样的BIT STRING类型来处理,但实际上它们的ASN.1编码完全不同:
keyUsage是BIT STRING类型,所以你用ASN1_BIT_STRING*接收没问题;- 但
extendedKeyUsage是SEQUENCE OF OBJECT IDENTIFIER类型,不能直接转成ASN1_BIT_STRING,强行转换后访问data成员自然会导致内存越界或未定义行为,这就是为什么每次运行结果都不一样。
正确的实现方式
要可靠地获取并验证extendedKeyUsage,你需要用OpenSSL提供的专门处理扩展密钥用法的结构体和函数,直接对比OID的NID值(而不是字符串)会更高效且无歧义:
#include <openssl/x509v3.h> #include <openssl/pem.h> #include <openssl/bio.h> int verifyDeviceCertExtendedKeyUsage() { OpenSSL_add_all_algorithms(); // 读取证书文件(假设readAllBytes是你已实现的函数) auto readBytes = MyApp::FileUtil::readAllBytes("path/to/pem"); BIO *bio_mem = BIO_new(BIO_s_mem()); if (!bio_mem) { // 处理错误 return ERR; } BIO_puts(bio_mem, readBytes.data()); X509 *x509 = PEM_read_bio_X509(bio_mem, NULL, NULL, NULL); if (!x509) { BIO_free(bio_mem); return ERR; } // 获取extendedKeyUsage扩展的索引 int ext_idx = X509_get_ext_by_NID(x509, NID_ext_key_usage, -1); if (ext_idx < 0) { // 证书没有这个扩展,直接返回错误 X509_free(x509); BIO_free(bio_mem); return ERR; } // 获取扩展对象并转成EXTENDED_KEY_USAGE结构体 X509_EXTENSION *ext = X509_get_ext(x509, ext_idx); EXTENDED_KEY_USAGE *eku = static_cast<EXTENDED_KEY_USAGE*>(X509V3_EXT_d2i(ext)); if (!eku) { X509_free(x509); BIO_free(bio_mem); return ERR; } // 标记是否找到需要的两个用途 bool has_server_auth = false; bool has_client_auth = false; // 遍历所有扩展密钥用法OID int eku_count = sk_ASN1_OBJECT_num(eku); for (int i = 0; i < eku_count; ++i) { ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(eku, i); int nid = OBJ_obj2nid(obj); if (nid == NID_server_auth) { // 对应TLS Web Server Authentication (1.3.6.1.5.5.7.3.1) has_server_auth = true; } else if (nid == NID_client_auth) { // 对应TLS Web Client Authentication (1.3.6.1.5.5.7.3.2) has_client_auth = true; } // 如果已经找到两个需要的,可以提前退出循环 if (has_server_auth && has_client_auth) { break; } } // 验证逻辑:必须同时包含且仅包含这两个用途 bool is_valid = (has_server_auth && has_client_auth); if (eku_count != 2) { is_valid = false; } // 释放所有OpenSSL资源(非常重要,避免内存泄漏) sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); X509_free(x509); BIO_free(bio_mem); return is_valid ? SUCCESS : ERR; }
对你找到的字符串对比方法的点评
你的更新思路是对的(用X509V3_EXT_d2i转成EXTENDED_KEY_USAGE),但有几个明显的问题:
- 逻辑错误:你写了两个完全相同的
strcmp判断,第二个应该对比客户端认证的OID1.3.6.1.5.5.7.3.2; - buffer初始化的疑问:
char buffer[100] = {0};是标准C++行为,不是编译器特定的——用{0}初始化数组时,所有元素都会被置为0; - 可靠性问题:用NID数值对比(比如
NID_server_auth)比字符串对比更可靠,不会出现大小写、格式等歧义; - 资源泄漏:你没有释放
EXTENDED_KEY_USAGE结构体,需要用sk_ASN1_OBJECT_pop_free来释放,否则会导致内存泄漏。
额外注意事项
- OpenSSL的
NID_server_auth和NID_client_auth宏与你提到的XKU_SSL_SERVER、XKU_SSL_CLIENT用途完全对应,无需手动对比数值; - 如果你需要严格验证“仅包含”这两个用途,一定要检查扩展中的OID数量是否为2,否则证书若包含其他用途会被误判;
- 所有OpenSSL分配的资源(
BIO、X509、EXTENDED_KEY_USAGE等)都必须手动释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者raidensan




