OpenSSL之X509證書(shū)用法

什么是數(shù)字證書(shū)?

數(shù)字證書(shū)就是互聯(lián)網(wǎng)通訊中標(biāo)志通訊各方身份信息的一系列數(shù)據(jù),提供了一種在Internet上驗(yàn)證您身份的方式,其作用類似于司機(jī)的駕駛執(zhí)照或日常生活中的身份證。它是由一個(gè)由權(quán)威機(jī)構(gòu)-----CA機(jī)構(gòu),又稱為證書(shū)授權(quán)(Certificate Authorit y)中心發(fā)行的,人們可以在網(wǎng)上用它來(lái)識(shí)別對(duì)方的身份。數(shù)字證書(shū)是一個(gè)經(jīng)證書(shū)授權(quán) 中心數(shù)字簽名的包含公開(kāi)密鑰擁有者信息以及公開(kāi)密鑰的文件。最簡(jiǎn)單的證書(shū)包含一個(gè)公開(kāi)密鑰、名稱以及證書(shū)授權(quán)中心的數(shù)字簽名。一般情況下證書(shū)中還包括密鑰的有效時(shí)間,發(fā)證機(jī)關(guān)(證書(shū)授權(quán)中心)的名稱,該證書(shū)的序列號(hào)等信息,證書(shū)的格式遵循 ITUT X.509國(guó)際標(biāo)準(zhǔn)。
一個(gè)標(biāo)準(zhǔn)的X.509數(shù)字證書(shū)包含以下一些內(nèi)容:
證書(shū)的版本信息;
證書(shū)的序列號(hào),每個(gè)證書(shū)都有一個(gè)唯一的證書(shū)序列號(hào);
證書(shū)所使用的簽名算法;
證書(shū)的發(fā)行機(jī)構(gòu)名稱,命名規(guī)則一般采用X.500格式;
證書(shū)的有效期,現(xiàn)在通用的證書(shū)一般采用UTC時(shí)間格式,它的計(jì)時(shí)范圍為1950-2049;
證書(shū)所有人的名稱,命名規(guī)則一般采用X.500格式;
證書(shū)所有人的公開(kāi)密鑰;
證書(shū)發(fā)行者對(duì)證書(shū)的簽名。

創(chuàng)建X509證書(shū)的過(guò)程:

  1. 用戶生成自己的公私鑰對(duì)。
  2. 構(gòu)造自己的證書(shū)申請(qǐng)文件,符合PKCS#10標(biāo)準(zhǔn)。該文件主要包括了用戶信息、公鑰以及一些可選的屬性信息,并用自己的私鑰給該內(nèi)容簽名。
  3. 用戶將證書(shū)申請(qǐng)文件提交給CA。
  4. CA驗(yàn)證簽名,提取用戶信息,并加上其他信息(比如頒發(fā)者等信息),用CA的私鑰簽發(fā)數(shù)字證書(shū)。

本文假設(shè)你已經(jīng)安裝好了OpenSSL,并且持有一份1.1.1的源碼。
證書(shū)相關(guān)的頭文件在x509.h和x509v3.h中、源文件在crypto/x509和crypto/x509v3目錄中。

主要結(jié)構(gòu):

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;

這個(gè)結(jié)構(gòu)定義了證書(shū)申請(qǐng)信息主體。主要字段含義如下:
enc —— 內(nèi)部編碼運(yùn)算結(jié)構(gòu),不參與DER編碼。
version —— 版本號(hào)。
subject —— 申請(qǐng)者信息。
pubkey —— 申請(qǐng)者公鑰。
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;

這個(gè)結(jié)構(gòu)定義了證書(shū)申請(qǐng)信息。主要字段含義如下:
req_info —— 版本號(hào)。
sig_alg —— 簽名算法。
signature —— 申請(qǐng)者私鑰簽名。

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;

這個(gè)結(jié)構(gòu)定義了數(shù)字證書(shū)的信息主體。主要字段含義如下:
version —— 版本號(hào)。
serialNumber —— 證書(shū)的序列號(hào)。
signature —— 證書(shū)采用的簽名算法。
issuer —— 證書(shū)的頒發(fā)者信息。
validity—— 證書(shū)的有效期。
subject —— 證書(shū)的持有者信息。
key —— 證書(shū)的持有者公鑰。
issuerUID —— 頒發(fā)者唯一標(biāo)識(shí)。
subjectUID —— 持有者唯一標(biāo)識(shí)。
extensions —— 證書(shū)的擴(kuò)展信息。

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;

