原文链接: 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、结束语

通过上述介绍,相信大家对数字证书又有了更进一步的了解。在开发网络通信软件,需要加入身份认证功能时,大家可以根据解析出来的证书信息,与自己的访问策略相对比,实现访问控制。

数字证书在信息安全中处于重要地位,随着密码学的发展,数字证书的应用也会越来越广。

最新文章

  1. Maven 代理设置
  2. C算法编程题(五)“E”的变换
  3. dubbo分析总结
  4. ActiveReports中如何使用Excel数据源
  5. java数学函数库 API(转)
  6. 用c#开发微信 (17) 微活动 3 投票活动 (文本投票)
  7. mysql数据导入
  8. 离开csdn来到blog园
  9. 关于Segmentation fault (core dumped)几个简单问题的整理
  10. DES加密系统的实现
  11. Qt数据库sqlite总结
  12. java学习面向对象构造函数
  13. js监听键盘方向键事件
  14. 2n皇后问题
  15. 一分钟上手artTemplate
  16. eclipse连接手机一直连接不起
  17. mysql替换字符串
  18. modbus调试工具
  19. CRM上线之路 走上了CRM实施顾问-第12天上班 -第三周
  20. openlayers3教材详解及demo(完整)

热门文章

  1. python小知识-__call__和类装饰器的结合使用,数据描述符__get__\__set__\__delete__(描述符类是Python中一种用于储存类属性值的对象)
  2. dijkstra基础
  3. 4.Django|ORM模型层
  4. 练习题|网络编程-socket开发
  5. 【Java】 剑指offer(8) 用两个栈实现队列
  6. HDU 1025 城市供应 【LIS】
  7. java中的instanceof用法详解
  8. luogu P4178 Tree
  9. Pycharm中实现多个项目共存的方式
  10. spring cloud:Edgware.RELEASE版本hystrix超时新坑