在之前的文章“通過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均已解析完畢!如需獲取證書的擴展項或者公鑰等數據,請關注後續博文。