虽然 OpenSSL 已成为执行 SSL 和 TLS 操作的事实上的库之一,但该库出人意料地不透明,其文档有时也很糟糕。作为我们最近研究的一部分,我们一直在对 HTTPS 主机进行互联网范围的扫描,以更好地了解 HTTPS 生态系统(HTTPS 证书生态系统分析,ZMap:快速互联网范围扫描及其安全应用程序)。我们将 OpenSSL 用于其中许多操作,包括解析 X.509 证书。然而,为了解析和验证证书,我们的团队必须挖掘 OpenSSL 代码库的部分内容和多个文档来源,以找到解析每条数据的正确函数。这篇文章旨在在一个位置记录许多这些操作,以期减轻其他人的痛苦过程。
如果您发现其他代码段特别有用,请不要犹豫,将它们一起发送,我们会更新帖子。我想指出,如果您开始针对 OpenSSL 进行开发,O’Reilly 的 Network Security with OpenSSL 是一个非常有用的资源;这本书包含许多我在网上找不到的片段和文档。我还要感谢 James Kasten,他帮助找到并记录了其中的几个解决方案。
我们讨论的所有操作都从单个 X.509 证书或证书“堆栈”开始。 OpenSSL 表示具有 X509 结构和证书列表的单个证书,例如在 TLS 握手期间作为 STACK_OF(X509) 呈现的证书链。 鉴于解析和验证源于这里,从如何创建或访问 X509 对象开始似乎是合理的。 一些常见的场景是:
在这种情况下,您可以访问 OpenSSL SSL 结构,您可以从中提取提供的证书以及服务器提供给客户端的整个证书链。 在我们的特定情况下,我们使用 libevent 来执行 TLS 连接,并且可以从 libevent bufferevent 访问 SSL 结构:SSL *ssl = bufferevent_openssl_get_ssl(bev)。 这显然会有所不同,具体取决于您完成连接的方式。 但是,一旦您拥有 SSL 上下文,就可以按如下方式提取服务器证书和提供的链:
#include <openssl/x509.h>
#include <openssl/x509v3.h>
X509 *cert = SSL_get_peer_certificate(ssl);
STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl);
我们发现,有时,即使提供了客户端证书(服务器证书通常作为堆栈中的第一个证书与其余链一起提供),OpenSSL 也会产生一个空的证书链(SSL_get_peer_cert_chain 将返回 NULL)。 我们不清楚为什么会发生这种情况,但这不是一个交易破坏者,因为创建新的证书堆栈很容易:
X509 *cert = SSL_get_peer_certificate(ssl);
STACK_OF(X509) *sk = sk_X509_new_null();
sk_X509_push(sk, cert);
作为参考,PEM 文件是 X.509 证书的 Base64 编码版本,它应该类似于以下内容:
-----BEGIN CERTIFICATE-----
MIIHIDCCBgigAwIBAgIIMrM8cLO76sYwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
iftrJvzAOMAPY5b/klZvqH6Ddubg/hUVPkiv4mr5MfWfglCQdFF1EBGNoZSFAU7y
ZkGENAvDmv+5xVCZELeiWA2PoNV4m/SW6NHrF7gz4MwQssqP9dGMbKPOF/D2nxic
TnD5WkGMCWpLgqDWWRoOrt6xf0BPWukQBDMHULlZgXzNtoGlEnwztLlnf0I/WWIS
eBSyDTeFJfopvoqXuws23X486fdKcCAV1n/Nl6y2z+uVvcyTRxY2/jegmV0n0kHf
gfcKzw==
-----END CERTIFICATE-----
在这种情况下,您可以按如下方式访问证书:
#include <stdio.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
FILE *fp = fopen(path, "r");
if (!fp) {
fprintf(stderr, "unable to open: %s\n", path);
return EXIT_FAILURE;
}
X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL);
if (!cert) {
fprintf(stderr, "unable to parse certificate in: %s\n", path);
fclose(fp);
return EXIT_FAILURE;
}
// any additional processing would go here..
X509_free(cert);
fclose(fp);
如果您可以访问内存中证书的原始编码,则可以按如下方式对其进行解析。 如果您将原始证书存储在数据库或类似的数据存储中,这将非常有用。
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>
const unsigned char *data = ... ;
size_t len = ... ;
X509 *cert = d2i_X509(NULL, &data, len);
if (!cert) {
fprintf(stderr, "unable to parse certificate in memory\n");
return EXIT_FAILURE;
}
// any additional processing would go here..
X509_free(cert);
char* pemCertString = ..... (includes "-----BEGIN/END CERTIFICATE-----")
size_t certLen = strlen(pemCertString);
BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, pemCertString, certLen);
X509* certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL);
if (!certX509) {
fprintf(stderr, "unable to parse certificate in memory\n");
return EXIT_FAILURE;
}
// do stuff
BIO_free(certBio);
X509_free(certX509);
现在我们可以访问 OpenSSL 中的证书,我们将专注于如何从证书中提取有用的数据。 我们不会在每个语句中都包含 #includes,而是在我们的代码库中使用以下标头:
#include <openssl/x509v3.h>
#include <openssl/bn.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
OpenSSL_add_all_algorithms();
您还需要 OpenSSL 库的开发版本并使用 -lssl 进行编译。
证书主题和颁发者可以轻松提取并表示为单个字符串,如下所示:
char *subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
char *issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
这些可以通过调用 OPENSSL_free 来释放。
默认情况下,主题和发行者以以下形式返回:
/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.google.com
如果您想将这些转换为更传统的 DN,例如:
C=US, ST=Texas, L=Austin, O=Polycom Inc., OU=Video Division, CN=a.digitalnetbr.net
它们可以使用以下代码进行转换:
int i, curr_spot = 0;
char *s = tmpBuf + 1; /* skip the first slash */
char *c = s;
while (1) {
if (((*s == '/') && ((s[1] >= 'A') && (s[1] <= 'Z') &&
((s[2] == '=') || ((s[2] >= 'A') && (s[2] <= 'Z')
&& (s[3] == '='))))) || (*s == '\0')) {
i = s - c;
strncpy(destination + curr_spot, c, i);
curr_spot += i;
assert(curr_spot < size);
c = s + 1; /* skip following slash */
if (*s != '\0') {
strncpy(destination + curr_spot, ", ", 2);
curr_spot += 2;
}
}
if (*s == '\0')
break;
++s;
}
还可以从主题中提取特定元素。 例如,以下代码将遍历主题中的所有值:
X509_NAME *subj = X509_get_subject_name(cert);
for (int i = 0; i < X509_NAME_entry_count(subj); i++) {
X509_NAME_ENTRY *e = X509_NAME_get_entry(subj, i);
ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
char *str = ASN1_STRING_data(d);
}
or
for (;;) {
int lastpos = X509_NAME_get_index_by_NID(subj, NID_commonName, lastpos);
if (lastpos == -1)
break;
X509_NAME_ENTRY *e = X509_NAME_get_entry(subj, lastpos);
/* Do something with e */
}
我们可以使用以下代码计算 SHA-1 指纹(或任何其他指纹):
#define SHA1LEN 20
char buf[SHA1LEN];
const EVP_MD *digest = EVP_sha1();
unsigned len;
int rc = X509_digest(cert, digest, (unsigned char*) buf, &len);
if (rc == 0 || len != SHA1LEN) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
这将产生原始指纹。 这可以转换为人类可读的十六进制版本,如下所示:
void hex_encode(unsigned char* readbuf, void *writebuf, size_t len)
{
for(size_t i=0; i < len; i++) {
char *l = (char*) (2*i + ((intptr_t) writebuf));
sprintf(l, "%02x", readbuf[i]);
}
}
char strbuf[2*SHA1LEN+1];
hex_encode(buf, strbuf, SHA1LEN);
解析证书版本很简单; 唯一奇怪的是它是零索引的:
int version = ((int) X509_get_version(cert)) + 1;
序列号可以是任意大的,也可以是正数或负数。 因此,我们在处理中将其作为字符串处理,而不是典型的整数。
#define SERIAL_NUM_LEN 1000;
char serial_number[SERIAL_NUM_LEN+1];
ASN1_INTEGER *serial = X509_get_serialNumber(cert);
BIGNUM *bn = ASN1_INTEGER_to_BN(serial, NULL);
if (!bn) {
fprintf(stderr, "unable to convert ASN1INTEGER to BN\n");
return EXIT_FAILURE;
}
char *tmp = BN_bn2dec(bn);
if (!tmp) {
fprintf(stderr, "unable to convert BN to decimal string.\n");
BN_free(bn);
return EXIT_FAILURE;
}
if (strlen(tmp) >= len) {
fprintf(stderr, "buffer length shorter than serial number\n");
BN_free(bn);
OPENSSL_free(tmp);
return EXIT_FAILURE;
}
strncpy(buf, tmp, len);
BN_free(bn);
OPENSSL_free(tmp);
证书上的签名算法存储为 OpenSSL NID:
int pkey_nid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);
if (pkey_nid == NID_undef) {
fprintf(stderr, "unable to find specified signature algorithm name.\n");
return EXIT_FAILURE;
}
这可以转换为字符串表示(短名称或长描述):
char sigalgo_name[SIG_ALGO_LEN+1];
const char* sslbuf = OBJ_nid2ln(pkey_nid);
if (strlen(sslbuf) > PUBKEY_ALGO_LEN) {
fprintf(stderr, "public key algorithm name longer than allocated buffer.\n");
return EXIT_FAILURE;
}
strncpy(buf, sslbuf, PUBKEY_ALGO_LEN);
这将产生一个字符串,例如 sha1WithRSAEncryption 或 md5WithRSAEncryption。
解析证书上的公钥是特定于类型的。 在这里,我们提供了有关如何提取包含的密钥类型以及解析 RSA 和 DSA 密钥的信息:
char pubkey_algoname[PUBKEY_ALGO_LEN];
int pubkey_algonid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);
if (pubkey_algonid == NID_undef) {
fprintf(stderr, "unable to find specified public key algorithm name.\n");
return EXIT_FAILURE;
}
const char* sslbuf = OBJ_nid2ln(pubkey_algonid);
assert(strlen(sslbuf) < PUBKEY_ALGO_LEN);
strncpy(buf, sslbuf, PUBKEY_ALGO_LEN);
if (pubkey_algonid == NID_rsaEncryption || pubkey_algonid == NID_dsa) {
EVP_PKEY *pkey = X509_get_pubkey(cert);
IFNULL_FAIL(pkey, "unable to extract public key from certificate");
RSA *rsa_key;
DSA *dsa_key;
char *rsa_e_dec, *rsa_n_hex, *dsa_p_hex, *dsa_p_hex,
*dsa_q_hex, *dsa_g_hex, *dsa_y_hex;
switch(pubkey_algonid) {
case NID_rsaEncryption:
rsa_key = pkey->pkey.rsa;
IFNULL_FAIL(rsa_key, "unable to extract RSA public key");
rsa_e_dec = BN_bn2dec(rsa_key->e);
IFNULL_FAIL(rsa_e_dec, "unable to extract rsa exponent");
rsa_n_hex = BN_bn2hex(rsa_key->n);
IFNULL_FAIL(rsa_n_hex, "unable to extract rsa modulus");
break;
case NID_dsa:
dsa_key = pkey->pkey.dsa;
IFNULL_FAIL(dsa_key, "unable to extract DSA pkey");
dsa_p_hex = BN_bn2hex(dsa_key->p);
IFNULL_FAIL(dsa_p_hex, "unable to extract DSA p");
dsa_q_hex = BN_bn2hex(dsa_key->q);
IFNULL_FAIL(dsa_q_hex, "unable to extract DSA q");
dsa_g_hex = BN_bn2hex(dsa_key->g);
IFNULL_FAIL(dsa_g_hex, "unable to extract DSA g");
dsa_y_hex = BN_bn2hex(dsa_key->pub_key);
IFNULL_FAIL(dsa_y_hex, "unable to extract DSA y");
break;
default:
break;
}
EVP_PKEY_free(pkey);
}
OpenSSL 将 not-valid-after (expiration) 和 not-valid-before 表示为 ASN1_TIME 对象,可以提取如下:
ASN1_TIME *not_before = X509_get_notBefore(cert);
ASN1_TIME *not_after = X509_get_notAfter(cert);
这些可以使用以下代码转换为 ISO-8601 时间戳:
#define DATE_LEN 128
int convert_ASN1TIME(ASN1_TIME *t, char* buf, size_t len)
{
int rc;
BIO *b = BIO_new(BIO_s_mem());
rc = ASN1_TIME_print(b, t);
if (rc <= 0) {
log_error("fetchdaemon", "ASN1_TIME_print failed or wrote no data.\n");
BIO_free(b);
return EXIT_FAILURE;
}
rc = BIO_gets(b, buf, len);
if (rc <= 0) {
log_error("fetchdaemon", "BIO_gets call failed to transfer contents to buf");
BIO_free(b);
return EXIT_FAILURE;
}
BIO_free(b);
return EXIT_SUCCESS;
}
char not_after_str[DATE_LEN];
convert_ASN1TIME(not_after, not_after_str, DATE_LEN);
char not_before_str[DATE_LEN];
convert_ASN1TIME(not_before, not_before_str, DATE_LEN);
检查证书是否是有效的 CA 证书并不是您所期望的布尔运算。 可以通过多种途径将证书解释为 CA 证书。 因此,与其直接检查各种 X.509 扩展名,不如使用 X509_check_ca。 任何 >= 1 的值都被视为 CA 证书,而 0 不是 CA 证书。
int raw = X509_check_ca(cert);
证书可以包含任何其他任意扩展名。 以下代码将遍历证书上的所有扩展名并将它们打印出来:
STACK_OF(X509_EXTENSION) *exts = cert->cert_info->extensions;
int num_of_exts;
if (exts) {
num_of_exts = sk_X509_EXTENSION_num(exts);
} else {
num_of_exts = 0
}
IFNEG_FAIL(num_of_exts, "error parsing number of X509v3 extensions.");
for (int i=0; i < num_of_exts; i++) {
X509_EXTENSION *ex = sk_X509_EXTENSION_value(exts, i);
IFNULL_FAIL(ex, "unable to extract extension from stack");
ASN1_OBJECT *obj = X509_EXTENSION_get_object(ex);
IFNULL_FAIL(obj, "unable to extract ASN1 object from extension");
BIO *ext_bio = BIO_new(BIO_s_mem());
IFNULL_FAIL(ext_bio, "unable to allocate memory for extension value BIO");
if (!X509V3_EXT_print(ext_bio, ex, 0, 0)) {
M_ASN1_OCTET_STRING_print(ext_bio, ex->value);
}
BUF_MEM *bptr;
BIO_get_mem_ptr(ext_bio, &bptr);
BIO_set_close(ext_bio, BIO_NOCLOSE);
// remove newlines
int lastchar = bptr->length;
if (lastchar > 1 && (bptr->data[lastchar-1] == '\n' || bptr->data[lastchar-1] == '\r')) {
bptr->data[lastchar-1] = (char) 0;
}
if (lastchar > 0 && (bptr->data[lastchar] == '\n' || bptr->data[lastchar] == '\r')) {
bptr->data[lastchar] = (char) 0;
}
BIO_free(ext_bio);
unsigned nid = OBJ_obj2nid(obj);
if (nid == NID_undef) {
// no lookup found for the provided OID so nid came back as undefined.
char extname[EXTNAME_LEN];
OBJ_obj2txt(extname, EXTNAME_LEN, (const ASN1_OBJECT *) obj, 1);
printf("extension name is %s\n", extname);
} else {
// the OID translated to a NID which implies that the OID has a known sn/ln
const char *c_ext_name = OBJ_nid2ln(nid);
IFNULL_FAIL(c_ext_name, "invalid X509v3 extension name");
printf("extension name is %s\n", c_ext_name);
}
printf("extension length is %u\n", bptr->length)
printf("extension value is %s\n", bptr->data)
}
有时,我们会收到乱序的证书链。 以下代码将尝试重新排序证书以根据每个证书的主题和颁发者字符串构建合理的证书链。 算法是 O(n^2),但我们通常只收到两到三个证书,并且在大多数情况下,它们已经是正确的顺序。
STACK_OF(X509) *r_sk = sk_X509_new_null();
sk_X509_push(r_sk, sk_X509_value(st, 0));
for (int i=1; i < sk_X509_num(st); i++) {
X509 *prev = sk_X509_value(r_sk, i-1);
X509 *next = NULL;
for (int j=1; j < sk_X509_num(st); j++) {
X509 *cand = sk_X509_value(st, j);
if (!X509_NAME_cmp(cand->cert_info->subject, prev->cert_info->issuer)
|| j == sk_X509_num(st) - 1) {
next = cand;
break;
}
}
if (next) {
sk_X509_push(r_sk, next);
} else {
// we're unable to figure out the correct stack so just use the original one provided.
sk_X509_free(r_sk);
r_sk = sk_X509_dup(st);
break;
}
}
在我们的扫描中,我们经常使用多个 CA 存储来模拟不同的浏览器。 在这里,我们描述了我们如何创建专门的商店并对其进行验证。
我们可以使用以下命令基于特定文件创建存储:
X509_STORE *s = X509_STORE_new();
if (s == NULL) {
fprintf(stderr, "unable to create new X509 store.\n");
return NULL;
}
int rc = X509_STORE_load_locations(s, store_path, NULL);
if (rc != 1) {
fprintf(stderr, "unable to load certificates at %s to store\n", store_path);
X509_STORE_free(s);
return NULL;
}
return s;
然后使用以下命令针对商店验证证书:
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (!ctx) {
fprintf(stderr, "unable to create STORE CTX\n");
return -1;
}
if (X509_STORE_CTX_init(ctx, store, cert, st) != 1) {
fprintf(stderr, "unable to initialize STORE CTX.\n");
X509_STORE_CTX_free(ctx);
return -1;
}
int rc = X509_verify_cert(ctx);
X509_STORE_CTX_free(ctx);
return rc;
值得注意的是,自签名证书总是无法通过 OpenSSL 的验证。 虽然这在大多数客户端应用程序中可能有意义,但我们经常对可能存在的其他错误感兴趣。 我们通过将自签名证书添加到临时存储中然后根据它进行验证来验证自签名证书。 这是一个小技巧,但比重新实现 OpenSSL 的验证技术要容易得多。
X509_STORE *s = X509_STORE_new();
int num = sk_X509_num(sk);
X509 *top = sk_X509_value(st, num-1);
X509_STORE_add_cert(s, top);
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(ctx, s, cert, st);
int rc = X509_verify_cert(ctx);
if (rc == 1) {
// validated OK. either trusted or self signed.
} else {
// validation failed
int err = X509_STORE_CTX_get_error(ctx);
}
// any additional processing..
X509_STORE_CTX_free(ctx);
X509_STORE_free(s);
有时您还会发现,您只需要检查证书是否是由可信来源颁发的,而不仅仅是考虑当前是否有效,这可以使用 X509_check_issued 来完成。 例如,如果您想检查证书是否是自签名的:
if (X509_check_issued(cert, cert) == X509_V_OK) {
is_self_signed = 1;
} else {
is_self_signed = 0;
}
还有其他几个函数用于故障排除,在您针对 OpenSSL 开发代码时可能会有所帮助。
打印出证书的基本信息:
#define MAX_LENGTH 1024
void print_certificate(X509* cert) {
char subj[MAX_LENGTH+1];
char issuer[MAX_LENGTH+1];
X509_NAME_oneline(X509_get_subject_name(cert), subj, MAX_LENGTH);
X509_NAME_oneline(X509_get_issuer_name(cert), issuer, MAX_LENGTH);
printf("certificate: %s\n", subj);
printf("\tissuer: %s\n\n", issuer);
}
打印出给定堆栈中的每个证书:
void print_stack(STACK_OF(X509)* sk)
{
unsigned len = sk_X509_num(sk);
printf("Begin Certificate Stack:\n");
for(unsigned i=0; i<len; i++) {
X509 *cert = sk_X509_value(sk, i);
print_certificate(cert);
}
printf("End Certificate Stack\n");
}
检查两个证书堆栈是否相同:
int certparse_sk_X509_cmp(STACK_OF(X509) *a, STACK_OF(X509) *b)
{
int a_len = sk_X509_num(a);
int b_len = sk_X509_num(b);
if (a_len != b_len) {
return 1;
}
for (int i=0; i < a_len; i++) {
if (X509_cmp(sk_X509_value(a, i), sk_X509_value(b, i))) {
return 1;
}
}
return 0;
}
检查证书上的主题和颁发者字符串是否相同:
int certparse_subjeqissuer(X509 *cert)
{
char *s = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
char *i = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
int rc = strcmp(s, i);
OPENSSL_free(s);
OPENSSL_free(i);
return (!rc);
}
将 OpenSSL 错误常量转换为可读字符串:
const char* get_validation_errstr(long e) {
switch ((int) e) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
return "ERR_UNABLE_TO_GET_ISSUER_CERT";
case X509_V_ERR_UNABLE_TO_GET_CRL:
return "ERR_UNABLE_TO_GET_CRL";
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
return "ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE";
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
return "ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE";
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
return "ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY";
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
return "ERR_CERT_SIGNATURE_FAILURE";
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
return "ERR_CRL_SIGNATURE_FAILURE";
case X509_V_ERR_CERT_NOT_YET_VALID:
return "ERR_CERT_NOT_YET_VALID";
case X509_V_ERR_CERT_HAS_EXPIRED:
return "ERR_CERT_HAS_EXPIRED";
case X509_V_ERR_CRL_NOT_YET_VALID:
return "ERR_CRL_NOT_YET_VALID";
case X509_V_ERR_CRL_HAS_EXPIRED:
return "ERR_CRL_HAS_EXPIRED";
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
return "ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD";
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
return "ERR_ERROR_IN_CERT_NOT_AFTER_FIELD";
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
return "ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD";
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
return "ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD";
case X509_V_ERR_OUT_OF_MEM:
return "ERR_OUT_OF_MEM";
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
return "ERR_DEPTH_ZERO_SELF_SIGNED_CERT";
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
return "ERR_SELF_SIGNED_CERT_IN_CHAIN";
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
return "ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY";
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
return "ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE";
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
return "ERR_CERT_CHAIN_TOO_LONG";
case X509_V_ERR_CERT_REVOKED:
return "ERR_CERT_REVOKED";
case X509_V_ERR_INVALID_CA:
return "ERR_INVALID_CA";
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
return "ERR_PATH_LENGTH_EXCEEDED";
case X509_V_ERR_INVALID_PURPOSE:
return "ERR_INVALID_PURPOSE";
case X509_V_ERR_CERT_UNTRUSTED:
return "ERR_CERT_UNTRUSTED";
case X509_V_ERR_CERT_REJECTED:
return "ERR_CERT_REJECTED";
case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
return "ERR_SUBJECT_ISSUER_MISMATCH";
case X509_V_ERR_AKID_SKID_MISMATCH:
return "ERR_AKID_SKID_MISMATCH";
case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
return "ERR_AKID_ISSUER_SERIAL_MISMATCH";
case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
return "ERR_KEYUSAGE_NO_CERTSIGN";
case X509_V_ERR_INVALID_EXTENSION:
return "ERR_INVALID_EXTENSION";
case X509_V_ERR_INVALID_POLICY_EXTENSION:
return "ERR_INVALID_POLICY_EXTENSION";
case X509_V_ERR_NO_EXPLICIT_POLICY:
return "ERR_NO_EXPLICIT_POLICY";
case X509_V_ERR_APPLICATION_VERIFICATION:
return "ERR_APPLICATION_VERIFICATION";
default:
return "ERR_UNKNOWN";
}
}
我希望这有帮助。 正如我之前所说,如果您发现其他有用的信息,请告诉我,我们会及时更新。 同样,如果您发现任何示例不起作用,请告诉我。