虽然OpenSSL已经成为执行SSL和TLS操作的实际库之一,但该库却出奇地不透明,其文档有时也非常糟糕。作为我们最近研究的一部分,我们一直在对HTTPS主机进行互联网范围的扫描,以便更好地理解HTTPS生态系统(对HTTPS证书生态系统的分析,ZMap:快速internet范围扫描及其安全应用程序)。我们对其中许多操作使用OpenSSL,包括解析X.509证书。但是,为了解析和验证证书,我们的团队必须挖掘OpenSSL代码库的部分和多个文档来源,以找到解析每个数据的正确函数。这篇文章的目的是将这些操作记录在一个单独的地方,希望能减轻其他人的痛苦。
如果您发现其他代码片段特别有用,请毫不犹豫地发送它们,我们将更新这篇文章。我想指出,如果你开始开发针对OpenSSL的网络安全,O 'Reilly使用OpenSSL的网络安全是一个非常有用的资源;这本书里有很多我在网上找不到的片段和文件。我还要感谢James Kasten,他帮助找到并记录了其中的几个解决方案。
创建一个OpenSSL X509对象
我们讨论的所有操作都以单个X.509证书或证书的“堆栈”开始。OpenSSL表示一个带有X509struct和证书列表的证书,例如在TLS握手时作为STACK_OF(X509)显示的证书链。考虑到解析和验证来自这里,从如何创建或访问一个X509对象开始似乎是合理的。一些常见的场景是:
1。您已经使用OpenSSL启动了SSL或TLS连接。
在本例中,您可以访问一个OpenSSL SSL结构体,您可以从中提取呈现的证书以及服务器呈现给客户机的整个证书链。在我们的具体示例中,我们使用libevent来执行TLS连接,并可以从libevent bufferevent: SSL * SSL = bufferevent_openssl_get_ssl(bev)访问SSL结构。这显然是不同的,这取决于你如何完成你的连接。但是,一旦您有了SSL上下文,就可以提取服务器证书和提供的链,如下所示:
#include
#include
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);
2。您已经将一个证书作为PEM文件存储在磁盘上。
作为参考,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
#include
#include
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);
3所示。您可以访问内存中的原始证书。
如果您可以访问内存中的证书的原始编码(二进制数据),您可以按照以下方式解析它。如果您已经在数据库或类似的数据存储中存储了原始证书,那么这将非常有用。
#include
#include
#include
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);
4所示。您可以在内存中访问Base64编码的PEM
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中的证书,我们将关注如何从证书中提取有用的数据。我们并没有在每个语句中都包含#include,但是在我们的代码库中使用以下标题:
#include
#include
#include
#include
#include
#include
#include
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 */
}
利用属性提取相关字段:
ULONG COpenSSLCertificate::get_SubjectName(LPSTR lpValue, ULONG *pulLen)
{
int iLen = 0;
int iSubNameLen = 0;
CHAR csSubName[1024] = {0};
CHAR csBuf[256] = {0};
X509_NAME *pSubName = NULL;
if (!m_pX509)
{
return CERT_ERR_INVILIDCALL;
}
if (!pulLen)
{
return CERT_ERR_INVALIDPARAM;
}
pSubName = X509_get_subject_name(m_pX509);
if (!pSubName)
{
return CERT_ERR_FAILED;
}
ZeroMemory(csBuf, 256);
iLen = X509_NAME_get_text_by_NID(pSubName, NID_countryName, csBuf, 256);
if (iLen > 0)
{
strcat_s(csSubName, 1024, "C=");
strcat_s(csSubName, 1024, csBuf);
strcat_s(csSubName, 1024, ", ");
}
ZeroMemory(csBuf, 256);
iLen = X509_NAME_get_text_by_NID(pSubName, NID_organizationName, csBuf, 256);
if (iLen > 0)
{
strcat_s(csSubName, 1024, "O=");
strcat_s(csSubName, 1024, csBuf);
strcat_s(csSubName, 1024, ", ");
}
ZeroMemory(csBuf, 256);
iLen = X509_NAME_get_text_by_NID(pSubName, NID_organizationalUnitName, csBuf, 256);
if (iLen > 0)
{
strcat_s(csSubName, 1024, "OU=");
strcat_s(csSubName, 1024, csBuf);
strcat_s(csSubName, 1024, ", ");
}
ZeroMemory(csBuf, 256);
iLen = X509_NAME_get_text_by_NID(pSubName, NID_commonName, csBuf, 256);
if (iLen > 0)
{
strcat_s(csSubName, 1024, "CN=");
strcat_s(csSubName, 1024, csBuf);
}
if (!lpValue)
{
*pulLen = strlen(csSubName) + 1;
return CERT_ERR_OK;
}
if (*pulLen < strlen(csSubName) + 1)
{
return CERT_ERR_BUFFER_TOO_SMALL;
}
strcpy_s(lpValue, *pulLen, csSubName);
*pulLen = strlen(csSubName);
return CERT_ERR_OK;
}
密码(例如sha - 1)指纹
我们可以用以下代码计算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) >= SERIAL_NUM_LEN ) {
fprintf(stderr, "buffer length shorter than serial number\n");
BN_free(bn);
OPENSSL_free(tmp);
return EXIT_FAILURE;
}
strncpy(serial_number, tmp, SERIAL_NUM_LEN );
BN_free(bn);
OPENSSL_free(tmp);
签名算法
证书上的签名算法存储为OpenSSSL 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作为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证书并不像您所期望的那样是一个布尔操作。证书可以通过几种途径解释为CA证书。因此,使用X509_check_ca比直接检查各种X.509扩展更可靠。任何>= 1的值都被认为是CA证书,而0不是CA证书。
int raw = X509_check_ca(cert);
其他X.509扩展
证书可以包含任何其他任意扩展。下面的代码将循环遍历证书上的所有扩展,并将它们打印出来:
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)
}
混乱的证书链
有时,我们会收到错误的证书链。下面的代码将尝试重新排序证书,根据每个证书的主题和发出者字符串构造一个rational证书链。算法是O(n ^ 2),但我们通常只接受两个或三个证书和majority-case,他们已经在正确的顺序。
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的验证。虽然这在大多数客户端应用程序中可能是有意义的,但是我们常常对可能出现的其他错误感兴趣。我们将自签名证书添加到临时存储中,然后对其进行验证,从而验证自签名证书。这是一种bick hackish,但比重新实现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_num(sk);
unsigned i;
X509 *cert;
printf("Begin Certificate Stack:\n");
for(i=0; i
检查两个证书堆栈是否相同:
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";
}
}
我希望这可以帮助。如前所述,如果你发现其他有用的信息,请告诉我,我们将会更新。类似地,如果你发现任何一个例子都不起作用,请告诉我。
感谢乔丹·怀特黑德的各种修正。