這個(gè)結(jié)構(gòu)定義了完整的X509數(shù)字證書(shū)。主要字段含義如下:
cert_info —— 證書(shū)主體信息。
sig_alg —— 簽名算法。
signature —— 簽名值,存放CA對(duì)該證書(shū)采用sig_alg生成的結(jié)果。
siginf —— 算名算法信息描述。
ex_data —— 存放證書(shū)自定義信息,用于證書(shū)驗(yàn)證。
skid —— 主體密鑰標(biāo)識(shí)。
akid —— 頒發(fā)者密鑰標(biāo)識(shí)。
policy_cache —— 證書(shū)的策略緩存。
sha1_hash —— 存放證書(shū)的sha1摘要值。
aux —— 輔助信息。

在1.1.1中,大多數(shù)的數(shù)據(jù)結(jié)構(gòu)已經(jīng)不再向使用者開(kāi)放,從封裝的角度來(lái)看,這是更合理的。如果你在頭文件中找不到結(jié)構(gòu)定義,不妨去源碼中搜一搜。

主要函數(shù):

int X509_REQ_set_version(X509_REQ *x, long version);
設(shè)置證書(shū)請(qǐng)求的版本。
成功返回1,失敗返回0。

long X509_REQ_get_version(const X509_REQ *req);
讀取證書(shū)請(qǐng)求的版本。

X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_txt(X509_NAME_ENTRY **ne, const char *field, int type, const unsigned char *bytes, int len);
創(chuàng)建一個(gè)字符串內(nèi)容項(xiàng)。
成功返回有效指針,失敗返回NULL。

int X509_NAME_add_entry(X509_NAME *name, const X509_NAME_ENTRY *ne, int loc, int set);
將內(nèi)容項(xiàng)加入到name集合中。
成功返回1,失敗返回0。

X509_NAME_ENTRY *X509_NAME_get_entry(const X509_NAME *name, int loc);
從name集合中獲取指定內(nèi)容項(xiàng)。
成功返回有效指針,失敗返回NULL。

X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *name, int loc);
從name集合中刪除指定內(nèi)容項(xiàng)。
成功返回有效指針,失敗返回NULL。

int X509_REQ_set_subject_name(X509_REQ *req, X509_NAME *name);
設(shè)置證書(shū)請(qǐng)求的申請(qǐng)者信息。
成功返回1,失敗返回0。

X509_NAME *X509_REQ_get_subject_name(const X509_REQ *req);
獲取證書(shū)請(qǐng)求的申請(qǐng)者信息。
成功返回有效指針,失敗返回NULL。

int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey);
設(shè)置證書(shū)請(qǐng)求的申請(qǐng)者公鑰。
成功返回1,失敗返回0。

EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *req);
獲取證書(shū)請(qǐng)求的申請(qǐng)者公鑰。
成功返回有效指針,失敗返回NULL。

int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md);
對(duì)證書(shū)請(qǐng)求結(jié)構(gòu)進(jìn)行簽名。
成功返回簽名長(zhǎng)度,失敗返回-1。

int X509_REQ_verify(X509_REQ *a, EVP_PKEY *r);
對(duì)證書(shū)請(qǐng)求結(jié)構(gòu)進(jìn)行簽名驗(yàn)證。
成功返回1,失敗返回0。

int X509_REQ_print(BIO *bp, X509_REQ *req);
打印證書(shū)請(qǐng)求結(jié)構(gòu)。
成功返回1,失敗返回0。

int X509_set_version(X509 *x, long version);
設(shè)置證書(shū)的版本。
成功返回1,失敗返回0。

long X509_get_version(const X509 *x);
讀取證書(shū)的版本。

int X509_set_issuer_name(X509 *x, X509_NAME *name);
設(shè)置證書(shū)的頒發(fā)者信息。
成功返回1,失敗返回0。

