《使用 OpenSSL 自建 CA 并签发服务端证书》
1 简介
在内部环境(测试、内网 HTTPS、OCSP 实验)常常需要自建 CA 并签发服务端证书。整体流程:生成 CA 证书 -> 生成服务端 CSR -> 用 CA 为 CSR 签发证书,并为证书嵌入 SAN、CRL、OCSP 等扩展信息。
2 目录与脚本
推荐工作目录结构:
.
├── ca.cnf # CA 签发配置
├── gen-ca-cert.sh # 生成 CA 证书
├── gen-server-csr.sh # 生成服务端 CSR
├── gen-server-cert.sh # 签发服务端证书
├── check-cert.sh # 检查证书
├── verify-csr.sh # 校验 CSR
└── https.cnf # 服务端 CSR 配置
3 配置文件
3.1 ca.cnf(CA 签发配置)
HOME = .
RANDFILE = $ENV::HOME/.rnd
####################################################################
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
default_days = 10000 # how long to certify for
default_crl_days = 30 # how long before next CRL
default_md = sha256 # use public key default MD
preserve = no # keep passed DN ordering
x509_extensions = ca_extensions # The extensions to add to the cert
email_in_dn = no # Don't concat the email in the DN
copy_extensions = copy # Required to copy SANs from CSR to cert
#====Following lines are for signing other certs, not for making the CA cert.====
base_dir = .
certificate = $base_dir/cacert.pem # The CA certificate
private_key = $base_dir/cakey.pem # The CA private key
new_certs_dir = $base_dir # Location for new certs after signing
database = $base_dir/index.txt # Database index file
serial = $base_dir/serial.txt # The current serial number
unique_subject = no # Allow several certs with same subject.
####################################################################
[ req ]
default_bits = 4096
default_keyfile = cakey.pem
distinguished_name = ca_distinguished_name
x509_extensions = ca_extensions
string_mask = utf8only
####################################################################
[ ca_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = BeiJing
localityName = Locality Name (eg, city)
localityName_default = HaiDian
organizationName = Organization Name (eg, company)
organizationName_default = Example Inc.
organizationalUnitName = Organizational Unit (eg, division)
organizationalUnitName_default = R&D
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_default = Root CA
emailAddress = Email Address
emailAddress_default = ca@example.com
####################################################################
[ ca_extensions ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints = critical, CA:true
keyUsage = keyCertSign, cRLSign
####################################################################
[ signing_policy ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
[ signing_req ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
3.2 https.cnf(服务端 CSR 配置,含 SAN/CRL/OCSP)
HOME = .
RANDFILE = $ENV::HOME/.rnd
####################################################################
[ req ]
default_bits = 2048
default_keyfile = httpskey.pem
distinguished_name = server_distinguished_name
req_extensions = server_req_extensions
string_mask = utf8only
####################################################################
[ server_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = BeiJing
localityName = Locality Name (eg, city)
localityName_default = HaiDian
organizationName = Organization Name (eg, company)
organizationName_default = Example Inc.
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_default = *.example.com
emailAddress = Email Address
emailAddress_default = admin@example.com
####################################################################
[ server_req_extensions ]
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alternate_names
nsComment = "OpenSSL Generated Certificate"
#证书吊销列表 CRL
crlDistributionPoints = @crl_section
#在线证书状态协议 OCSP
authorityInfoAccess = @ocsp_section
####################################################################
[ alternate_names ]
DNS.1 = *.example.com
DNS.2 = localhost
DNS.3 = 127.0.0.1
# IPv4 localhost
IP.1 = 127.0.0.1
# IPv6 localhost
IP.2 = ::1
#证书吊销列表地址
[ crl_section ]
URI.0 = http://local.example.com:8241/crt.crl
[ ocsp_section ]
#CA 证书
caIssuers;URI.0 = http://local.example.com:8241/ca.crt
#验证地址
OCSP;URI.0 = http://local.example.com:8241/ocsp
4 生成脚本
4.1 生成 CA 证书(gen-ca-cert.sh)
# 清理旧产物
rm -rf index.txt serial.txt *.pem
# 一路回车即可,全部使用 ca.cnf 中的默认配置
openssl req -x509 -config ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM
# 建立签发数据库文件
touch index.txt
echo '01' > serial.txt
4.2 生成服务端 CSR(gen-server-csr.sh)
rm -f httpscert.csr
openssl req -config https.cnf -newkey rsa:2048 -sha256 -nodes -out httpscert.csr -outform PEM
4.3 用 CA 签发服务端证书(gen-server-cert.sh)
rm -f httpscert.pem
# 生成带扩展信息的服务端证书
openssl ca -config ca.cnf -policy signing_policy -extensions signing_req \
-out httpscert.pem -infiles httpscert.csr
# 将 CA 证书追加进去形成完整证书链
#(OCSP 等模块必须要有完整的证书链)
cat cacert.pem >> httpscert.pem
5 验证
# 查看服务端证书内容
openssl x509 -in httpscert.pem -text -noout
# 校验 CSR 签名与内容
openssl req -text -noout -verify -in httpscert.csr
6 常见问题
6.1 证书缺少 SAN 导致浏览器不信任
- 服务端必须走
https.cnf的req_extensions,并通过copy_extensions = copy把 CSR 的 SAN 复制进证书; - 确认
openssl x509 -in httpscert.pem -text中能看到Subject Alternative Name。
6.2 私钥被命令行历史暴露
-nodes 表示不加密私钥,仅用于测试;生产密钥应去掉该选项并妥善保护 cakey.pem。
7 参考文档
阅读 —
·
全站 —