自建 CA 憑證授權中心實務

在企業內部環境或開發測試場景中,自建憑證授權中心(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

設計原則

  1. 根 CA 離線保存:根 CA 的私鑰應保存在離線環境,僅在簽發中繼 CA 憑證時使用
  2. 中繼 CA 依用途分類:可依據憑證用途建立不同的中繼 CA
  3. 憑證有效期規劃
    • 根 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

安全最佳實踐

  1. 私鑰保護

    • 根 CA 私鑰使用強密碼加密
    • 根 CA 私鑰存放於離線環境或 HSM
    • 設定適當的檔案權限(400 或 600)
  2. 憑證生命週期管理

    • 建立憑證清單與追蹤機制
    • 設定憑證到期提醒
    • 定期輪換憑證
  3. CRL 與 OCSP

    • 定期更新 CRL
    • 考慮建置 OCSP 服務提供即時憑證狀態查詢
  4. 備份策略

    • 定期備份 CA 目錄
    • 備份應包含私鑰、憑證、設定檔與資料庫
    • 備份檔案應加密存放

總結

自建 CA 架構需要審慎規劃,本文介紹的階層式架構可以在安全性與便利性之間取得平衡。透過分離根 CA 與中繼 CA,可以在保護根 CA 私鑰的同時,便利地簽發與管理終端憑證。

在實際部署時,建議:

  1. 嚴格保護根 CA 私鑰,考慮使用硬體安全模組(HSM)
  2. 建立完善的憑證生命週期管理流程
  3. 定期檢視並更新即將過期的憑證
  4. 保持 CRL 更新,確保撤銷的憑證不被信任

透過本文的指引,您應該能夠建立一套完整的內部 PKI 架構,滿足企業內部的憑證需求。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy