這篇文章主要介紹PKI公鑰體系中非常核心元素——數字證書的程式設計解析。在SSL,SET等安全協定通信時,數字證書用于通信雙方進行身份認證,并且依靠數字證書和非對稱加密算法加密傳輸資料,或者根據數字證書協商通信雙方的共享密鑰。是以,使用者想要開發自己的應用,實作身份認證,必須對數字證書進行解析。根據解析結果,符合一定條件的終端使用者,才可以接入。
1、證書格式介紹
現有的數字證書大都采用了X.509規範,主要由一下資訊組成:版本号,證書序列号,有效期(證書生效時間和失效時間),使用者資訊(姓名、機關、組織、城市、國家等),頒發者資訊,其他擴充資訊,擁有者的公鑰,CA對證書整體的簽名.
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(); //獲得證書公鑰
其中,函數具體的參數和使用,結合下面程式設計代碼向大家介紹。
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);