openssl 证书解析例子

虽然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 <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);

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 <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);

3所示。您可以访问内存中的原始证书。

如果您可以访问内存中的证书的原始编码(二进制数据),您可以按照以下方式解析它。如果您已经在数据库或类似的数据存储中存储了原始证书,那么这将非常有用。

#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);

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 <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 */
}

利用属性提取相关字段:

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<len; i++) {
		cert = (X509*) sk_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";
	}
}

我希望这可以帮助。如前所述,如果你发现其他有用的信息,请告诉我,我们将会更新。类似地,如果你发现任何一个例子都不起作用,请告诉我。
感谢乔丹·怀特黑德的各种修正。

猜你喜欢

转载自blog.csdn.net/liyu123__/article/details/81077476