0. 准备工作

  • 一个Linux的机器,安装好openssl,大部分机器都已经默认安装好了,你可以试试执行看看openssl version。我这里使用的是Ubuntu 24.04 LTS。
  • 一个足够强度的根证书密码,本文用gwIQpfpItKSBJ0bvH3hQ,该密码仅作为示例用,你需要创建自己的密码并妥善保管。
  • 一个足够强度的中间证书密码,并妥善保管,本文用Fqvgw9GlCrOlXwkym3kQ作为示例。

基础知识

graph LR A[根证书(Root CA)
----------------------------------
•信任链的起点
• 主要用于签发中间证书
• 离线保存,降低被攻击风险] -- 签发 --> B B[中间证书(Intermediate CA)
----------------------------------
• 服务器证书的实际签发者] -- 签发 --> C C[服务器证书(Server Cert)
----------------------------------
• 终端实际使用者
• 可用于网站加密等,包含域名信息]
  • 根证书(Root CA)不直接签发业务证书,而只签发中间证书(Intermediate CA)。
  • 下级证书的信任来源于上级证书
  • 根证书私钥离线存储,中间证书私钥严格保护,且各级证书都有独立的吊销机制(CRL/OCSP)

1. 创建CA证书项目

1.1 初始化项目

为了方便演示,我把该项目命名为cert_maintains,并且放在home目录。当然你可以放在任意目录,注意调整命令中的路径即可。

mkdir -p ~/cert_maintains/ca/{root,intermediate}/{certs,crl,newcerts,private,csr}
cd ~/cert_maintains
touch ca/root/index.txt ca/intermediate/index.txt
echo 1000 > ca/root/serial
echo 1000 > ca/intermediate/serial
echo 1000 > ca/root/crlnumber
echo 1000 > ca/intermediate/crlnumber

1.2 Root CA证书

1.2.1 创建Root CA的配置文件

Root CA配置文件路径:~/cert_maintains/ca/root/openssl.cnf

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = .
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
crlnumber         = $dir/crlnumber

certificate       = $dir/certs/ca.cert.pem
private_key       = $dir/private/ca.key.pem
crl               = $dir/crl/ca.crl.pem

default_md        = sha256
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 7300
preserve          = no
policy            = policy_strict

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

[ req ]
string_mask         = utf8only
default_md          = sha256
prompt              = no
distinguished_name  = dn

[ dn ]
C  = CN
ST = Guangdong
L  = Shenzhen
O  = Linhancai
OU = Root CA
CN = Linhancai Root CA

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true, pathlen:0
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

1.2.2 签发Root CA

# 1. 切换到Root CA目录
cd ~/cert_maintains/ca/root

# 2. 生成Root CA私钥,回车后提示输入两次数据密码(gwIQpfpItKSBJ0bvH3hQ)
openssl genrsa -aes256 -out private/ca.key.pem 4096

# 3. 使用Root CA私钥签发Root CA证书
openssl req -config openssl.cnf \
  -key private/ca.key.pem \
  -new -x509 -days 7300 \
  -extensions v3_ca \
  -out certs/ca.cert.pem

本步骤得到Root CA私钥private/ca.key.pem和Root CA证书certs/ca.cert.pem

1.3. 中间CA

1.3.1 创建中间CA配置文件

中间CA配置文件路径为:~/cert_maintains/ca/intermediate/openssl.cnf

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = .
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
crlnumber         = $dir/crlnumber

certificate       = $dir/certs/intermediate.cert.pem
private_key       = $dir/private/intermediate.key.pem
crl               = $dir/crl/intermediate.crl.pem

default_md        = sha256
default_days      = 375
policy            = policy_loose
preserve          = no

