OpenSSL入门-PKI实战

入门

需要准备三台电脑:根CA主机、子CA主机和客户主机,下面先搭建根CA主机和子CA主机:

先查看防火墙,若开启则要关闭:

1
2
firewall-cmd --state #查看状态
systemctl stop firewalld

打开OpenSSL配置文件如下,或Windows下为/bin/openssl.cfg。

1
cat /etc/ssl/openssl.cnf

部分内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[ ca ]
default_ca = CA_default # 默认CA
[ CA_default ]
dir = /etc/pki/CA # CA工作目录 这里改一下
certs = $dir/certs # 证书存储路径
crl_dir = $dir/crl # 证书吊销列表
database = $dir/index.txt # 证书数据库列表
new_certs_dir = $dir/newcerts # 新证书路径
certificate = $dir/cacert.pem # CA自己的证书
serial = $dir/serial # 当前证书编号 十六进制 默认00
crlnumber = $dir/crlnumber # 当前要被吊销的证书编号 十六进制 默认00
crl = $dir/crl.pem # 当前CRL
private_key = $dir/private/cakey.pem# CA私钥
x509_extensions = usr_cert # 加入证书中的扩展部分
name_opt = ca_default # 命名方式
cert_opt = ca_default # CA选项
default_days = 365 # 默认证书有效期限
default_crl_days= 30 # CRL到下一个CRL前的时间
default_md = default # 使用公钥默认MD
preserve = no # 保持传递的DN顺序
policy = policy_match #策略
[ policy_match ] #CA以及子CA必须一致
countryName = match #国家
stateOrProvinceName = match #州或省
organizationName = match #组织公司
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ policy_anything ] #可对外提供证书申请 此时证书匹配可不那么严格
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

创建需要的文件:

1
2
touch /etc/pki/CA/index.txt
echo 01>/etc/pki/CA/serial

然后在根CA主机上构造自签名证书,还要输入两次口令:

1
2
3
umask 066
openssl genrsa -out /etc/pki/CA/private/cakey.pem -des3 2048 #生成私钥
openssl req -new -x509 -key /etc/pki/CA/private/cakey.pem -days 7300 -out /etc/pki/CA/cacert.pem #生成自签名证书

在子CA主机上生成证书请求,并将证书申请文件传给根CA。根CA签发证书后将根CA生成的证书发给子CA。

1
2
3
4
5
6
7
8
9
10
umask 066
openssl genrsa -out /etc/pki/CA/private/cakey.pem 1024
openssl req -new -key /etc/pki/CA/private/cakey.pem -days 3650 -out /etc/pki/tls/subca.csr
scp /etc/pki/tls/subca.csr 根CA的IP:/etc/pki/CA
touch /etc/pki/CA/index.txt
touch /etc/pki/CA/serial
echo "01">/etc/pki/CA/serial
#在根CA主机上:
openssl ca -in /etc/pki/CA/subca.csr -out /etc/pki/CA/certs/subca.crt -days 3650
scp /etc/pki/CA/certs/subca.crt 子CA的IP:/etc/pki/CA/cacert.pem

客户端向子CA申请证书:

1
2
3
4
5
6
7
umask 066
openssl genrsa -out /etc/pki/tls/private/app.key 1024
openssl req -new -key /etc/pki/tls/private/app.key -out /etc/pki/tls/app.csr
scp /etc/pki/tls/app.csr 子CA的IP:/etc/pki/CA
#在子CA主机上:
openssl ca -in /etc/pki/CA/app.csr -out /etc/pki/CA/certs/app.crt -days 365
scp /etc/pki/CA/certs/app.crt 客户端IP:/etc/pki/CA/certs/

X.509 v3结构

