《使用 OpenSSL 自建 CA 并签发服务端证书》

《使用 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 参考文档

阅读 — · 全站 —
🎸 我的歌单 0 首