原文鏈接: http://www.cangfengzhe.com/wangluoanquan/37.html
這篇文章主要介紹PKI公鑰體系中非常核心元素——數字證書的編程解析。在SSL,SET等安全協議通信時,數字證書用於通信雙方進行身份認證,並且依靠數字證書和非對稱加密算法加密傳輸數據,或者根據數字證書協商通信雙方的共享密鑰。所以,用戶想要開發自己的應用,實現身份認證,必須對數字證書進行解析。根據解析結果,符合一定條件的終端用戶,才可以接入。
1、證書格式介紹
現有的數字證書大都采用了X.509規范,主要由一下信息組成:版本號,證書序列號,有效期(證書生效時間和失效時間),用戶信息(姓名、單位、組織、城市、國家等),頒發者信息,其他擴展信息,擁有者的公鑰,CA對證書整體的簽名,如圖1所示。
圖1 X.509 V3數字證書格式
OPENSSL開發包中實現了對X.509證書解析的所有操作,如獲得證書的版本、公鑰、擁有者信息、頒發者信息、有效期等,下面就向大家介紹如何通過編程,解析出我們需要的證書信息。
2、證書解析編程實現
2.1 數據結構介紹
X.509證書在OPENSSL中定了專門的數據結構,方便用戶對其操作,其結構如下所示:
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;
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;
struct AUTHORITY_KEYID_st *akid;
X509_POLICY_CACHE *policy_cache;
#ifndef OPENSSL_NO_SHA
unsigned char sha1_hash[SHA_DIGEST_LENGTH];
#endif
X509_CERT_AUX *aux;
};
該結構表示了一個完整的數字證書。各項意義如下:
cert_info:證書主體信息;
sig_alg:簽名算法;
signature:簽名值,存放CA對該證書簽名的結果;
valid:是否是合法證書,1為合法,0為未知;
references:引用次數,被引用一次則加一;
name:證書持有者信息;
ex_data:擴展數據結構,用於存放用戶自定義的信息;
ex_pathlen:證書路徑長度;
ex_kusage:密鑰用法;
ex_xkusage:擴展密鑰用法;
ex_nscert:Netscape證書類型;
skid:主體密鑰標識;
akid:頒發者密鑰標識;
policy_cache:各種策略緩存;
sha1_hash:存放證書的sha1摘要值;
aux:輔助信息;
其中,證書主體信息—X509_CINF結構體定義如下:
typedef struct x509_cinf_st
{
ASN1_INTEGER *version; //證書版本
ASN1_INTEGER *serialNumber; //序列號
X509_ALGOR *signature; //簽名算法
X509_NAME *issuer; //頒發者
X509_VAL *validity; // 有效時間
X509_NAME *subject; // 持有者
X509_PUBKEY *key; // 公鑰
ASN1_BIT_STRING *issuerUID; // 頒發者唯一標識
ASN1_BIT_STRING *subjectUID; // 持有者唯一標識
STACK_OF(X509_EXTENSION) *extensions; // 擴展項
} X509_CINF;
2.2 函數介紹
根據上述結構體可知,我們可以通過編程,讀取結構體中的證書信息,下面介紹一下幾個常用的函數。
(1)編碼轉換函數
數字證書分為DER編碼和PEM編碼,所以對應的操作是不一樣的。對於DER編碼的證書,我們可以通過函數:X509 * d2i_X509(x509 **cert , unsigned char **d , int len),返回一個X.509的結構體指針。而對於PEM編碼的證書,我沒找到一個函數來實現編碼轉換,但可以通過OPENSSL提供的BIO函數,實現這一功能:先調用BIO_new_file() 返回一個BIO結構體,然后通過 PEM_read_bio_X509() 返回一個X.509結構體。
(2)獲得證書信息
其實獲得證書信息的操作,僅僅是解析X509和X509_CINF結構體的操作,可以得到如:證書版本,頒發者信息,證書擁有者信息,有效期,證書公鑰等信息,主要函數如下:
X509_get_version(); //獲得證書版本;
X509_get_issuer_name(); //獲得證書頒發者信息
X509_get_subjiect_name(); //獲得證書擁有者信息
X509_get_notBefore(); //獲得證書起始日期
X509_get_notAfter(); //獲得證書終止日期
X509_get_pubkey(); //獲得證書公鑰
其中,函數具體的參數和使用,結合下面編程代碼向大家介紹。
2.3編程實現
通過上述的函數和結構體的介紹,下面編程實現解析一個數字證書就非常簡單了。在此,我編寫了一個解析證書的軟件,實現關鍵代碼如下:
fp=fopen(filename.GetBuffer(0),"rb");
if(fp==NULL)
{
MessageBox("讀取證書錯誤");
return ;
}
Certlen=fread(Cert,1,4096,fp);
fclose(fp);
//判斷是否為DER編碼的用戶證書,並轉化為X509結構體
pTmp=Cert;
usrCert = d2i_X509(NULL,(const unsigned char ** )&pTmp,Certlen);
if(usrCert==NULL)
{
BIO *b;
/* 判斷是否為PEM格式的數字證書 */
b=BIO_new_file(filename.GetBuffer(0),"r");
usrCert=PEM_read_bio_X509(b,NULL,NULL,NULL);
BIO_free(b);
if(usrCert==NULL)
{
MessageBox("轉化格式錯誤!");
return;
}
}
//解析證書
X509_NAME *issuer = NULL;//X509_NAME結構體,保存證書頒發者信息
X509_NAME *subject = NULL;//X509_NAME結構體,保存證書擁有者信息
//獲取證書版本
Version = X509_get_version(usrCert);
//獲取證書頒發者信息,X509_NAME結構體保存了多項信息,包括國家、組織、部門、通用名、mail等。
issuer = X509_get_issuer_name(usrCert);
//獲取X509_NAME條目個數
entriesNum = sk_X509_NAME_ENTRY_num(issuer->entries);
//循環讀取各條目信息
for(i=0;i<entriesNum;i++)
{
//獲取第I個條目值
name_entry = sk_X509_NAME_ENTRY_value(issuer->entries,i);
//獲取對象ID
Nid = OBJ_obj2nid(name_entry->object);
//判斷條目編碼的類型
if(name_entry->value->type==V_ASN1_UTF8STRING)
//把UTF8編碼數據轉化成可見字符
{
nUtf8 = 2*name_entry->value->length;
pUtf8 = (unsigned short *)malloc(nUtf8);
memset(pUtf8,0,nUtf8);
rv = MultiByteToWideChar(
CP_UTF8,
0,
(char*)name_entry->value->data,
name_entry->value->length,
pUtf8,
nUtf8);
rv = WideCharToMultiByte(
CP_ACP,
0,
pUtf8,
rv,
(char*)msginfo,
nUtf8,
NULL,
NULL);
free(pUtf8);
pUtf8 = NULL;
msginfoLen = rv;
msginfo[msginfoLen]='\0';
}
else
{
msginfoLen=name_entry->value->length;
memcpy(msginfo,name_entry->value->data,msginfoLen);
msginfo[msginfoLen]='\0';
}
//根據NID打印出信息
switch(Nid)
{
case NID_countryName://國家
tmp.Format("issuer 's countryName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_stateOrProvinceName://省
tmp.Format("issuer 's ProvinceName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_localityName://地區
tmp.Format("issuer 's localityName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_organizationName://組織
tmp.Format("issuer 's organizationName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_organizationalUnitName://單位
tmp.Format("issuer 's organizationalUnitName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_commonName://通用名
tmp.Format("issuer 's commonName:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
case NID_pkcs9_emailAddress://Mail
tmp.Format("issuer 's emailAddress:%s\n",msginfo);
m_list.InsertString(-1,tmp);
tmp.Empty();
break;
}//end switch
}
//獲取證書主題信息,與前面類似,在此省略
subject = X509_get_subject_name(usrCert);
………
//獲取證書生效日期
time = X509_get_notBefore(usrCert);
tmp.Format("Cert notBefore:%s\n",time->data);
m_list.InsertString(-1,tmp);
tmp.Empty();
//獲取證書過期日期
time = X509_get_notAfter(usrCert);
tmp.Format("Cert notAfter:%s\n",time->data);
m_list.InsertString(-1,tmp);
tmp.Empty();
//獲取證書公鑰
pubKey = X509_get_pubkey(usrCert);
pTmp=derpubkey;
//把證書公鑰轉為DER編碼的數據
derpubkeyLen=i2d_PublicKey(pubKey,&pTmp);
printf("PublicKey is: \n");
for(i = 0; i < derpubkeyLen; i++)
{
CString tmpp;
tmpp.Format("%02x", derpubkey[i]);
tmp=tmp+tmpp;
}
m_list.InsertString(-1,tmp);
//釋放結構體內存
X509_free(usrCert);
使用軟件解析證書效果,如下圖所示:
圖2 解析效果
3、結束語
通過上述介紹,相信大家對數字證書又有了更進一步的了解。在開發網絡通信軟件,需要加入身份認證功能時,大家可以根據解析出來的證書信息,與自己的訪問策略相對比,實現訪問控制。
數字證書在信息安全中處於重要地位,隨着密碼學的發展,數字證書的應用也會越來越廣。