ASN.1标准编码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Certificate::=SEQUENCE{
tbsCertificate TBSCertificate, --主体名称、签发者名称、主体公钥、证书有效期等
signatureAlgorithm AlgorithmIdentifer, --证书签发算法标识符
signatureValue BIT STRING --签名结果
}
TBScertificate::=SEQUENCE{
Version [0] EXPLICIT Version DEFAULT v1, --版本号
serialNumber CertificateSerialNumber, --序列号
signature AlgorithmIdentifier, --签名算法
Issuer Name, --颁发者
validity Validity, --有效日期
subject Name, --主体
subjectPublicKeyInfo SubjectPublicKeyInfo, --主体公钥信息
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, --颁发者唯一标识符
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, --主体唯一标识符
extension [3] EXPLICIT Extensions OPTIONAL --扩展项
}
Version::=INTEGER(v1(0),v2(1),v3(2))
CertificateSerialNumber::=INTEGER
Validity::=SEQUENCE{
notBefore Time,
notAfter Time
}
Time::={
utcTime UTCTime,
generalTime GeneralizedTime
}
UniqueIdentifier::=BIT STRING
SubjectPublicKeyInfo::=SEQUENCE{
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
Extension::=SEQUENCE SIZE(1..MAX)OF Extension
Extension::=SEQUENCE{
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING
}
AlgorithmIdentifier::=SEQUENCE{
Algorithm OBJECT IDENTIFIER, --具体算法 必须与tbsCertificate->signature相同
parameters ANY DEFINED BY algorithm OPTIONAL --可选参数
}

证书编程

.cer为单个X.509证书文件,不含私钥。.p7b为PKCS#7证书链文件,包含一个或多个X.509证书,不含私钥,通常从CA申请RSA证书时返回这种签名证书文件。.pfx为PCKS#12证书文件,包含一个或多个X.509证书,含私钥,一般有密码保护,从CA申请RSA证书时加密证书和RSA加密私钥为该格式。

d2i_X509把DER编码转换为内部结构体:

1
2
3
4
5
X509* d2i_X509(
X509** cert, //要转码的证书
unsigned char** d, //DER编码的证书数据
int len //证书数据长度
);//成功返回X509结构体证书数据

X509内部结构体为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct x509_st{
X509_CINF* cert_info; //证书数据信息
X509_ALGOR* sig_alg; //签名算法
ASN1_BIT_STRING* signature; //CA对证书的签名值
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;
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 ASIdentifier_st* rfc3779_asid;
#endif
#ifndef OPENSSL_NO_SHA
unsigned char sha1_hash[SHA_DIGEST_LENGTH];
#endif
X509_CERT_AUX* aux;
};
typedef struct x509_cinf_st{
ASN1_INTEGER* version; //证书版本 0为v1 1为v2
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;
ASN1_ENCODING enc;
}X509_CINF;

X509_get_version获取证书版本:

1
#define X509_get_version(x) ASN1_INTEGER_get((x)->cert_info->version) //返回LONG型证书版本号

X509_get_serialNumber证书序列号:

1
2
3
ASN1_INTEGER* X509_get_serialNumber(
X509* x
);

X509_get_issuer_name获得证书颁发者信息:

1
2
3
X509_NAME* X509_get_issuer_name(
X509* a
);

X509_NAME结构如下,每个该结构体包含多个X509_NAME_ENTRY结构体,后者保存不能发这的信息,包括对象和值。对象有国家、通用名、单位、组织、地区、邮件等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct X509_name_st{
STACK_OF(X509_NAME_ENTRY)* entries;
int modified;
#ifndef OPENSSL_NO_BUFFER
BUF_MEM* bytes;
#else
char* bytes;
#endif
unsigned char* cannon_enc;
int canon_enclen;
};
typedef struct X509_name_entry_st{
ASN1_OBJECT* object;
ASN1_STRING* value;
int set;
int size;
}X509_NAME_ENTRY;

X509_get_subject_nameX509_get_notBeforeX509_get_notAfterX509_get_pubkey分别获取证书拥有者信息、有效期起始日期、有效期终止日期和证书公钥:

1
2
3
4
5
6
7
8
X509_NAME* X509_get_subject_name(
X509* a
);
#define X509_get_notBefore(x) ((x)->cert_info->validity->notBefore)
#define X509_get_notAfter(x) ((x)->cert_info->validity->notAfter)
EVP_PKEY* X509_get_pubkey(
X509* x
);

X509_STORE_CTX_freeX509_SOTRE_CTX_freeX509_STORE_CTX_init分别创建、释放和初始化证书存储区上下文环境函数。

1
2
3
4
5
6
7
8
9
10
X509_STORE_CTX* X509_STORE_CTX_new();
void X509_STORE_CTX_free(
X509_STORE_CTX* ctx //证书存储区上下文环境
);
int X509_STORE_CTX_init(
X509_STORE_CTX* ctx,
X509_STORE* store, //根证书存储区
X509* x509,
STACK_OF(X509)* chain //证书链
); //成功1 失败0

X509_verify_cert验证证书函数:

1
2
3
int X509_verify_cert(
X509_STORE_CTX* ctx
); //成功1 失败0

X509_STORE_newX509_STORE_free分别创建和释放证书存储区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
X509_STORE* X509_STORE_new(void);
void X509_STORE_free(X509_STORE* v);
typedef struct x509_store_st X509_STORE;
struct x509_store_st{
int cache;
STACK_OF(X509_OBJECT)* objs;
STACK_OF(X509_LOOKUP)* get_cert_methods;
X509_VERIFY_PARAM* param;
int (*verify)(X509_STORE_CTX* ctx);
int (*verify_cb)(int ok,X509_STORE_CTX* ctx);
int (*get_issuer)(X509** issuer,X509_STORE_CTX* ctx,X509* x);
int (*check_issued)(X509_STORE_CTX* ctx,X509* x,X509* issuer);
int (*check_revocation)(X509_STORE_CTX* ctx);
int (*get_crl)(X509_STORE_CTX* ctx,X509_CRL* crl,X509* x);
int (*check_url)(X509_STORE_CTX* ctx,X509_CRL* crl);
int (*cert_crl)(X509_STORE_CTX* ctx,X509_CRL* CRL,x509* x);
STACK_OF(X509)* (*lookup_certs)(X509_STORE_CTX* ctx,X509_NAME* nm);
STACK_OF(X509_CRL)* (*lookup_crls)(X509_STORE_CTX* ctx,X509_NAME* nm);
int (*cleanup)(X509_STORE_CTX* ctx);
CRYPTO_EX_DATA ex_data;
int references;
};

X509_STORE_add_certX509_STORE_add_crl向证书存储区添加证书和证书吊销列表:

1
2
3
4
5
6
7
8
int X509_STORE_add_cert(
X509_STORE* ctx, //证书存储区
X509* x //受信任的根证书
); //成功1 失败0
int X509_STORE_add_crl(
X509_STORE* ctx,
X509_CRL* x //证书吊销列表
); //同上

X509_free释放X509结构体:

1
void X509_free(X509* a);

实战

之前生成了一个subca.crt的PEM编码证书,这里把它转为DER编码证书:

1
openssl x509 -in subca.crt -outform der -out subca.der

然后按钮触发与证书的解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
void CtestDlg::OnBnClickedSelCert(){
unsigned char buf[4096]="";
CFileDialog dlg(TRUE,".der",NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,"文本文件(*.der)|*.der|所有文件(*.*)|*.*||");
if(dlg.DoModal()==IDOK){
CString strPath=dlg.GetPathName();
FILE* fp=fopen((LPSTR)(LPCSTR)strPath,"rb");
if(!fp)
return; //文件打开失败
int nSize=fread(buf,1,4096,fp);
AnsX509(buf,nSize);
m_strCert=gstr;
UpdateData(FALSE);
};
};
void AnsX509(unsigned char* usrCertificate,unsigned long usrCertificateLen){
X509* x509Cert=NULL;
unsigned char* pTmp=NULL;
X509_NAME* issuer=NULL; //颁发者信息
X509_NAME* subject=NULL; //拥有者信息
int i,entriesNum;
X509_NAME_ENTRY* name_entry;
ASN1_INTEGER* Serial=NULL; //证书序列号
long Nid;
ASN1_TIME* time; //有效期
EVP_PKEY* pubKey; //公钥
long Version; //版本
unsigned char derpubkey[1024];
int derpubkeyLen;
unsigned char msginfo[1024];
int msginfoLen;
unsigned short* pUtf8=NULL;
int nUtf8,rv;
char szSign[256];
ULONG ulen=256;
char szTmp[256]="";
pTmp=usrCertificate;
x509Cert=d2i_X509(NULL,(const unsigned char**)&pTmp,usrCertificatelen);
if(x509Cert==NULL)
return; //解析失败 非DER证书
Version=X509_get_version(x509Cert); //获取证书版本
myprintf("X509 Version:V%ld\r\n",Version+1);
Serial=X509_get_serialNumber(x509Cert); //获取证书序列号
for(i=0;i<Serial->length;i++)
myprintf("%02x",Serial->data[i]);
myprintf("\r\n");
if(-1==get_SignatureAlgOid(x509Cert,szSign,&ulen))
return;
myprintf("%s\r\n",szSign); //签名算法
issuer=X509_get_issuer_name(x509Cert); //获取证书颁发者信息
entriesNum=sk_X509_NAME_ENTRY_num(issuer->entries); //获取X509_NAME条目个数
for(i=0;i<entriesNum;i++){
name_entry=sk_X509_NAME_ENTRY_value(issuer->entries,i);
Nid=OBJ_obj2nid(name_entry->object); //获取对象ID
if(name_entry->value->type==V_ASN1_UTF8STRING){
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,(LPWSTR)pUtf8,nUtf8);
rv=WideCharToMultiByte(CP_ACP,0,(LPCWSTR)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';
}
switch(Nid){
case NID_countryName:{
myprintf("%s\r\n",msginfo); //签发者国家
break;
};
case NID_stateOrProvinceName:{
myprintf("%s\r\n",msginfo); //签发者省份
break;
};
case NID_localityName:{
myprintf("%s\r\n",msginfo); //签发者地区
break;
};
case NID_organizationName:{
myprintf("%s\r\n",msginfo); //签发者组织
break;
};
case NID_organizationUnitName:{
myprintf("%s\r\n",msginfo); //签发者单位
break;
};
case NID_commonName:{
myprintf("%s\r\n",msginfo); //签发者通用名
break;
};
case NID_pkcs9_emailAddress:{
myprintf("%s\r\n",msginfo); //Mail
break;
};
};
};
subject=X509_get_subject_name(x509Cert); //获取证书主题信息
entriesNum=sk_X509_NAME_ENTRY_num(subject->entries); //获取证书主题信息条目个数
for(i=0;i<entiresNum;i++){
name_entry=sk_X509_NAME_ENTRY_value(subject->entries,i);
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);
rv=MultiByteToWideChar(CP_UTF8,0,(char*)name_entry->value->data,name_entry->value->length,(LPWSTR)pUtf8,nUtf8);
rv=WideCharToMultiByte(CP_ACP,0,(LPCWSTR)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';
};
switch(Nid){
case NID_countryName:{
myprintf("%s\r\n",msginfo); //持有者国家
break;
};
case NID_stateOrProvinceName:{
myprintf("%s\r\n",msginfo); //持有者省
break;
};
case NID_localityName:{
myprintf("%s\r\n",msginfo); //持有者地区
break;
};
case NID_organizationName:{
myprintf("%s\r\n",msginfo); //持有者组织
break;
};
case NID_organizationalUnitName:{
myprintf("%s\r\n",msginfo); //持有者单位
break;
};
case NID_commonName:{
myprintf("%s\r\n",msginfo); //持有者通用名
break;
};
case NID_pkcs9_emailAddress:{
myprintf("%s\r\n",msginfo); //持有者Mail
break;
};
};
};
time=X509_get_notBefore(x509Cert); //获取证书生效日期
myprintf("%s\r\n",time->data);
time=X509_get_notAfter(x509Cert); //获取证书
myprintf("%s\r\n",time->data);
if(szSign[4]=='8'){ //为RSA公钥
pubKey=X509_get_pubkey(x509Cert);
if(!pubKey)
goto end;
pTmp=derpubkey;
derpubkeyLen=i2d_PublicKey(pubKey,&pTmp); //证书公钥转DER编码数据
for(i=0;i<derpubkeyLen;i++){
if(i>0&&i%16==0)
myprintf("\r\n");
myprintf("%02x",derpubkey[i]);
};
};
end:
X509_free(x509Cert);
};
ULONG get_SignatureAlgOid(X509* x509Cert,LPSTR lpscOid,ULONG* pulLen){
char oid[128]={0};
ASN1_OBJECT* salg=NULL;
if(!x509Cert)
return -1;
if(!pulLen)
return -1;
salg=x509Cert->sig_alg->algorithm;
OBJ_obj2txt(oid,128,salg,1);
if(!lpscOid){
*pulLen=strlen(oid)+1;
return -1;
};
if(*pulLen<strlen(oid)+1)
return -1;
strncpy(lpscOid,oid,*pulLen);
*pulLen=strlen(oid)+1;
return 0;
};
CString gstr;
void myprintf(const char* format,...){
va_list v1;
char Buffer[2*MAX_PATH]={0};
LONG nRes;
va_start(v1,format);
vsprintf(Buffer,format,v1);
CString str;
str.Format("%s",Buffer);
gstr+=str;
};