random tips on coding, ops, ...

使用 OpenSSL 建立 CA 及签发,撤消证书

首先,OpenSSL 软件包自身就已经包含 CA.[sh|pl] 脚本 (前者为 shell 脚本,后者为 perl),利用该命令(可能需要做一些小的修改)就已 经可以完成 CA 的创建和证书的签发,其基本命令流程如下:

  • CA.sh -newca # 建立 CA
  • CA.sh -newreq # 生成私钥及证书请求
  • CA.sh -sign # 签发该证书

这之中 CA 以及证书的一些信息会在命令运行过程中提示使用者输入,还有些信息是需要 在某个配置文件中配置的(一般对应文件名为 openssl.cnf)。

此外, openvpn 提供了一个称为 easy-rsa 的工具,包含了一些辅助的脚本来进行 CA 的建立和证书的签发。感兴趣的可以参考。

本文的重点在于介绍如何使用 openssl 这个命令来完成同样的任务。熟悉这些最底层的 命令后,无论是利用上面这两个工具还是自己写脚本都可以完成建立和维护 CA 的任务。

简单的说来, CA 建立和证书签发的流程是一样的,两者都需要首先生成私钥文件,基于 私钥文件生成证书请求,然后对证书文件进行签名操作。只不过对 CA 证书来说其证书 是自签名的,普通的证书则是由 CA 来进行签名。签名之后的证书文件是可以公开的,而 私钥文件则需要严格保护,尤其是 CA 的私钥文件。一旦 CA 的私钥文件泄漏,则拿到该 文件的使用者可以伪造出由该 CA 签发的证书。

此外,生成的证书文件除对原始证书请求的验证外,还会包含额外的扩展信息,如该证书 的用途等。这些信息一般在配置文件中来指定,证书签发时根据相应的命令行参数和配 置来决定附加哪些信息。例如, “basicConstraints = CA:FALSE” 表示该证书不能用作 CA 证书, “basicConstraints=critical,CA:TRUE, pathlen:0” 则表示该证书下不能有次级 CA, “nsCertType = server” 表示该证书类别为服务器,等等。实际使用中,可以将不同的 配置信息放在下面要提到的配置文件(openssl.cnf)中的不同 section 中,在签发证 书时可以通过”-extensions” 这个参数来选择。具体这些字段的意思和如何配置都可以 参考 OpenSSL的手册页,如 man x509v3_configman reqman ca 等。

具体如何使用这些信息是由应用程序来决定的。例如,openvpn 在使用 SSL 作认证时, 缺省情况下,客户端会验证服务段的证书字段 ns-cert-type (或”Netscape Cert Type”)为 server。反过来,服务端也可以要求该字段为 client。此外, openvpn 的服务端和客户端都还可以验证对端的证书的用途和扩展用途(参考 –remote-cert-ku 和 –remote-cert-eku 这两个指令)。

生成私钥的命令为 “openssl genrsa”。生成证书请求的命令为 “openssl req”。后者 也可以包含前者的功能。证书签发的命令则为 “openssl ca” 。

执行下面的 bash 脚本即能演示 CA 建立以及证书签发和撤消的完整过程。该脚本中部分 命令使用了 “-nodes” 选项,表示相应的文件无加密(即无密码保护),在实际环境中 需要结具体的使用情况进行调整。

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
#!/bin/bash

CATOP=./demoCA
CAKEY=${CATOP}/private/cakey.pem
CAREQ=${CATOP}/careq.pem
CACERT=${CATOP}/cacert.pem

SSLCNF="-config ./openssl.cnf"

