使用 OpenSSL 建立私有 CA:2 中間證書

sparkdev發表於2019-02-18

OpenSSL 建立私有 CA 三部曲:
使用 OpenSSL 建立私有 CA:1 根證照
使用 OpenSSL 建立私有 CA:2 中間證照
使用 OpenSSL 建立私有 CA:3 使用者證照

本文將在前文《使用 OpenSSL 建立私有 CA:1 根證照》的基礎上介紹如何為私有 CA 建立中間證照。
說明:本系列文章的演示環境為 Ubuntu 18.04,OpenSSL 的版本為 1.1.0g。

為什麼要建立中間證照?

使用 CA 的根證照是可以直接簽發使用者證照的,那麼為什麼還還要建立中間證照呢?
大概有兩個原因:

  • 安全性
  • 建立中間證照頒發機構(CA)

先說安全性,通過簽發中間證照,再用中間證照籤發使用者證照的方式,可以最大程度的減少更證照的使用頻率,甚至可以離線儲存根證照。如果發現中間證照被破壞,還可以吊銷該中間證照並頒發新的中間證照。
再來說說中間證照頒發機構(CA),一箇中間證照頒發機構(CA)是一個實體,它具有根 CA 頒發的中間證照,並且可以代表根 CA 頒發使用者證照,從而形成一條被信任的證照鏈。
這裡有必要介紹下證照產業的基本玩法:首先根證照的所有者(大的證照廠商)會和 OS 廠商合作,讓 OS 等環境預設信任自己的根證照。然後根證照的所有者會簽發中間證照賣給小一些的證照廠商。這些小的證照廠商再用中間證照建立最終證照賣給普通消費者。
舉個例子,像我們常用的 Windows 作業系統預設就信任了很多廠商的根證照,比如 VeriSign 的證照,並且這些證照還會隨著 Windows 系統的更新而更新:

上圖中,VeriSign 是根證照所有者,你安裝 windows 作業系統時,VeriSign 的根證照就被安裝到信任列表中了。Symantec Class 3 SHA256 Code Signing CA 則是 Symantec 公司從 VeriSign 公司購買的中間證照。GrapeCity inc. 則是 GrapeCity 公司從 Symantec 公司購買的最終證照用來對產品進行簽名。當然,有時候根證照廠商比如 VeriSign 等也會直接向終端使用者銷售證照。

為了模擬中間證照頒發機構(CA),本文將使用前文中建立的根證照建立一個名稱為 power 的中間證照。

準備中間 CA 的配置檔案

建立檔案配置檔案 powerca/powerca.cnf,編輯其內容如下:

# OpenSSL root CA configuration file.
# v1
[ ca ]
# `man ca`
default_ca = CA_default
[ CA_default ]
# Directory and file locations.
dir = /home/nick/projects/myca/powerca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/db/index
serial = $dir/db/serial
RANDFILE = $dir/private/random
# The root key and root certificate.
private_key = $dir/private/powerca.key.pem
certificate = $dir/certs/powerca.cert.pem
# For certificate revocation lists.
crlnumber = $dir/db/crlnumber
crl = $dir/crl/powerca.crl.pem
crl_extensions = crl_ext
default_crl_days = 30
# SHA-1 is deprecated, so use SHA-2 instead.
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 3750
copy_extensions = copy
preserve = no
policy = policy_loose
[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
# Options for the `req` tool (`man req`).
# Optionally, specify some defaults.
prompt = no
input_password = 123456
default_bits = 2048
distinguished_name = req_distinguished_name
string_mask = utf8only
# SHA-1 is deprecated, so use SHA-2 instead.
default_md = sha256
# Extension to add when the -x509 option is used.
# make sure use x509_extensions, do not use req_extensions.
x509_extensions = v3_ca
# use the req_extensions not work.
#req_extensions = v3_ca
[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName = CN
stateOrProvinceName = ShaanXi
localityName = Xian
organizationName = NickLi Ltd
organizationalUnitName = NickLi Ltd CA
commonName = NickLi Power CA
emailAddress = ljfpower@163.com
[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always
[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

這個配置檔案的內容和 rootca/rootca.cnf 有很多相同之處,下面是一些比較重要的不同之處:

[ CA_default ]
dir             = /home/nick/projects/myca/powerca
private_key     = $dir/private/powerca.key.pem
certificate     = $dir/certs/powerca.cert.pem
crl             = $dir/crl/powerca.crl.pem
policy          = policy_loose
[ req_distinguished_name ]
commonName = NickLi Power CA 

CA_default 中的變化主要是告訴 OpenSSL 相關檔案的路徑,而中間證照的 commonName 必須不同於根證照。

準備目錄和檔案

與 rootca 目錄相同,我們需要在 powerca 目錄下建立如下的目錄和檔案:

powerca/certs/
powerca/db/
powerca/private/
powerca/crl/
powerca/csr/
powerca/newcerts/

powerca/db/index
powerca/db/serial
powerca/db/crlnumber

其中的 powerca/private 目錄需同樣要 700 的許可權,我們使用下面的指令碼來建立這些目錄和檔案:

#!/bin/bash

# create dir certs db private crl csr newcerts under powerca dir.
if [ ! -d powerca/certs ]; then
    mkdir -p powerca/certs
fi

if [ ! -d powerca/db ]; then
    mkdir -p powerca/db
    touch powerca/db/index
    openssl rand -hex 16 > powerca/db/serial
    echo 1001 > powerca/db/crlnumber
fi

if [ ! -d powerca/private ]; then
    mkdir -p powerca/private
    chmod 700 powerca/private
fi

if [ ! -d powerca/crl ]; then
    mkdir -p powerca/crl
fi

if [ ! -d powerca/newcerts ]; then
    mkdir -p powerca/newcerts
fi

if [ ! -d powerca/csr ]; then
    mkdir -p powerca/csr
fi

把上面的程式碼儲存到 myca/powerhelper.sh 檔案中,然後 cd 到 myca 目錄下執行:

$ ./powerhelper.sh

此時當前目錄為 myca,powerca 下的子目錄和檔案都已經建立成功。

建立中間證照的祕鑰

進入 powerca 目錄:

$ cd powerca 

執行下面的命令重建私鑰:

$ openssl genrsa -aes256 -out powerca/private/powerca.key.pem 4096

這裡筆者設定的密碼為:123456,記住這個密碼,後面還會用到。然後為了確保安全,把祕鑰的許可權設定為 400:

$ chmod 400 private/powerca.key.pem

此時當前目錄為 myca/powerca。

建立 Certificate Signing Requests(csr)

要建立中間證照,需要使用帶有 v3_intermediate_ca 副檔名的根 CA 來簽署中間 CSR,其中 v3_intermediate_ca 擴充套件的配置資訊就在 powerca/powerca.cnf 中:

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

現在直接建立 csr 就行了:

$ openssl req -new \
    -config powerca.cnf \
    -sha256 \
    -key private/powerca.key.pem \
    -out csr/powerca.csr.pem

下面的命令可以檢查生成的 csr:

$ openssl req -text -noout -in csr/powerca.csr.pem

注意,csr 中包含了 CA 的基本資訊,和公鑰資訊。

建立中間證照

下面是整個過程中最為關鍵的地方!通過 Root CA 的資訊和使用者的 csr 為使用者生成證照。
建立中間證照需要用到 rootca/rootca.cnf 中的配置資訊,所以先進入 myca 目錄:

# 從 powerca 目錄回到 myca 目錄
$ cd ..      
$ openssl ca -config rootca/rootca.cnf \
    -extensions v3_intermediate_ca \
    -days 3650 -notext -md sha256 \
    -in powerca/csr/powerca.csr.pem \
    -out powerca/certs/powerca.cert.pem

在互動式的提示中輸入私鑰的密碼 123456,並同意其它的確認提示,就完成了根證照的生成操作:

rootca/db/index 檔案是 OpenSSL CA 工具儲存證照的資料庫,請不要手動修改這個檔案(除非你清楚的知道自己在幹什麼)。此時它應該包含了根證照和中間證照的資訊:

證照生成後我們把它的許可權修改為 444:

$ chmod 444 powerca/certs/powerca.cert.pem

驗證中間證照

通過下面的命令驗證中間證照:

$ openssl x509 -noout -text -in powerca/certs/powerca.cert.pem

在中間證照中,Subject 的 Common Name 為 "NickLi Power CA",而 Issuer 的 Common Name 為 "NickLi Root CA"。
還可以通過下面的命令來檢查中間證照的狀態:

$ openssl verify -CAfile rootca/certs/rootca.cert.pem powerca/certs/powerca.cert.pem

建立證照鏈檔案

當 web 瀏覽器等應用程式試圖驗證中間 CA 頒發的證照時,它還必須根據根證照驗證中間證照。這就需要構建完整的證照信任鏈供應用程式驗證。所謂的證照鏈,簡單的說就是把根證照和中間證照按照順序放置在同一個證照檔案中。重點是:中間證照在上面,根證照在下面。比如為我們的中間證照建立證照鏈:

$ cat powerca/certs/powerca.cert.pem \
      rootca/certs/rootca.cert.pem > powerca/certs/powerca-chain.cert.pem
$ chmod 444 powerca/certs/powerca-chain.cert.pem

注意:在區域網環境中,我們一般會把生成的這個證照鏈安裝到使用者的機器上。

pem 格式的證照、證照鏈檔案適用的場景比較多,但是在 windows 系統中一般使用 p12 格式,所以我們還需要建立一個 p12 格式的證照鏈:

$ openssl pkcs12 -export \
    -name "powerca chain" \
    -inkey powerca/private/powerca.key.pem \
    -in powerca/certs/powerca.cert.pem \
    -certfile powerca/certs/powerca-chain.cert.pem \
    -out powerca/certs/powerca-chain.cert.p12

這個過程中需要輸入私鑰 powerca/private/powerca.key.pem 的密碼(這裡是 123456),並且為新證照設定的密碼。

總結

現在我們已經有了私有 CA 的根證照並且生成了中間證照和證照鏈,在接下來的《使用 OpenSSL 建立私有 CA:3 使用者證照》一文中我們將詳細的介紹如何使用中間證照建立使用者證照,以及如何把證照部署到 web 站點和客戶端。

參考:
OpenSSL Certificate Authority
《openssl-cookbook》

相關文章