通過OpenSSL解析X509證書基本項

       在之前的文章“通過OpenSSL解碼X509證書文件”裏,講述瞭如何使用OpenSSL將證書文件解碼,得到證書上下文結構體X509的方法。下面我們接着講述如何通過證書上下文結構體X509,獲得想要的證書項。本文先講述如何獲取證書的基本項,後面還有文章介紹如何獲取證書的擴展項。

       下面的代碼,都是假定已經通過解碼證書文件、得到了證書上下文結構體X509。至於如何使用OpenSSL解碼證書文件、得到證書上下文結構體X509,請閱讀之前的文章。

        首先,我們看看關於證書結構體X509定義:

struct x509_st
{
	X509_CINF *cert_info;
	X509_ALGOR *sig_alg;
	ASN1_BIT_STRING *signature;
	int valid;
	int references;
	char *name;
	CRYPTO_EX_DATA ex_data;
	/* These contain copies of various extension values */
	long ex_pathlen;
	long ex_pcpathlen;
	unsigned long ex_flags;
	unsigned long ex_kusage;
	unsigned long ex_xkusage;
	unsigned long ex_nscert;
	ASN1_OCTET_STRING *skid;
	AUTHORITY_KEYID *akid;
	X509_POLICY_CACHE *policy_cache;
	STACK_OF(DIST_POINT) *crldp;
	STACK_OF(GENERAL_NAME) *altname;
	NAME_CONSTRAINTS *nc;
#ifndef OPENSSL_NO_RFC3779
	STACK_OF(IPAddressFamily) *rfc3779_addr;
	struct ASIdentifiers_st *rfc3779_asid;
#endif
#ifndef OPENSSL_NO_SHA
	unsigned char sha1_hash[SHA_DIGEST_LENGTH];
#endif
	X509_CERT_AUX *aux;
} /* X509 */;

typedef struct x509_cinf_st
{
	ASN1_INTEGER *version;		/* [ 0 ] default of v1 */
	ASN1_INTEGER *serialNumber;
	X509_ALGOR *signature;
	X509_NAME *issuer;
	X509_VAL *validity;
	X509_NAME *subject;
	X509_PUBKEY *key;
	ASN1_BIT_STRING *issuerUID;		/* [ 1 ] optional in v2 */
	ASN1_BIT_STRING *subjectUID;		/* [ 2 ] optional in v2 */
	STACK_OF(X509_EXTENSION) *extensions;	/* [ 3 ] optional in v3 */
	ASN1_ENCODING enc;
} X509_CINF;
      我們想要獲取的證書基本項,有些就直接存在於這兩個結構體中。
一、版本號

      通過解碼證書文件,得到證書結構體m_pX509之後,可以通過函數X509_get_version()獲取證書的版本。具體代碼如下:

int ver = X509_get_version(m_pX509);
switch(ver)  	
{
	case 0:		//V1
		//...
	break;
	case 1:		//V2
		//...
	break;
	case 2:		//V3
		//...
	break;
	default:
		//Error!
	break;
}
需要注意的是,0代表V1;1代表V2;2代表V3。目前絕大多數證書都是V3版本。

二、序列號

      同樣,有了m_pX509之後,調用函數X509_get_serialNumber()即可獲得證書的序列號。只是該函數返回的是ASN1_INTEGER類型,需要轉換後才能是我們平常看到的十六進制表示的序列號。具體實現函數如下:

ULONG COpenSSLCertificate::get_SN(LPSTR lptcSN, ULONG *pulLen)
{
	ULONG ulRet = CERT_ERR_OK;
	ASN1_INTEGER *asn1_i = NULL;
	BIGNUM *bignum = NULL;
	char *serial = NULL;

	if (!m_pX509)
	{
	    return CERT_ERR_INVILIDCALL;
	}
	if (!pulLen)
	{
		return CERT_ERR_INVALIDPARAM;
	}
	asn1_i = X509_get_serialNumber(m_pX509);
	bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
	if (bignum == NULL) 
	{
		ulRet = CERT_ERR_FAILED;
		goto FREE_MEMORY;
	}
	serial = BN_bn2hex(bignum);
    if (serial == NULL) 
	{
		ulRet = CERT_ERR_FAILED;
		goto FREE_MEMORY;
	}
    BN_free(bignum);
	if (!lptcSN)
	{
		*pulLen = strlen(serial) + 1;
		ulRet = CERT_ERR_OK;
		goto FREE_MEMORY;
	}
	if (*pulLen < strlen(serial) + 1)
	{
		ulRet = CERT_ERR_BUFFER_TOO_SMALL;
		goto FREE_MEMORY;
	}
    strcpy_s(lptcSN, *pulLen, serial);
	*pulLen = strlen(serial);
FREE_MEMORY:
    OPENSSL_free(serial);	
	return ulRet;
}

三、公鑰算法(證書算法)

      要想獲取證書公鑰算法,需要先調用函數X509_get_pubkey()得到公鑰屬性結構體,然後通過type字段來判斷公鑰的算法類型。具體實現函數如下:

ULONG COpenSSLCertificate::get_KeyType(ULONG* pulType)
{
	EVP_PKEY *pk = NULL;
	stack_st_X509* chain = NULL;
	X509_EXTENSION *pex = NULL;
	
	if (!m_pX509)
	{
		return CERT_ERR_INVILIDCALL;
	}
	if (!pulType)
	{
		return CERT_ERR_INVALIDPARAM;
	}

	pk = X509_get_pubkey(m_pX509);
	if (!pk)
	{
		return CERT_ERR_FAILED;
	}

	if (EVP_PKEY_RSA == pk->type)
	{
		*pulType = CERT_KEY_ALG_RSA;
	}
	else if (EVP_PKEY_EC == pk->type)
	{
		*pulType = CERT_KEY_ALG_ECC;
	}
	else if (EVP_PKEY_DSA == pk->type)
	{
		*pulType = CERT_KEY_ALG_DSA;
	}
	else if (EVP_PKEY_DH == pk->type)
	{
		*pulType = CERT_KEY_ALG_DH;
	}
	else
	{
		return CERT_KEY_ALG_UNKNOWN;
	}		
	
	return CERT_ERR_OK;
}
目前常見的證書算法爲RSA和ECC,ECC在國內又成爲SM2。SM2是國家密碼管理局基於橢圓算法(ECC)制定的國內非對稱算法標準。

四、證書用途

      證書從用途來分,分爲“簽名證書”和“加密證書”兩大類。“簽名證書”的公鑰用來驗證簽名,而“加密證書”的公鑰則用來加密數據。我們可以通過調用X509中的ex_kusage字段來判斷證書的用途,具體函數實現如下:

ULONG COpenSSLCertificate::get_KeyUsage(ULONG* lpUsage)
{
	ULONG lKeyUsage = 0;

	if (!m_pX509)
	{
		return CERT_ERR_INVILIDCALL;
	}
	if (!lpUsage)
	{
		return CERT_ERR_INVALIDPARAM;
	}

	*lpUsage = CERT_USAGE_UNKNOWN;
	
	//X509_check_ca() MUST be called!
	X509_check_ca(m_pX509);
	lKeyUsage = m_pX509->ex_kusage;
	if ((lKeyUsage & KU_DATA_ENCIPHERMENT) == KU_DATA_ENCIPHERMENT)
	{
		*lpUsage = CERT_USAGE_EXCH;<span style="white-space:pre">	</span>//加密證書
	}
	else if ((lKeyUsage & KU_DIGITAL_SIGNATURE) == KU_DIGITAL_SIGNATURE)
	{
		*lpUsage = CERT_USAGE_SIGN;<span style="white-space:pre">	</span>//簽名證書
	}

	return CERT_ERR_OK;
}

五、簽名算法

      證書的簽名算法,是指證書用來簽名時使用的算法(包含HASH算法)。簽名算法用結構體X509中sig_alg字段來表示,可以通過sig_alg的子字段algorithm返回簽名算法對象,從而得到簽名算法的Oid。首先,簽名算法的Oid常見得定義如下:

/*	Certificate siganture alg */
#define CERT_SIGNATURE_ALG_RSA_RSA			"1.2.840.113549.1.1.1"
#define CERT_SIGNATURE_ALG_MD2RSA			"1.2.840.113549.1.1.2"
#define CERT_SIGNATURE_ALG_MD4RSA			"1.2.840.113549.1.1.3"
#define CERT_SIGNATURE_ALG_MD5RSA			"1.2.840.113549.1.1.4"
#define CERT_SIGNATURE_ALG_SHA1RSA			"1.2.840.113549.1.1.5"
#define CERT_SIGNATURE_ALG_SM3SM2			"1.2.156.10197.1.501"

      獲取簽名算法Oid的具體實現函數如下:

ULONG COpenSSLCertificate::get_SignatureAlgOid(LPSTR lpscOid, ULONG *pulLen)
{
	char oid[128] = {0};
	ASN1_OBJECT* salg  = NULL;

	if (!m_pX509)
	{
		return CERT_ERR_INVILIDCALL;
	}
	if (!pulLen)
	{
		return CERT_ERR_INVALIDPARAM;
	}

	salg = m_pX509->sig_alg->algorithm;
	OBJ_obj2txt(oid, 128, salg, 1);
	if (!lpscOid)
	{
		*pulLen = strlen(oid) + 1;
		return CERT_ERR_OK;
	}
	if (*pulLen < strlen(oid) + 1)
	{
		return CERT_ERR_BUFFER_TOO_SMALL;
	}

	strcpy_s(lpscOid, *pulLen, oid);
	*pulLen = strlen(oid) + 1;
	return CERT_ERR_OK;
}
由於Windows對SM2/SM3算法還沒有定義,所以對於ECC(SM2)證書,Windows直接顯示簽名算法的Oid:“1.2.156.10197.1.501”,如下圖所示:

六、頒發者

      關於頒發者,我們可以通過調用函數X509_get_issuer_name()獲取屬性。不過該函數返回的是X509_NAME類型,需要調用函數X509_NAME_get_text_by_NID()將其轉化爲ASCII字符形式。具體通過下面函數實現:

ULONG COpenSSLCertificate::get_Issuer(LPSTR lpValue, ULONG *pulLen)
{
	int nNameLen = 512;
	CHAR csCommonName[512] = {0};
	X509_NAME *pCommonName = NULL;

	if (!m_pX509)
	{
		return CERT_ERR_INVILIDCALL;
	}
	if (!pulLen)
	{
		return CERT_ERR_INVALIDPARAM;
	}

	pCommonName = X509_get_issuer_name(m_pX509);
	if (!pCommonName)
	{
		return CERT_ERR_FAILED;
	}
	nNameLen = X509_NAME_get_text_by_NID(pCommonName, NID_commonName, csCommonName, nNameLen);
	if (-1 == nNameLen)
	{
		return CERT_ERR_FAILED;
	};	
	if (!lpValue)
	{
		*pulLen = nNameLen + 1;
		return CERT_ERR_OK;
	}
	if (*pulLen < (ULONG)nNameLen + 1)
	{
		return CERT_ERR_BUFFER_TOO_SMALL;
	}

	strcpy_s(lpValue, *pulLen, csCommonName);
	*pulLen = nNameLen;
	return CERT_ERR_OK;
}

七、使用者

      關於證書使用者,我們可以通過調用函數X509_get_subject_name)獲取屬性。同樣,該函數返回的是X509_NAME類型,需要調用函數X509_NAME_get_text_by_NID()將其轉化爲ASCII字符形式。具體通過下面函數實現:

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

八、有效期限

      要獲取證書的有效期屬性,需要通過調用函數X509_get_notBefore()和X509_get_notAfter()來實現。而且這兩個函數返回的時間是time_t類型,需要轉化爲SYSTEMTIME類型。具體實現函數如下:

ULONG COpenSSLCertificate::get_ValidDate(SYSTEMTIME *ptmStart, SYSTEMTIME *ptmEnd)
{
	int err = 0;
	ASN1_TIME *start = NULL;
	ASN1_TIME *end = NULL;
	time_t ttStart = {0};
	time_t ttEnd = {0};
	LONGLONG nLLStart = 0;
	LONGLONG nLLEnd = 0;
	FILETIME ftStart = {0};
	FILETIME ftEnd = {0};

	if (!m_pX509)
	{
		return CERT_ERR_INVALIDPARAM;
	}

	start = X509_get_notBefore(m_pX509);
	end = X509_get_notAfter(m_pX509);
	
	ttStart = ASN1_TIME_get(start, &err);
	ttEnd = ASN1_TIME_get(end, &err);	
    nLLStart = Int32x32To64(ttStart, 10000000) + 116444736000000000;
    nLLEnd = Int32x32To64(ttEnd, 10000000) + 116444736000000000;

    ftStart.dwLowDateTime = (DWORD)nLLStart;
    ftStart.dwHighDateTime = (DWORD)(nLLStart >> 32);
    ftEnd.dwLowDateTime = (DWORD)nLLEnd;
    ftEnd.dwHighDateTime = (DWORD)(nLLEnd >> 32);

    FileTimeToSystemTime(&ftStart, ptmStart);
    FileTimeToSystemTime(&ftEnd, ptmEnd);

	return 0;
}

      至此,X509證書的基本項通過OpenSLL均已解析完畢!如需獲取證書的擴展項或者公鑰等數據,請關注後續博文。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章