[ policy_loose ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
string_mask         = utf8only
default_md          = sha256
prompt              = no
distinguished_name  = dn

[ dn ]
C  = CN
ST = Guangdong
L  = Shenzhen
O  = Linhancai
OU = Intermediate CA
CN = Linhancai Intermediate CA

[ v3_intermediate_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true, pathlen:0
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

[ server_cert ]
basicConstraints       = CA:false
nsCertType             = server
nsComment              = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth

[ client_cert ]
basicConstraints       = CA:false
nsCertType             = client
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, digitalSignature
extendedKeyUsage       = clientAuth

1.3.2 签发中间证书

# 1. 切换到中间证书目录
cd ~/cert_maintains/ca/intermediate

# 2. 生成中间CA私钥,同样回车后提示输入两次数据密码(Fqvgw9GlCrOlXwkym3kQ)
openssl genrsa -aes256 -out private/intermediate.key.pem 4096

# 3. 利用中间CA私钥,生成中间CA CSR文件
openssl req -config openssl.cnf \
  -new -key private/intermediate.key.pem \
  -out csr/intermediate.csr.pem
  
# 4. **切换回Root CA目录**,用Root CA签发中间CA证书
#   执行命令需要输入Root CA私钥的密码(gwIQpfpItKSBJ0bvH3hQ)
#   输入密码后根据提示输入y确认签发
cd ca/root
openssl ca -config openssl.cnf \
  -extensions v3_intermediate_ca \
  -days 3650 \
  -notext -md sha256 \
  -in ../intermediate/csr/intermediate.csr.pem \
  -out ../intermediate/certs/intermediate.cert.pem
  

1.4 设置好权限

cd ~/cert_maintains

chmod 700 ca/root/private
chmod 700 ca/intermediate/private
chmod 600 ca/root/private/*.key.pem
chmod 600 ca/intermediate/private/*.key.pem

至此CA证书签发已经签发完成,项目结构为:

cert_maintains
└── ca
    ├── intermediate
    │   ├── certs
    │   │   └── intermediate.cert.pem
    │   ├── crl
    │   ├── crlnumber
    │   ├── csr
    │   │   └── intermediate.csr.pem
    │   ├── index.txt
    │   ├── newcerts
    │   ├── openssl.cnf
    │   ├── private
    │   │   └── intermediate.key.pem
    │   └── serial
    └── root
        ├── certs
        │   └── ca.cert.pem
        ├── crl
        ├── crlnumber
        ├── csr
        ├── index.txt
        ├── index.txt.attr
        ├── index.txt.old
        ├── newcerts
        │   └── 1000.pem
        ├── openssl.cnf
        ├── private
        │   └── ca.key.pem
        ├── serial
        └── serial.old

2. 签发业务证书

2.1 为nginx签发https证书

现在我想要用自签名CA为域名 example.linhancai.cn签发一个证书,用于配置nginx的https。

2.1.1 创建域名证书配置文件

证书的路径放在~/cert_maintains/ca/intermediate/csr/example.linhancai.cn.cnf,内容如下

[ req ]
default_bits        = 2048
prompt              = no
default_md          = sha256
distinguished_name  = dn
req_extensions      = req_ext

[ dn ]
C  = CN
ST = Guangdong
L  = Shenzhen
O  = Linhancai
OU = Domain Cert
CN = example.linhancai.cn

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = example.linhancai.cn
DNS.2 = www.example.linhancai.cn
IP.1  = 192.168.100.20

2.2.2 签发域名证书

# 1. 切换到中间证书目录
cd ~/cert_maintains/ca/intermediate

# 2. 创建一个域名证书私钥,这里就不设置密码了
openssl genrsa -out private/example.linhancai.cn.key.pem 2048

# 3. 生成域名的CSR文件
openssl req \
  -config csr/example.linhancai.cn.cnf \
  -key private/example.linhancai.cn.key.pem \
  -new \
  -out csr/example.linhancai.cn.csr.pem

# 4. 中间CA签发证书,需要输入中间CA的私钥密码(Fqvgw9GlCrOlXwkym3kQ),然后输入y确认签发。
openssl ca \
  -config openssl.cnf \
  -extensions server_cert \
  -in csr/example.linhancai.cn.csr.pem \
  -out certs/example.linhancai.cn.cert.pem
  
# 5. 生成证书链,就是把中间CA证书和域名证书打包到一个证书文件。
cat \
  certs/example.linhancai.cn.cert.pem \
  certs/intermediate.cert.pem > certs/example.linhancai.cn.fullchain.pem

# 6. 验证证书的有效性
# 得到输出`ca/intermediate/certs/example.linhancai.cn.cert.pem: OK` 说明证书有效
cd ~/cert_maintains
openssl verify \
  -CAfile ca/root/certs/ca.cert.pem \
  -untrusted ca/intermediate/certs/intermediate.cert.pem \
  ca/intermediate/certs/example.linhancai.cn.cert.pem

2.2.3 配置nginx使用证书

server {
    listen 443 ssl;
    server_name example.linhancai.cn www.example.linhancai.cn;

    ssl_certificate     /etc/nginx/ssl/example.linhancai.cn.fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.linhancai.cn.key.pem;
    ssl_prefer_server_ciphers on; 
    add_header Strict-Transport-Security "max-age=31536000" always;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        return 200 "TLS OK\n";
    }
}

3. 其他问题

3.1 为什么我配置了证书后,浏览器还是提示不安全?

因为Root CA的证书是我们自签的,客户端系统不认识这个证书,你可以把它导入操作系统的信任证书列表中。