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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容