function newca()
{
    if [ ! -d ${CATOP} ] ; then
        mkdir -p ${CATOP}
        mkdir -p ${CATOP}/private
        mkdir -p ${CATOP}/newcerts
        touch ${CATOP}/index.txt
        echo "unique_subject = yes" > ${CATOP}/index.txt.attr
        echo "01" > ${CATOP}/crlnumber
    fi

    # create ca private key & certificate request
    openssl req ${SSLCNF} -new -nodes -keyout ${CAKEY} -out ${CAREQ}

    # creat ca certificate by self-signed
    openssl ca ${SSLCNF} -keyfile ${CAKEY} \
        -selfsign -create_serial           \
        -extensions v3_ca_cert             \
        -days 7305 -batch                  \
        -out ${CACERT}                     \
        -outdir ${CATOP}/newcerts          \
        -infiles ${CATOP}/careq.pem
}

function mkcert()
{
    # create a private key certificate request
    openssl req ${SSLCNF} -new -nodes -keyout newkey.pem -out newreq.pem

    # sign the certficate request
    openssl ca ${SSLCNF} -policy policy_cert -batch \
        -extensions www_cert -notext                \
        -out newcert.pem -infiles newreq.pem
}

function verify_cert()
{
    openssl verify -CAfile ${CACERT} newcert.pem
}

function revoke_cert()
{
    openssl ca ${SSLCNF} -revoke newcert.pem
    openssl ca ${SSLCNF} -gencrl -out ${CATOP}/crl.pem
}

newca

mkcert

verify_cert

revoke_cert

openssl x509 -text -noout -in newcert.pem 可以用来查看签发的证书的详细信息。 openssl crl -in crl.pem -noout -text 可以用来查看该 CA 下所有已撤消证书的 详细信息。

脚本中用到的配置文件 openssl.cnf 为常见的 ini 文件格式,其内容与下。 各个 section 的意义从其字面以及集合使用的命令行参数等即可大致了解,详细的解释 则需要参考相关命令的手册。

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
[ca]
default_ca = CA_default          # default ca section

[CA_default]
dir           = ./demoCA
certs         = $dir/certs
database      = $dir/index.txt   # CA database index file
new_certs_dir = $dir/newcerts    # default place for new certs

serial        = $dir/serial            # current serial number
certificate   = $dir/cacert.pem        # CA certificate
crlnumber     = $dir/crlnumber         # current crl number
private_key   = $dir/private/cakey.pem # CA private key

default_md       = default        # digest method
default_days     = 30             # how long to certify for
default_crl_days = 30             # how long before next CRL

policy        = policy_ca

[policy_ca]
countryName            = match
stateOrProvinceName    = optional
organizationName       = match
organizationalUnitName = supplied
commonName             = supplied
emailAddress           = supplied

[policy_cert]
countryName            = match
stateOrProvinceName    = optional
organizationName       = match
organizationalUnitName = supplied
commonName             = supplied
emailAddress           = supplied

[req]
default_bits       = 2048
distinguished_name = req_distinguished_name

[req_distinguished_name]
countryName         = Country Name (2 letter code)
countryName_default = CN
countryName_min     = 2
countryName_max     = 2

0.organizationName         = Organization Name (eg, company)
0.organizationName_default = Demo

organizationalUnitName         =  Organizational Unit Name (eg, section)
organizationalUnitName_default = Users

commonName     = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64

emailAddress     = Email Address
emailAddress_max = 64

[v3_ca_cert]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = critical,CA:true
keyUsage               = cRLSign, keyCertSign
nsCertType             = sslCA, emailCA

[vpn_server_cert]
basicConstraints       = CA:FALSE
nsCertType             = server
keyUsage               = keyAgreement, digitalSignature
extendedKeyUsage       = serverAuth
nsComment              = "Certificate Generated by Demo CA"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

[vpn_cert]
basicConstraints       = CA:FALSE
nsCertType             = client, email
keyUsage               = keyAgreement, digitalSignature
extendedKeyUsage       = clientAuth, emailProtection
nsComment              = "Certificate Generated by Demo CA"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

[www_cert]
basicConstraints       = CA:FALSE
nsCertType             = server
extendedKeyUsage       = serverAuth, emailProtection
nsComment              = "Certificate Generated by Demo CA"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

[crl_ext]
authorityKeyIdentifier = keyid:always,issuer:always

comments powered by Disqus