天天看點

openssl - 數字證書的程式設計解析

這篇文章主要介紹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);      

繼續閱讀