OpenSSL之X509证书用法

什么是数字证书?

数字证书就是互联网通讯中标志通讯各方身份信息的一系列数据,提供了一种在Internet上验证您身份的方式,其作用类似于司机的驾驶执照或日常生活中的身份证。它是由一个由权威机构-----CA机构,又称为证书授权(Certificate Authorit y)中心发行的,人们可以在网上用它来识别对方的身份。数字证书是一个经证书授权 中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。一般情况下证书中还包括密钥的有效时间,发证机关(证书授权中心)的名称,该证书的序列号等信息,证书的格式遵循 ITUT X.509国际标准。
一个标准的X.509数字证书包含以下一些内容:
证书的版本信息;
证书的序列号,每个证书都有一个唯一的证书序列号;
证书所使用的签名算法;
证书的发行机构名称,命名规则一般采用X.500格式;
证书的有效期,现在通用的证书一般采用UTC时间格式,它的计时范围为1950-2049;
证书所有人的名称,命名规则一般采用X.500格式;
证书所有人的公开密钥;
证书发行者对证书的签名。

创建X509证书的过程:

  1. 用户生成自己的公私钥对。
  2. 构造自己的证书申请文件,符合PKCS#10标准。该文件主要包括了用户信息、公钥以及一些可选的属性信息,并用自己的私钥给该内容签名。
  3. 用户将证书申请文件提交给CA。
  4. CA验证签名,提取用户信息,并加上其他信息(比如颁发者等信息),用CA的私钥签发数字证书。

本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
证书相关的头文件在x509.h和x509v3.h中、源文件在crypto/x509和crypto/x509v3目录中。

主要结构:

struct X509_req_info_st {
    ASN1_ENCODING enc;          /* cached encoding of signed part */
    ASN1_INTEGER *version;      /* version, defaults to v1(0) so can be NULL */
    X509_NAME *subject;         /* certificate request DN */
    X509_PUBKEY *pubkey;        /* public key of request */
    STACK_OF(X509_ATTRIBUTE) *attributes;
};
typedef struct X509_req_info_st X509_REQ_INFO;

这个结构定义了证书申请信息主体。主要字段含义如下:
enc —— 内部编码运算结构,不参与DER编码。
version —— 版本号。
subject —— 申请者信息。
pubkey —— 申请者公钥。
attributes —— 可选属性信息。

struct X509_req_st {
    X509_REQ_INFO req_info;     /* signed certificate request data */
    X509_ALGOR sig_alg;         /* signature algorithm */
    ASN1_BIT_STRING *signature; /* signature */
    CRYPTO_REF_COUNT references;
    CRYPTO_RWLOCK *lock;
};
typedef struct X509_req_st X509_REQ;

这个结构定义了证书申请信息。主要字段含义如下:
req_info —— 版本号。
sig_alg —— 签名算法。
signature —— 申请者私钥签名。

struct x509_cinf_st {
    ASN1_INTEGER *version;      /* [ 0 ] default of v1 */
    ASN1_INTEGER serialNumber;
    X509_ALGOR signature;
    X509_NAME *issuer;
    X509_VAL validity;
    X509_NAME *subject;
    X509_PUBKEY *key;
    ASN1_BIT_STRING *issuerUID; /* [ 1 ] optional in v2 */
    ASN1_BIT_STRING *subjectUID; /* [ 2 ] optional in v2 */
    STACK_OF(X509_EXTENSION) *extensions; /* [ 3 ] optional in v3 */
    ASN1_ENCODING enc;
};
typedef struct x509_cinf_st X509_CINF;

这个结构定义了数字证书的信息主体。主要字段含义如下:
version —— 版本号。
serialNumber —— 证书的序列号。
signature —— 证书采用的签名算法。
issuer —— 证书的颁发者信息。
validity—— 证书的有效期。
subject —— 证书的持有者信息。
key —— 证书的持有者公钥。
issuerUID —— 颁发者唯一标识。
subjectUID —— 持有者唯一标识。
extensions —— 证书的扩展信息。

struct x509_st {
    X509_CINF cert_info;
    X509_ALGOR sig_alg;
    ASN1_BIT_STRING signature;
    X509_SIG_INFO siginf;
    CRYPTO_REF_COUNT references;
    CRYPTO_EX_DATA ex_data;
    /* These contain copies of various extension values */
    long ex_pathlen;
    long ex_pcpathlen;
    uint32_t ex_flags;
    uint32_t ex_kusage;
    uint32_t ex_xkusage;
    uint32_t ex_nscert;
    ASN1_OCTET_STRING *skid;
    AUTHORITY_KEYID *akid;
    X509_POLICY_CACHE *policy_cache;
    STACK_OF(DIST_POINT) *crldp;
    STACK_OF(GENERAL_NAME) *altname;
    NAME_CONSTRAINTS *nc;
#ifndef OPENSSL_NO_RFC3779
    STACK_OF(IPAddressFamily) *rfc3779_addr;
    struct ASIdentifiers_st *rfc3779_asid;
# endif
    unsigned char sha1_hash[SHA_DIGEST_LENGTH];
    X509_CERT_AUX *aux;
    CRYPTO_RWLOCK *lock;
    volatile int ex_cached;
} /* X509 */ ;
typedef struct x509_st X509;

