2024年10月3日 星期四

Root CA 指南 - 自建憑證管理單位的流程及方法

 

CA 證書單位

任何人都可以自己建立一個 CA 證書單位,並且用你所建立的 Root CA Key 去幫別人簽名,發給他人的證書基本上就會基於你的 Root CA Key 簽名得到背書,並關聯起來。

每個人的電腦系統都會安裝一些預設的 Root CA 證書,讓某些服務都可以使用,而瀏覽器本身也會自帶安裝一些 Root CA 的證書,讓民眾使用瀏覽器時可以信任某些單位的 CA,以 Chrome 為例,Chrome 有一個管理 CA 的計畫叫做 Chrome Root Program Policy (https://www.chromium.org/Home/chromium-security/root-ca-policy/),要申請加入要經過審查以及可能無法很快的就被加入 (根據網站說明是有可能一季一次)。


CA 證書單位常見的結構

  • Root CA 跟憑證
    • Private Key - CA 憑證的 Private Key,要用於幫別人簽名用,須保存好
    • Public Key - 對外展示的 Public Key (.CRT / .PEM)
    • CRL 吊銷證書列表 - 列出吊銷的 Public Key
    • OCSP 吊銷證書服務 (Port: 9080) - 驗證 Public Key 是否被吊銷
    • Intermediate CA (二級 CA 憑證)
      • Private Key
      • Public Key
      • CRL 吊銷證書列表
      • OCSP 吊銷證書服務 (Port: 9081)
      • 子憑證
        • Private Key (產生 CSR 的時候一併產生的)
        • Public Key (CSR 被 Intermediate CA Private Key 簽名過後的 Public Key / .crt / .pem)
    • 子憑證
      • Private Key (產生 CSR 的時候一併產生的)
      • Public Key (CSR 被 Root CA Private Key 簽名過後的 Public Key / .crt / .pem)

CRL 吊銷列表是一個檔案、OCSP Server 也是用於紀錄吊銷的憑證,這兩者基本上,瀏覽器訪問的時候不會特別去檢查 CRL 和 OCSP,CRL 只會不定期 (甚至需要自行去下載) 更新到電腦裡面,瀏覽器才會知道有哪些證書是吊銷過的。

在購買網域時,有時候會看到 EV SSL Extended Validation (EV),這個加註的功能是來自證書單位認定,他們會對證書做延伸驗證,就是用於檢查這個證書有沒有過期,延伸驗證基本上就是對 OCSP Server 發起請求去檢查有效性。

*一般狀況是不會有人對 OCSP 發起驗證,除非做 Extended Validation (EV) 之類的
*大多 EV 收費高,是根據發出的證書數量決定每個月的價格

OpenSSL CA Config 詳細說明

需要先建立一個: openssl-rootca.conf

[default]
name = ca # 自訂命名
domain_suffix = example.com # 需要填寫 Domain 名稱
aia_url = http://$name.$domain_suffix/root-ca.crt # 有用到變數
crl_url = http://crl.$domain_suffix/$name.crl
ocsp_url = http://ocsp.$domain_suffix:9080

name_opt = utf8,esc_ctrl,multiline,lname,align
default_ca = ca_default # 參照下方 ca_default 設定

[ca_dn]
countryName = "國家代碼兩碼"
organizationName = "組織名稱"
commonName = "example.com"


[ca_default]
home = ./rootca
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt # 預設證書的位置
private_key = $home/private/private.key # CSR Private Key 儲存位置
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = copy
default_days = 3650 # 10 年 CA Root
default_crl_days = 3650 # 10 年 CRL 效期
default_md = sha256
policy = policy_c_o_match # 參照下方 policy_c_o_match 設定

[policy_c_o_match]
countryName = match
stateOrProvinceName = optional
organizationName = match
organizationalUnitName = optional
commonName = supplied # 設定支援 common name
emailAddress = optional

[req]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn # 請參考上面的 ca_dn 設定
req_extensions = ca_ext

[root_ca_ext]
basicConstraints = critical,CA:true keyUsage = critical,keyCertSign,cRLSign # RFC 3280: "The nonRepudiation bit is asserted when the subject public key is used to verify digital signatures used to provide a non-repudiation service which protects against the signing entity falsely denying some action, excluding certificate or CRL signing. In the case of later conflict, a reliable third party may determine the authenticity of the signed data." subjectKeyIdentifier = hash [sub_ca_ext] # 二級 CA 設定 authorityInfoAccess = @issuer_info authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:true,pathlen:0 # pathlen:0 規定這個簽發下去的證書二級 CA 無法再簽發新的 CA crlDistributionPoints = @crl_info extendedKeyUsage = clientAuth,serverAuth keyUsage = critical,keyCertSign,cRLSign,digitalSignature # v3_intermediate_ca nameConstraints = @name_constraints # 參照下方 name_constraints 設定 subjectKeyIdentifier = hash [crl_info] URI.0 = $crl_url [issuer_info] caIssuers;URI.0 = $aia_url OCSP;URI.0 = $ocsp_url [name_constraints] permitted;DNS.0 = examples.com # 設定簽發出來可使用的 domain name 或 IP permitted;DNS.1 = localhost excluded;IP.0=0.0.0.0/0.0.0.0 excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0 [ocsp_ext] authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:false extendedKeyUsage = OCSPSigning keyUsage = critical,digitalSignature subjectKeyIdentifier = hash [server_ext] # 給 server 用的證書簽名的設定 authorityInfoAccess = @issuer_info authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:false # 簽發出來的證書不是 CA crlDistributionPoints = @crl_info extendedKeyUsage = clientAuth,serverAuth # 簽發出來的證書可以開 server, 當 client 證書拿去驗證 keyUsage = critical,digitalSignature,keyEncipherment subjectKeyIdentifier = hash [client_ext] # 給一般 client 用的證書簽名的設定 authorityInfoAccess = @issuer_info authorityKeyIdentifier = keyid:always basicConstraints = critical,CA:false # 簽發出來的證書不是 CA crlDistributionPoints = @crl_info extendedKeyUsage = clientAuth # 簽發出來的證書不可以開 server,只能當 client 證書用,除非加上 serverAuth keyUsage = critical,digitalSignature subjectKeyIdentifier = hash


OpenSSL CA 生成憑證

1. 產生 CA 證書的結構目錄 (目錄權限皆為 700)
  • certs (由 CA 簽名出的證書 Public Key Certificate / .crt)
  • db (目錄)
    • index (文件)
    • serial (文件)
    • crlnumber (文件)
  • private (目錄)
提前產生這些目錄,可以使用以下指令:

mkdir -p ./rootca/certs ./rootca/db ./rootca/private
openssl rand -hex 16 > ./rootca/db/serial
echo 1001 > ./rootca/db/crlnumber

2. 產生 Root CA 用的 CSR 憑證

openssl req -new -config openssl-rootca.conf -out RootCAPublicKey.csr -keyout ./rootca/private/private.key

3. 自行簽作為起始方 (Private Key 會讀取 openssl-rootca.conf 裡面的 ./rootca/private/private.key 位置

openssl ca -selfsign -config openssl-rootca.conf -in RootCAPublicKey.csr -out RootCA.crt -extensions root_ca_ext

現在 CA 憑證已經完成簽發,RootCA 就是 CA 憑證本身 (也是完成簽名的 PublicKey)。


建立 CRL 吊銷憑證證書設定

吊銷證書是附屬在 CA 憑證環節中重要的部分,他記錄了哪一些發出去的證書已經吊銷了。

openssl ca -gencrl -config openssl-rootca.conf -out ./rootca/ca.crl

現在已經完成產生基本的 crl 文件,裡面沒有任何被吊銷的證書。


CRL 吊銷證書

要吊銷證書,請執行指令

openssl ca -config openssl-rootca.conf -revoke ./rootca/certs/xxxx.crt -crl_reason keyCompromise 

-crl_reason 這個可以使用其他種,原因有:

  • keyCompromise
  • CACompromise
  • affiliationChanged
  • superseded
  • cessationOfOperation
  • certificateHold
  • removeFromCRL

每次吊銷完證書,需要重新執行一次 gencrl (參考上一小節)。


把所有發過的證書全部吊銷掉的腳本

for entry in "$(pwd)/rootca/certs/*"
do
    openssl ca -config openssl-rootca.conf -revoke $entry -crl_reason keyCompromise -passin pass:[你的 CA 上鎖密碼]
done

openssl ca -gencrl -config openssl-rootca.conf -out ./rootca/ca.crl

驗證證書有效性

openssl verify -extended_crl -verbose -CAfile ./crl-bundle -crl_check ./certificate


建立 OSCP 吊銷憑證證書 Server 設定

OCSP 吊銷證書是啟動一個 Server 服務,然後供服務連入查詢,OpenSSL 內建就可以開啟這樣的 OCSP Server。 不過一定要注意 OSCP Server 連入不能是 https。


1. 先簽發一個 OCSP 用的 CSR 待簽名證書

openssl req -new -newkey rsa:2048 -subj "/C=TW/O=Org/CN=example.com" -keyout ./rootca/private/ocsp-private.key -out ./rootca/ocsp.csr 

2. 用 CA 去發

openssl ca -config openssl-rootca.conf -in ./rootca/ocsp.csr -out ./rootca/ocsp.crt -extensions ocsp_ext -days 3650

3. 打開 OCSP Server

openssl ocsp -port 9080 -index ./rootca/db/index -rsigner ./rootca/ocsp.crt -rkey ./rootca/private/ocsp-private.key -CA ./rootca/ca.crt -text -ignore_err

4. 測試驗證 OCSP 

openssl ocsp -issuer ./rootca/ca.crt -CAfile ./rootca/ca.crt -cert ./rootca/ocsp.crt -url http://127.0.0.1:9080


作為 CA 去簽發憑證給最終端使用者

1. 先設定一個 openssl-server.conf 給這個證書定義一些內容

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

[dn]
C = TW
ST = Asia
L = Taiwan
O = Example
OU = Example
emailAddress = [email protected]
CN = localhost

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = my.example.com # 自己新增一些適用的證書範圍,也可以是 IP
DNS.2 = localhost

2. 簽發一個自用的 CSR 待簽名證書

openssl req -new -config openssl-server.conf -out ./my.csr -keyout ./my-private.key

3. 拿去 CA 簽發一個證書下來

openssl ca -config openssl-rootca.conf -in my.csr -out my.crt -extensions server_ext

如此一來就會產生一個被 CA 簽名過的證書了。


建立二級 CA

建立二級 CA 一樣會需要先定義一個 openssl-intermediate-ca.conf

目錄可學 root ca 一樣建立一樣的結構,目錄叫: ./intermediate-ca

1. 設定 openssl-intermediate-ca.conf:

[default]
name = intermediate-ca
domain_suffix = example.com
aia_url = http://$name.$domain_suffix/$name.crt
crl_url = http://$name.$domain_suffix/$name.crl
ocsp_url = http://ocsp.$name.$domain_suffix:9081

default_ca = ca_default
name_opt = utf8,esc_ctrl,multiline,lname,align

[ca_dn]
countryName = "TW"
organizationName = "Org Name"
commonName = "Intermediate CA"


[ca_default]
home = ./intermediate-ca
database = $home/db/index
serial = $home/db/serial
crlnumber = $home/db/crlnumber
certificate = $home/$name.crt
private_key = $home/private/$name.key
RANDFILE = $home/private/random
new_certs_dir = $home/certs
unique_subject = no
copy_extensions = copy
default_days = 365
default_crl_days = 30
default_md = sha256
policy = policy_c_o_match

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

[req]
default_bits = 4096
encrypt_key = yes
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
req_extensions = ca_ext

[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash


[sub_ca_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:true,pathlen:0
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,keyCertSign,cRLSign
nameConstraints = @name_constraints
subjectKeyIdentifier = hash

[crl_info]
URI.0 = $crl_url

[issuer_info]
caIssuers;URI.0 = $aia_url
OCSP;URI.0 = $ocsp_url


[name_constraints] 
permitted;DNS.0 = xx.example.com
permitted;DNS.1 = localhost 
excluded;IP.0=0.0.0.0/0.0.0.0 
excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0

[ocsp_ext]
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
extendedKeyUsage = OCSPSigning
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash


[server_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,digitalSignature,keyEncipherment
subjectKeyIdentifier = hash

[client_ext]
authorityInfoAccess = @issuer_info
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:false
crlDistributionPoints = @crl_info
extendedKeyUsage = clientAuth,serverAuth
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash

2. 產生一個待簽名 CSR (config 用的是 openssl-intermediate-ca.conf)

openssl req -new -config openssl-intermediate-ca.conf -out intermediate-ca.csr -keyout ./intermediate-ca/private/private.key

3. 用 Root CA 簽發 Intermediate CA

openssl ca -config openssl-rootca.conf -in ./intermediate-ca/intermediate-ca.csr -out ./intermediate-ca/intermediate-ca.crt -extensions sub_ca_ext


其他部分 - CA 單位驗證擁有權

通常作為 CA 要幫其他人去簽發證書,都會先去驗證他人對於他的 domain 擁有權,才能幫他們背書,這就是普遍發憑證的時候會要求去 DNS 加 txt 紀錄或是用 email 驗證的原因。


開 Server 測試

可先參考前面章節 (作為 CA 去簽發憑證給最終端使用者) 簽發一個證書,得到 my.crt 和 my-private.key 這兩個檔案,再來開 Server。
package main

import "github.com/gin-gonic/gin"

func main() {

  r := gin.Default()
  r.GET("/", func(ctx *gin.Context) {
    ctx.String(200, "ok")
  })

  r.RunTLS("0.0.0.0:443", "my.crt", "my-private.key")
}

接著,應該要先去點擊 ca.crt 兩下,讓電腦去信任他,並且把所有信任選項都打勾,開啟 Server 之後,透過瀏覽器去看,就會得到綠色的 https 了。 (前提是要手動電腦自己去點那個憑證然後信任)


沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014