X509_NAME *X509_get_issuer_name(const X509 *a);
獲取證書(shū)的頒發(fā)者信息。
成功返回有效指針,失敗返回NULL。

int X509_set_subject_name(X509 *x, X509_NAME *name);
設(shè)置證書(shū)的所有者信息。
成功返回1,失敗返回0。

X509_NAME *X509_get_subject_name(const X509 *a);
獲取證書(shū)的所有者信息。
成功返回有效指針,失敗返回NULL。

int X509_set1_notBefore(X509 *x, const ASN1_TIME *tm);
int X509_set1_notAfter(X509 *x, const ASN1_TIME *tm);
設(shè)置證書(shū)的有效期。
成功返回1,失敗返回0。

const ASN1_TIME * X509_get0_notBefore(const X509 *x);
const ASN1_TIME *X509_get0_notAfter(const X509 *x);
獲取證書(shū)的有效期。
成功返回有效指針,失敗返回NULL。

int X509_set_pubkey(X509 *x, EVP_PKEY *pkey);
設(shè)置證書(shū)的頌發(fā)者公鑰。
成功返回1,失敗返回0。

EVP_PKEY *X509_get_pubkey(X509 *x);
獲取證書(shū)的頌發(fā)者公鑰。
成功返回有效指針,失敗返回NULL。

int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md);
對(duì)證書(shū)結(jié)構(gòu)進(jìn)行簽名。成功返回簽名長(zhǎng)度。

int X509_verify(X509 *a, EVP_PKEY *r);
驗(yàn)證證書(shū)的簽名。成功返回1,失敗返回0。

int X509_print(BIO *bp, X509 *x);
打印證書(shū)結(jié)構(gòu)。成功返回1,失敗返回0。

int X509_check_issued(X509 *issuer, X509 *subject);
檢查subject證書(shū)是否由issuer頒發(fā)。成功返回1,失敗返回0。

使用舉例1:

下面這個(gè)例子演示如何使用API生成證書(shū)申請(qǐng)文件。

#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文件內(nèi)容:
-----BEGIN CERTIFICATE REQUEST-----
MIHHMHMCAQEwEDEOMAwGA1UEKQwFemhhbjMwWjANBgkqhkiG9w0BAQEFAANJADBG
AkEAxYRRHNO231nDXzt1t6y21BDim3x5xeHDbdhvcP3GVi0reAh8qsd4PzJ9Z7AU
3NgSFunTfYJu4IdKO2ZNNv4uoQIBA6AAMA0GCSqGSIb3DQEBBQUAA0EAQmQRd9e+
ORnnsokq28fG3uImdtnI5lpjf/RLsOJ5QUvbBYcXsZ4poOB/PfBTXIDikX2eBB++
bpzxkTyonMg5TA==
-----END CERTIFICATE REQUEST-----

使用舉例2:

下面這個(gè)例子演示如何使用API讀取并解碼證書(shū)申請(qǐng)文件。

#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:

下面這個(gè)例子演示如何使用API生成證書(shū)文件。但是由于證書(shū)的結(jié)構(gòu)非常復(fù)雜,這個(gè)例子僅僅操作了部分字段。

#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文件內(nèi)容:
-----BEGIN CERTIFICATE-----
MIIBDDCBt6ADAgEBAgEAMA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNVBCkMBXpoYW4z
MB4XDTIxMDkxMTEzNDkyOVoXDTIxMDkxMTEzNDkyOVowEDEOMAwGA1UEKQwFemhh
bjMwWjANBgkqhkiG9w0BAQEFAANJADBGAkEAvAYbGFNXsmDDYmWDsK6wRrL6/zyP
aJnKIfwHjLhK6f/6LVhHQCRhDPHlWR4lUAwRYzCh6Ypa21nk3mkPxAEOBQIBAzAN
BgkqhkiG9w0BAQUFAANBAGw7dvUpR8FBcuUkRDI/pK+dbYH1WKpUdi82QmATFLfR
4MhRAoybnhYlyxLA3D+c0NfBcl3vk3bhUa/K56ItmpU=
-----END CERTIFICATE-----

使用舉例4:

下面這個(gè)例子演示如何使用API讀取并解碼證書(shū)文件。

#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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容