这个结构定义了完整的X509数字证书。主要字段含义如下:
cert_info —— 证书主体信息。
sig_alg —— 签名算法。
signature —— 签名值,存放CA对该证书采用sig_alg生成的结果。
siginf —— 算名算法信息描述。
ex_data —— 存放证书自定义信息,用于证书验证。
skid —— 主体密钥标识。
akid —— 颁发者密钥标识。
policy_cache —— 证书的策略缓存。
sha1_hash —— 存放证书的sha1摘要值。
aux —— 辅助信息。

在1.1.1中,大多数的数据结构已经不再向使用者开放,从封装的角度来看,这是更合理的。如果你在头文件中找不到结构定义,不妨去源码中搜一搜。

主要函数:

int X509_REQ_set_version(X509_REQ *x, long version);
设置证书请求的版本。
成功返回1,失败返回0。

long X509_REQ_get_version(const X509_REQ *req);
读取证书请求的版本。

X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_txt(X509_NAME_ENTRY **ne, const char *field, int type, const unsigned char *bytes, int len);
创建一个字符串内容项。
成功返回有效指针,失败返回NULL。

int X509_NAME_add_entry(X509_NAME *name, const X509_NAME_ENTRY *ne, int loc, int set);
将内容项加入到name集合中。
成功返回1,失败返回0。

X509_NAME_ENTRY *X509_NAME_get_entry(const X509_NAME *name, int loc);
从name集合中获取指定内容项。
成功返回有效指针,失败返回NULL。

X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *name, int loc);
从name集合中删除指定内容项。
成功返回有效指针,失败返回NULL。

int X509_REQ_set_subject_name(X509_REQ *req, X509_NAME *name);
设置证书请求的申请者信息。
成功返回1,失败返回0。

X509_NAME *X509_REQ_get_subject_name(const X509_REQ *req);
获取证书请求的申请者信息。
成功返回有效指针,失败返回NULL。

int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey);
设置证书请求的申请者公钥。
成功返回1,失败返回0。

EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *req);
获取证书请求的申请者公钥。
成功返回有效指针,失败返回NULL。

int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md);
对证书请求结构进行签名。
成功返回签名长度,失败返回-1。

int X509_REQ_verify(X509_REQ *a, EVP_PKEY *r);
对证书请求结构进行签名验证。
成功返回1,失败返回0。

int X509_REQ_print(BIO *bp, X509_REQ *req);
打印证书请求结构。
成功返回1,失败返回0。

int X509_set_version(X509 *x, long version);
设置证书的版本。
成功返回1,失败返回0。

long X509_get_version(const X509 *x);
读取证书的版本。

int X509_set_issuer_name(X509 *x, X509_NAME *name);
设置证书的颁发者信息。
成功返回1,失败返回0。

X509_NAME *X509_get_issuer_name(const X509 *a);
获取证书的颁发者信息。
成功返回有效指针,失败返回NULL。

int X509_set_subject_name(X509 *x, X509_NAME *name);
设置证书的所有者信息。
成功返回1,失败返回0。

X509_NAME *X509_get_subject_name(const X509 *a);
获取证书的所有者信息。
成功返回有效指针,失败返回NULL。

int X509_set1_notBefore(X509 *x, const ASN1_TIME *tm);
int X509_set1_notAfter(X509 *x, const ASN1_TIME *tm);
设置证书的有效期。
成功返回1,失败返回0。

const ASN1_TIME * X509_get0_notBefore(const X509 *x);
const ASN1_TIME *X509_get0_notAfter(const X509 *x);
获取证书的有效期。
成功返回有效指针,失败返回NULL。

int X509_set_pubkey(X509 *x, EVP_PKEY *pkey);
设置证书的颂发者公钥。
成功返回1,失败返回0。

EVP_PKEY *X509_get_pubkey(X509 *x);
获取证书的颂发者公钥。
成功返回有效指针,失败返回NULL。

int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md);
对证书结构进行签名。成功返回签名长度。

int X509_verify(X509 *a, EVP_PKEY *r);
验证证书的签名。成功返回1,失败返回0。

int X509_print(BIO *bp, X509 *x);
打印证书结构。成功返回1,失败返回0。

int X509_check_issued(X509 *issuer, X509 *subject);
检查subject证书是否由issuer颁发。成功返回1,失败返回0。

