You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用OpenSSL 1.0.2p可靠验证X509证书的extendedKeyUsage字段?

如何正确验证X509证书的Extended Key Usage字段

嘿,我一眼就看出你第一个代码出问题的核心原因了——类型不匹配!你把extendedKeyUsage当成了和keyUsage一样的BIT STRING类型来处理,但实际上它们的ASN.1编码完全不同:

  • keyUsageBIT STRING类型,所以你用ASN1_BIT_STRING*接收没问题;
  • extendedKeyUsageSEQUENCE 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),但有几个明显的问题:

  1. 逻辑错误:你写了两个完全相同的strcmp判断,第二个应该对比客户端认证的OID 1.3.6.1.5.5.7.3.2
  2. buffer初始化的疑问char buffer[100] = {0};是标准C++行为,不是编译器特定的——用{0}初始化数组时,所有元素都会被置为0;
  3. 可靠性问题:用NID数值对比(比如NID_server_auth)比字符串对比更可靠,不会出现大小写、格式等歧义;
  4. 资源泄漏:你没有释放EXTENDED_KEY_USAGE结构体,需要用sk_ASN1_OBJECT_pop_free来释放,否则会导致内存泄漏。

额外注意事项

  • OpenSSL的NID_server_authNID_client_auth宏与你提到的XKU_SSL_SERVERXKU_SSL_CLIENT用途完全对应,无需手动对比数值;
  • 如果你需要严格验证“仅包含”这两个用途,一定要检查扩展中的OID数量是否为2,否则证书若包含其他用途会被误判;
  • 所有OpenSSL分配的资源(BIOX509EXTENDED_KEY_USAGE等)都必须手动释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者raidensan

火山引擎 最新活动