zoukankan      html  css  js  c++  java
  • openssl

    原文链接: http://www.cangfengzhe.com/wangluoanquan/37.html

    这篇文章主要介绍PKI公钥体系中非常核心元素——数字证书的编程解析。在SSL,SET等安全协议通信时,数字证书用于通信双方进行身份认证,并且依靠数字证书和非对称加密算法加密传输数据,或者根据数字证书协商通信双方的共享密钥。所以,用户想要开发自己的应用,实现身份认证,必须对数字证书进行解析。根据解析结果,符合一定条件的终端用户,才可以接入。

    1、证书格式介绍

    现有的数字证书大都采用了X.509规范,主要由一下信息组成:版本号,证书序列号,有效期(证书生效时间和失效时间),用户信息(姓名、单位、组织、城市、国家等),颁发者信息,其他扩展信息,拥有者的公钥,CA对证书整体的签名,如图1所示。

    X.509 V3数字证书格式

    图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]='';
    }
    else
    {
    msginfoLen=name_entry->value->length;
    memcpy(msginfo,name_entry->value->data,msginfoLen);
    msginfo[msginfoLen]='';
    }
    //根据NID打印出信息
    switch(Nid)
    {
    case NID_countryName://国家
    tmp.Format("issuer 's countryName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_stateOrProvinceName://省
    tmp.Format("issuer 's ProvinceName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_localityName://地区
    tmp.Format("issuer 's localityName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_organizationName://组织
    tmp.Format("issuer 's organizationName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_organizationalUnitName://单位
    tmp.Format("issuer 's organizationalUnitName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_commonName://通用名
    tmp.Format("issuer 's commonName:%s ",msginfo);
            m_list.InsertString(-1,tmp);
            tmp.Empty();
    break;
    case NID_pkcs9_emailAddress://Mail
    tmp.Format("issuer 's emailAddress:%s ",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 ",time->data);
    m_list.InsertString(-1,tmp);
    tmp.Empty();
    //获取证书过期日期
    time = X509_get_notAfter(usrCert);
    tmp.Format("Cert notAfter:%s ",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: ");
    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、结束语

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

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

  • 相关阅读:
    字符串类型入门
    报错:Uncaught SyntaxError: Unexpected token)
    EasyUI选项卡避免重复打开
    JS获取当前时间并格式化
    vs2010修改的内容在浏览器页面不变怎么办
    EasyUI的时间控件禁止输入
    HTML表格<tr>行距调整
    Easyui表格显示日期格式错误
    sql server 2008查询时报错,消息:8155,没有为'a'的列2指向任何列
    解决Sql Server服务远程过程调用失败
  • 原文地址:https://www.cnblogs.com/huhu0013/p/4791430.html
Copyright © 2011-2022 走看看