使用举例1:

下面这个例子演示如何使用API生成证书申请文件。

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

namespace dakuang {}

int main(int argc, char* argv[])
{
    X509_REQ* req = X509_REQ_new();

    int ret = X509_REQ_set_version(req, 1);
    printf("X509_REQ_set_version() ret:[%d] \n", ret);

    X509_NAME* name = X509_NAME_new();
    X509_NAME_ENTRY* entry = X509_NAME_ENTRY_create_by_txt(NULL, "name", V_ASN1_UTF8STRING, (const unsigned char*)"zhan3", 5);
    printf("X509_NAME_ENTRY_create_by_txt() ret:[%p] \n", entry);
    ret = X509_NAME_add_entry(name, entry, 0, -1);
    printf("X509_NAME_add_entry() ret:[%d] \n", ret);
    X509_NAME_ENTRY_free(entry);
    ret = X509_REQ_set_subject_name(req, name);
    printf("X509_REQ_set_subject_name() ret:[%d] \n", ret);
    X509_NAME_free(name);

    EVP_PKEY* pkey = EVP_PKEY_new();
    RSA* rsa = RSA_generate_key(512, RSA_3, NULL, NULL);
    EVP_PKEY_assign_RSA(pkey, rsa);
    ret = X509_REQ_set_pubkey(req, pkey);
    printf("X509_REQ_set_pubkey() ret:[%d] \n", ret);
    EVP_PKEY_free(pkey);

    ret = X509_REQ_sign(req, pkey, EVP_sha1());
    printf("X509_REQ_sign() ret:[%d] \n", ret);

    BIO* bio = BIO_new_file("certreq.pem", "w");
    ret = PEM_write_bio_X509_REQ(bio, req);
    printf("PEM_write_bio_X509_REQ() ret:[%d] \n", ret);
    BIO_free(bio);

    X509_REQ_free(req);
    
    return 0;
}

输出:
X509_REQ_set_version() ret:[1]
X509_NAME_ENTRY_create_by_txt() ret:[0x20ef2d0]
X509_NAME_add_entry() ret:[1]
X509_REQ_set_subject_name() ret:[1]
X509_REQ_set_pubkey() ret:[1]
X509_REQ_sign() ret:[64]
PEM_write_bio_X509_REQ() ret:[1]

生成的certreq.pem文件内容:
-----BEGIN CERTIFICATE REQUEST-----
MIHHMHMCAQEwEDEOMAwGA1UEKQwFemhhbjMwWjANBgkqhkiG9w0BAQEFAANJADBG
AkEAxYRRHNO231nDXzt1t6y21BDim3x5xeHDbdhvcP3GVi0reAh8qsd4PzJ9Z7AU
3NgSFunTfYJu4IdKO2ZNNv4uoQIBA6AAMA0GCSqGSIb3DQEBBQUAA0EAQmQRd9e+
ORnnsokq28fG3uImdtnI5lpjf/RLsOJ5QUvbBYcXsZ4poOB/PfBTXIDikX2eBB++
bpzxkTyonMg5TA==
-----END CERTIFICATE REQUEST-----

使用举例2:

下面这个例子演示如何使用API读取并解码证书申请文件。

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

namespace dakuang {}

int main(int argc, char* argv[])
{
    BIO* bio = BIO_new_file("certreq.pem","r");
    X509_REQ* req = PEM_read_bio_X509_REQ(bio, NULL, NULL, NULL);
    printf("PEM_read_bio_X509_REQ() ret:[%p] \n", req);
    BIO_free(bio);

    EVP_PKEY* pkey = X509_REQ_get_pubkey(req);
    int ret = X509_REQ_verify(req, pkey);
    printf("X509_REQ_verify() ret:[%d] \n", ret);
    EVP_PKEY_free(pkey);

    X509_NAME* name = X509_REQ_get_subject_name(req);
    printf("X509_REQ_get_subject_name() ret:[%p] \n", name);
    if (name)
    {
        X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, 0);
        ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
        if (data)
        {
            BIO* b = BIO_new(BIO_s_file());
            BIO_set_fp(b, stdout, BIO_NOCLOSE);
            ASN1_STRING_print(b, data);
            BIO_free(b);
        }
    }

    X509_REQ_free(req);

    return 0;
}

输出:
PEM_read_bio_X509_REQ() ret:[0x1157390]
X509_REQ_verify() ret:[1]
X509_REQ_get_subject_name() ret:[0x1157510]
zhan3

使用举例3:

下面这个例子演示如何使用API生成证书文件。但是由于证书的结构非常复杂,这个例子仅仅操作了部分字段。

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

namespace dakuang {}

