在企業內部環境或開發測試場景中,自建憑證授權中心(Certificate Authority,CA)是管理 SSL/TLS 憑證的常見做法。本文將介紹如何使用 OpenSSL 從零開始建立完整的 PKI(Public Key Infrastructure)架構,包含根 CA、中繼 CA 以及終端憑證的簽發與管理。
CA 架構設計
在設計 CA 架構時,建議採用階層式架構,將根 CA 與中繼 CA 分離。這種設計有以下優點:
階層式 CA 架構
1
2
3
4
5
6
7
8
9
| Root CA(離線保存)
│
├── Intermediate CA 1(伺服器憑證)
│ ├── Web Server Certificate
│ └── API Server Certificate
│
└── Intermediate CA 2(客戶端憑證)
├── User Certificate
└── Device Certificate
|
設計原則
- 根 CA 離線保存:根 CA 的私鑰應保存在離線環境,僅在簽發中繼 CA 憑證時使用
- 中繼 CA 依用途分類:可依據憑證用途建立不同的中繼 CA
- 憑證有效期規劃:
- 根 CA:10-20 年
- 中繼 CA:5-10 年
- 終端憑證:1-2 年
目錄結構規劃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| /opt/ca/
├── root-ca/
│ ├── private/ # 私鑰目錄
│ ├── certs/ # 憑證目錄
│ ├── newcerts/ # 新簽發憑證
│ ├── crl/ # 憑證撤銷列表
│ ├── index.txt # 憑證資料庫
│ └── serial # 序號檔案
└── intermediate-ca/
├── private/
├── certs/
├── newcerts/
├── crl/
├── csr/ # 憑證請求
├── index.txt
└── serial
|
使用 OpenSSL 建立根 CA
準備工作目錄
首先建立根 CA 所需的目錄結構:
1
2
3
4
5
6
7
8
9
10
11
| # 建立根 CA 目錄
mkdir -p /opt/ca/root-ca/{private,certs,newcerts,crl}
cd /opt/ca/root-ca
# 設定私鑰目錄權限
chmod 700 private
# 初始化憑證資料庫
touch index.txt
echo 1000 > serial
echo 1000 > crlnumber
|
根 CA 設定檔
建立根 CA 的 OpenSSL 設定檔 /opt/ca/root-ca/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
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
| # OpenSSL Root CA Configuration
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /opt/ca/root-ca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
RANDFILE = $dir/private/.rand
private_key = $dir/private/root-ca.key
certificate = $dir/certs/root-ca.crt
crlnumber = $dir/crlnumber
crl = $dir/crl/root-ca.crl
crl_extensions = crl_ext
default_crl_days = 30
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 375
preserve = no
policy = policy_strict
[ policy_strict ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ policy_loose ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
string_mask = utf8only
default_md = sha256
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
countryName_default = TW
stateOrProvinceName_default = Taiwan
localityName_default = Taipei
0.organizationName_default = My Organization
organizationalUnitName_default = IT Department
[ 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
[ crl_ext ]
authorityKeyIdentifier=keyid:always
|
產生根 CA 私鑰與憑證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| cd /opt/ca/root-ca
# 產生根 CA 私鑰(4096 位元,使用 AES256 加密)
openssl genrsa -aes256 -out private/root-ca.key 4096
chmod 400 private/root-ca.key
# 產生根 CA 憑證(有效期 20 年)
openssl req -config openssl.cnf \
-key private/root-ca.key \
-new -x509 -days 7300 -sha256 \
-extensions v3_ca \
-out certs/root-ca.crt \
-subj "/C=TW/ST=Taiwan/L=Taipei/O=My Organization/OU=IT Department/CN=My Root CA"
# 驗證根 CA 憑證
openssl x509 -noout -text -in certs/root-ca.crt
|
建立中繼 CA
準備中繼 CA 目錄
1
2
3
4
5
6
7
8
| # 建立中繼 CA 目錄
mkdir -p /opt/ca/intermediate-ca/{private,certs,newcerts,crl,csr}
cd /opt/ca/intermediate-ca
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > crlnumber
|
中繼 CA 設定檔
建立中繼 CA 的 OpenSSL 設定檔 /opt/ca/intermediate-ca/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
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
| # OpenSSL Intermediate CA Configuration
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /opt/ca/intermediate-ca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
RANDFILE = $dir/private/.rand
private_key = $dir/private/intermediate-ca.key
certificate = $dir/certs/intermediate-ca.crt
crlnumber = $dir/crlnumber
crl = $dir/crl/intermediate-ca.crl
crl_extensions = crl_ext
default_crl_days = 30
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 365
preserve = no
policy = policy_loose
[ policy_loose ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
string_mask = utf8only
default_md = sha256
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ server_cert_san ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ client_cert ]
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
[ crl_ext ]
authorityKeyIdentifier=keyid:always
|
產生中繼 CA 私鑰與 CSR
1
2
3
4
5
6
7
8
9
10
11
12
| cd /opt/ca/intermediate-ca
# 產生中繼 CA 私鑰
openssl genrsa -aes256 -out private/intermediate-ca.key 4096
chmod 400 private/intermediate-ca.key
# 產生中繼 CA 憑證簽署請求(CSR)
openssl req -config openssl.cnf \
-new -sha256 \
-key private/intermediate-ca.key \
-out csr/intermediate-ca.csr \
-subj "/C=TW/ST=Taiwan/L=Taipei/O=My Organization/OU=IT Department/CN=My Intermediate CA"
|
使用根 CA 簽發中繼 CA 憑證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| cd /opt/ca/root-ca
# 使用根 CA 簽發中繼 CA 憑證(有效期 10 年)
openssl ca -config openssl.cnf \
-extensions v3_intermediate_ca \
-days 3650 -notext -md sha256 \
-in /opt/ca/intermediate-ca/csr/intermediate-ca.csr \
-out /opt/ca/intermediate-ca/certs/intermediate-ca.crt
# 驗證中繼 CA 憑證
openssl x509 -noout -text -in /opt/ca/intermediate-ca/certs/intermediate-ca.crt
# 驗證憑證鏈
openssl verify -CAfile certs/root-ca.crt \
/opt/ca/intermediate-ca/certs/intermediate-ca.crt
|
建立憑證鏈檔案
1
2
3
4
5
6
| # 建立 CA 憑證鏈(中繼 CA + 根 CA)
cat /opt/ca/intermediate-ca/certs/intermediate-ca.crt \
/opt/ca/root-ca/certs/root-ca.crt > \
/opt/ca/intermediate-ca/certs/ca-chain.crt
chmod 444 /opt/ca/intermediate-ca/certs/ca-chain.crt
|
簽發終端憑證
簽發伺服器憑證
產生私鑰與 CSR
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 建立憑證目錄
mkdir -p /opt/ca/certs/example.com
cd /opt/ca/certs/example.com
# 產生私鑰(不加密,適用於伺服器自動啟動)
openssl genrsa -out example.com.key 2048
chmod 400 example.com.key
# 產生 CSR
openssl req -new -sha256 \
-key example.com.key \
-out example.com.csr \
-subj "/C=TW/ST=Taiwan/L=Taipei/O=My Organization/OU=Web Services/CN=example.com"
|
使用中繼 CA 簽發憑證
對於需要支援多個域名的憑證,建立 SAN(Subject Alternative Name)設定檔:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 建立 SAN 設定檔
cat > example.com.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
IP.1 = 192.168.1.100
EOF
|
簽發憑證:
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
| cd /opt/ca/intermediate-ca
# 簽發伺服器憑證(有效期 1 年)
openssl ca -config openssl.cnf \
-extensions server_cert \
-days 365 -notext -md sha256 \
-in /opt/ca/certs/example.com/example.com.csr \
-out /opt/ca/certs/example.com/example.com.crt
# 或使用 SAN 設定檔簽發
openssl x509 -req -sha256 \
-in /opt/ca/certs/example.com/example.com.csr \
-CA certs/intermediate-ca.crt \
-CAkey private/intermediate-ca.key \
-CAcreateserial \
-out /opt/ca/certs/example.com/example.com.crt \
-days 365 \
-extfile /opt/ca/certs/example.com/example.com.ext
# 驗證憑證
openssl x509 -noout -text -in /opt/ca/certs/example.com/example.com.crt
# 驗證憑證鏈
openssl verify -CAfile certs/ca-chain.crt \
/opt/ca/certs/example.com/example.com.crt
|
簽發客戶端憑證
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
| # 建立客戶端憑證目錄
mkdir -p /opt/ca/certs/clients
cd /opt/ca/certs/clients
# 產生客戶端私鑰
openssl genrsa -aes256 -out user1.key 2048
# 產生 CSR
openssl req -new -sha256 \
-key user1.key \
-out user1.csr \
-subj "/C=TW/ST=Taiwan/L=Taipei/O=My Organization/OU=Users/CN=User One/emailAddress=user1@example.com"
# 簽發客戶端憑證
cd /opt/ca/intermediate-ca
openssl ca -config openssl.cnf \
-extensions client_cert \
-days 365 -notext -md sha256 \
-in /opt/ca/certs/clients/user1.csr \
-out /opt/ca/certs/clients/user1.crt
# 匯出為 PKCS#12 格式(供瀏覽器或應用程式使用)
openssl pkcs12 -export \
-out /opt/ca/certs/clients/user1.p12 \
-inkey /opt/ca/certs/clients/user1.key \
-in /opt/ca/certs/clients/user1.crt \
-certfile certs/ca-chain.crt
|
簽發萬用字元憑證
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
| # 產生萬用字元憑證私鑰
openssl genrsa -out wildcard.example.com.key 2048
# 產生 CSR
openssl req -new -sha256 \
-key wildcard.example.com.key \
-out wildcard.example.com.csr \
-subj "/C=TW/ST=Taiwan/L=Taipei/O=My Organization/CN=*.example.com"
# 建立 SAN 設定檔
cat > wildcard.example.com.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.example.com
DNS.2 = example.com
EOF
# 簽發憑證
openssl x509 -req -sha256 \
-in wildcard.example.com.csr \
-CA /opt/ca/intermediate-ca/certs/intermediate-ca.crt \
-CAkey /opt/ca/intermediate-ca/private/intermediate-ca.key \
-CAcreateserial \
-out wildcard.example.com.crt \
-days 365 \
-extfile wildcard.example.com.ext
|
憑證管理
憑證撤銷
當憑證需要撤銷時(例如私鑰洩露),使用以下步驟:
1
2
3
4
5
6
7
8
9
10
11
12
| cd /opt/ca/intermediate-ca
# 撤銷憑證
openssl ca -config openssl.cnf \
-revoke /opt/ca/certs/example.com/example.com.crt
# 產生憑證撤銷列表(CRL)
openssl ca -config openssl.cnf \
-gencrl -out crl/intermediate-ca.crl
# 查看 CRL 內容
openssl crl -in crl/intermediate-ca.crl -noout -text
|
憑證更新
憑證過期前應及時更新:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 查看憑證有效期
openssl x509 -in certificate.crt -noout -dates
# 建立憑證到期監控腳本
cat > /opt/ca/scripts/check-expiry.sh << 'EOF'
#!/bin/bash
CERT_DIR="/opt/ca/certs"
WARN_DAYS=30
find "$CERT_DIR" -name "*.crt" | while read cert; do
expiry=$(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ $days_left -lt $WARN_DAYS ]; then
echo "WARNING: $cert expires in $days_left days"
fi
done
EOF
chmod +x /opt/ca/scripts/check-expiry.sh
|
憑證資訊查詢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 查看憑證詳細資訊
openssl x509 -in certificate.crt -noout -text
# 查看憑證主體
openssl x509 -in certificate.crt -noout -subject
# 查看憑證發行者
openssl x509 -in certificate.crt -noout -issuer
# 查看憑證有效期
openssl x509 -in certificate.crt -noout -dates
# 查看憑證指紋
openssl x509 -in certificate.crt -noout -fingerprint -sha256
# 驗證憑證與私鑰是否匹配
openssl x509 -in certificate.crt -noout -modulus | openssl md5
openssl rsa -in private.key -noout -modulus | openssl md5
|
憑證格式轉換
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # PEM 轉 DER
openssl x509 -in certificate.pem -outform DER -out certificate.der
# DER 轉 PEM
openssl x509 -in certificate.der -inform DER -outform PEM -out certificate.pem
# PEM 轉 PKCS#7
openssl crl2pkcs7 -nocrl -certfile certificate.pem -out certificate.p7b
# PKCS#12 轉 PEM
openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes
# PEM 轉 PKCS#12
openssl pkcs12 -export -out certificate.p12 \
-inkey private.key -in certificate.crt -certfile ca-chain.crt
|
安全最佳實踐
私鑰保護
- 根 CA 私鑰使用強密碼加密
- 根 CA 私鑰存放於離線環境或 HSM
- 設定適當的檔案權限(400 或 600)
憑證生命週期管理
- 建立憑證清單與追蹤機制
- 設定憑證到期提醒
- 定期輪換憑證
CRL 與 OCSP
- 定期更新 CRL
- 考慮建置 OCSP 服務提供即時憑證狀態查詢
備份策略
- 定期備份 CA 目錄
- 備份應包含私鑰、憑證、設定檔與資料庫
- 備份檔案應加密存放
總結
自建 CA 架構需要審慎規劃,本文介紹的階層式架構可以在安全性與便利性之間取得平衡。透過分離根 CA 與中繼 CA,可以在保護根 CA 私鑰的同時,便利地簽發與管理終端憑證。
在實際部署時,建議:
- 嚴格保護根 CA 私鑰,考慮使用硬體安全模組(HSM)
- 建立完善的憑證生命週期管理流程
- 定期檢視並更新即將過期的憑證
- 保持 CRL 更新,確保撤銷的憑證不被信任
透過本文的指引,您應該能夠建立一套完整的內部 PKI 架構,滿足企業內部的憑證需求。