int main(int argc, char* argv[])
{
    X509* x509 = X509_new();

    int ret = X509_set_version(x509, 1);
    printf("X509_set_version() ret:[%d] \n", ret);

    X509_NAME* name = X509_NAME_new();
    X509_NAME_ENTRY* entry = X509_NAME_ENTRY_create_by_txt(NULL, "name", V_ASN1_UTF8STRING, (const unsigned char*)"zhan3", 5);
    printf("X509_NAME_ENTRY_create_by_txt() ret:[%p] \n", entry);
    ret = X509_NAME_add_entry(name, entry, 0, -1);
    printf("X509_NAME_add_entry() ret:[%d] \n", ret);
    X509_NAME_ENTRY_free(entry);
    ret = X509_set_subject_name(x509, name);
    printf("X509_set_subject_name() ret:[%d] \n", ret);
    ret = X509_set_issuer_name(x509, name);
    printf("X509_set_issuer_name() ret:[%d] \n", ret);
    X509_NAME_free(name);

    ASN1_TIME* tm = ASN1_TIME_new();
    ASN1_TIME_set(tm, time(NULL));
    ret = X509_set1_notBefore(x509, tm);
    printf("X509_set1_notBefore() ret:[%d] \n", ret);
    ret = X509_set1_notAfter(x509, tm);
    printf("X509_set1_notAfter() ret:[%d] \n", ret);
    ASN1_TIME_free(tm);

    EVP_PKEY* pkey = EVP_PKEY_new();
    RSA* rsa = RSA_generate_key(512, RSA_3, NULL, NULL);
    EVP_PKEY_assign_RSA(pkey, rsa);
    ret = X509_set_pubkey(x509, pkey);
    printf("X509_set_pubkey() ret:[%d] \n", ret);
    EVP_PKEY_free(pkey);

    ret = X509_sign(x509, pkey, EVP_sha1());
    printf("X509_sign() ret:[%d] \n", ret);

    BIO* bio = BIO_new_file("cert.pem", "w");
    ret = PEM_write_bio_X509(bio, x509);
    printf("PEM_write_bio_X509() ret:[%d] \n", ret);
    BIO_free(bio);

    X509_free(x509);

    return 0;
}

输出:
X509_set_version() ret:[1]
X509_NAME_ENTRY_create_by_txt() ret:[0x1a9c4d0]
X509_NAME_add_entry() ret:[1]
X509_set_subject_name() ret:[1]
X509_set_issuer_name() ret:[1]
X509_set1_notBefore() ret:[1]
X509_set1_notAfter() ret:[1]
X509_set_pubkey() ret:[1]
X509_sign() ret:[64]
PEM_write_bio_X509() ret:[1]

生成的cert.pem文件内容:
-----BEGIN CERTIFICATE-----
MIIBDDCBt6ADAgEBAgEAMA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNVBCkMBXpoYW4z
MB4XDTIxMDkxMTEzNDkyOVoXDTIxMDkxMTEzNDkyOVowEDEOMAwGA1UEKQwFemhh
bjMwWjANBgkqhkiG9w0BAQEFAANJADBGAkEAvAYbGFNXsmDDYmWDsK6wRrL6/zyP
aJnKIfwHjLhK6f/6LVhHQCRhDPHlWR4lUAwRYzCh6Ypa21nk3mkPxAEOBQIBAzAN
BgkqhkiG9w0BAQUFAANBAGw7dvUpR8FBcuUkRDI/pK+dbYH1WKpUdi82QmATFLfR
4MhRAoybnhYlyxLA3D+c0NfBcl3vk3bhUa/K56ItmpU=
-----END CERTIFICATE-----

使用举例4:

下面这个例子演示如何使用API读取并解码证书文件。

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

namespace dakuang {}

int main(int argc, char* argv[])
{
    BIO* bio = BIO_new_file("cert.pem", "r");
    X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
    printf("PEM_read_bio_X509() ret:[%p] \n", x509);
    BIO_free(bio);

    EVP_PKEY* pkey = X509_get_pubkey(x509);
    int ret = X509_verify(x509, pkey);
    printf("X509_verify() ret:[%d] \n", ret);
    EVP_PKEY_free(pkey);

    X509_NAME* name = X509_get_subject_name(x509);
    printf("X509_get_subject_name() ret:[%p] \n", name);
    if (name)
    {
        X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, 0);
        ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
        if (data)
        {
            BIO* b = BIO_new(BIO_s_file());
            BIO_set_fp(b, stdout, BIO_NOCLOSE);
            ASN1_STRING_print(b, data);
            BIO_free(b);
        }
    }

    X509_free(x509);

    return 0;
}

输出:
PEM_read_bio_X509() ret:[0x16eb930]
X509_verify() ret:[1]
X509_get_subject_name() ret:[0x16eb7e0]
zhan3

你可能感兴趣的:(OpenSSL之X509证书用法)