如何使用 OpenSSL 簽發中介 CA

通常我們在私人用途的服務需要憑證時可能會考慮自簽憑證(或如果服務公開在網際網路的話可以考慮 Let's Encrypt[1]),服務較多時則會考慮自簽 CA 之後再往下發。

而在一定規模的組織內部時,有可能在根 CA(Root CA)下面還有其他中介 CA(Intermediate CA),這篇就是來記錄一下要怎麼透過 OpenSSL 工具簽發中介 CA。

簽發 Root CA

首先我們在有中介 CA 之前,必須要有 Root CA,這邊會介紹怎麼簽發 Root CA:

建立 Root CA 用的 Key

建立 Key 應該是整個環節中最簡單的步驟了,利用簡單的 openssl genrsa 即可,這邊採用 AES256 加密 Key 並將長度設為 4096(如果你不想要加上密碼,就不要下 -aes256,但你的 Key 就少了一層保護):

$ openssl genrsa -aes256 -out ca.key 4096
Generating RSA private key, 4096 bit long modulus
# ......
e is 65537 (0x10001)
Enter pass phrase for ca.key:
Verifying - Enter pass phrase for ca.key:
$ 

如此一來你就成功的建立了一個給 Root CA 用的 Key,請妥善保管它(跟它的密碼),我們接下來會用這個 Key 來簽發 Root CA。

建立 Root CA

有了 Key 之後,我們就要用這把 Key 來建立 Root CA 了,這裡利用 openssl req 直接簽發 Root CA:

$ openssl req -new -x509 -key ca.key \
              -days 7300 -sha256 \
              -extensions v3_ca \
              -out ca.pem
Enter pass phrase for ca.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Davy.TW
Organizational Unit Name (eg, section) []:Davy.TW Certificate Authority
Common Name (e.g. server FQDN or YOUR name) []:Davy.TW Root CA
Email Address []:
$

如此一來就會用剛剛的 Key 簽發一張 x509 的 Root CA 憑證,一般來說 openssl req 會需要 -config 來帶入一個設定檔,預設會使用 /etc/pki/tls/openssl.cnf(Red Hat)/ /etc/ssl/openssl.cnf(Ubuntu/FreeBSD),其內部會預先定義好一些設定值(例如 -extension v3_ca 的設定值)。

驗證 Root CA

簽發好 Root CA 了之後,我們可以來檢查一下憑證是不是如我們預期的建立了:

$ openssl x509 -noout -text -in ca.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 13178084018309537468 (0xb6e1f5981b979ebc)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Certificate Authority, CN=Davy.TW Root CA
        Validity
            Not Before: Nov 28 17:06:10 2019 GMT
            Not After : Nov 23 17:06:10 2039 GMT
        Subject: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Certificate Authority, CN=Davy.TW Root CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus: ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                DC:74:06:33:97:F5:E6:58:2E:51:66:14:81:B4:4F:EF:4C:78:9B:9B
            X509v3 Authority Key Identifier:
                keyid:DC:74:06:33:97:F5:E6:58:2E:51:66:14:81:B4:4F:EF:4C:78:9B:9B

            X509v3 Basic Constraints:
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         ...
$

我們可以看到以下資訊:

  • Signature Algorithm: 簽章演算法
  • Validity: 憑證有效期限
  • Public-Key 長度
  • Issuer: 簽發單位,這邊應該會顯示我們剛剛填寫的內容
  • Subject: 憑證主體,因為我們是自簽 Root CA,所以應該會跟 Issuer 相同

另外,因為我們有使用 v3_ca 的擴充,所以還會看到這個憑證帶有 X509v3 擴充資訊,並顯示 X509v3 Basic Constraints 中有 CA:TRUE,表示這張憑證當作 CA 使用。

簽發 Intermediate CA

在有了 Root CA 之後,我們就可以利用這個 Root CA 在底下簽發 Intermediate CA,接下來會稍稍有點複雜,因為預設的 openssl.cnf 通常都不會帶有適用的現成 extension 可以用,我們必須自訂這些東西:

建立 Intermediate CA Key

我們假設 Intermediate CA 與 Root CA 的單位不同,所以我們需要使用不同的 Key 來簽名,一樣,使用 openssl genrsa 簡單地建立 Key:

$ openssl genrsa -aes256 -out intermediate.key 4096
Generating RSA private key, 4096 bit long modulus
# ...
e is 65537 (0x10001)
Enter pass phrase for intermediate.key:
Verifying - Enter pass phrase for intermediate.key:
$

建立 Intermediate CA CSR

既然兩個 CA 的單位不同,總不可能讓 Intermediate CA 用的 Key 流出去到 Root CA 那邊吧?
所以我們要先建立 CSR(Certificate Signing Request),再將 CSR 交給 Root CA 來核發憑證:

$ openssl req -sha256 -new -key intermediate.key -out intermediate.csr
Enter pass phrase for intermediate.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Davy.TW
Organizational Unit Name (eg, section) []:Davy.TW Blog Certificate Authority
Common Name (e.g. server FQDN or YOUR name) []:Davy.TW Blog Intermediate CA
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$

CSR 會記錄憑證要記載的 Subject 事項,因此我們剛剛輸入的內容等下都會變成憑證的一部分。

簽發 Intermediate CA

在將 CSR 交給 Root CA 的單位之後,我們就可以準備簽發 Intermediate CA,這邊我們先定義一個給 Intermediate CA 用的 extension:

$ cat intermediate.ext
[ intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true, pathlen:0
$

為什麼要自己定義呢? 用原本的 v3_ca 不好嗎?
當然,如果你想要讓這個 CA 跟 Root CA 一樣可以繼續往下簽 Intermediate CA 的話,那你可以選擇繼續使用 v3_ca
上面定義的 intermediate_ca 相較預設的 v3_ca 多了一個 pathlen:0,表示他往下還可以有 0 個 Intermediate CA(也就是沒有的意思)。

$ openssl x509 -req -in intermediate.csr \
               -CA ca.pem -CAkey ca.key \
               -CAserial ca.serial -CAcreateserial \
               -days 730 \
               -extensions intermediate_ca -extfile intermediate.ext \
               -out intermediate.pem
Signature ok
subject=/C=TW/ST=Taiwan/O=Davy.TW/OU=Davy.TW Blog Certificate Authority/CN=Davy.TW Blog Intermediate CA
Getting CA Private Key
Enter pass phrase for ca.key:
$

其中有幾個選項需要介紹一下:

  • -CAserial ca.serial: 每個 CA 簽發憑證時都需要一個流水號,這裡是指出流水號記錄檔檔名
  • -CAcreateserial: 只有當 CA 初次簽發時需要,他會初始化一個流水號出來,第二次之後簽發就不要帶這個參數,以免流水號被重置
  • -extensions intermediate_ca: 我們想要指定剛剛我們建立的 extension
  • -extfile intermediate.ext: 需要指定包含 extension 的設定檔

驗證 Intermediate CA

一樣,首先先看一下憑證內容是不是正確的:

$ openssl x509 -noout -text -in intermediate.pem 
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 15568167468282800826 (0xd80d3dd426c9eaba)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Certificate Authority, CN=Davy.TW Root CA
        Validity
            Not Before: Nov 29 10:09:13 2019 GMT
            Not After : Nov 28 10:09:13 2021 GMT
        Subject: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Blog Certificate Authority, CN=Davy.TW Blog Intermediate CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus: ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                EA:0D:FA:82:1B:80:BA:00:5A:0D:37:7B:DB:52:71:85:E0:84:3F:13
            X509v3 Authority Key Identifier:
                keyid:DC:74:06:33:97:F5:E6:58:2E:51:66:14:81:B4:4F:EF:4C:78:9B:9B

            X509v3 Basic Constraints:
                CA:TRUE, pathlen:0
    Signature Algorithm: sha256WithRSAEncryption
        ...
$

可以看到 Issuer 是我們 Root CA 的名稱,而 Subject 是 CSR 中填寫的內容。
並且有將 pathlen 設為 0,表示這個 CA 只能用來簽發終端憑證。

另外,我們還可以透過 openssl verify 驗證這個憑證是不是由 Root CA 一路發下來的。

$ openssl verify -CAfile ca.pem intermediate.pem
intermediate.pem: OK

簽發終端憑證

好的,既然我們有了中介 CA,那我們就可以來試試看簽發終端憑證並驗證是不是有成功形成一個 chain:

建立終端憑證 Key/CSR

這部份就不累述,一樣的步驟:

$ openssl genrsa -aes256 -out endentity.key 4096
Generating RSA private key, 4096 bit long modulus
...
e is 65537 (0x10001)
Enter pass phrase for endentity.key:
Verifying - Enter pass phrase for endentity.key:
$ openssl req -sha256 -new -key endentity.key -out endentity.csr
Enter pass phrase for endentity.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Davy.TW
Organizational Unit Name (eg, section) []:Davy.TW Blog
Common Name (e.g. server FQDN or YOUR name) []:blog.davy.tw
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$

這邊要注意的是,由於我們的終端憑證會使用在服務上,所以 Common Name 的部分要記得填寫服務的 FQDN (domain name)。

簽發終端憑證

由於我們只是要簽發終端憑證而不是其他特殊用途的憑證,所以就不需要再另外定義 extension:

$ openssl x509 -req -in endentity.csr \
               -CA intermediate.pem -CAkey intermediate.key \
               -CAserial intermediate.serial -CAcreateserial \
               -days 365 \
               -out endentity.pem
Signature ok
subject=/C=TW/ST=Taiwan/O=Davy.TW/OU=Davy.TW Blog/CN=blog.davy.tw
Getting CA Private Key
Enter pass phrase for intermediate.key:
$

驗證終端憑證

$ openssl x509 -noout -text -in endentity.pem
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 9266986341589110827 (0x809af2faa4d0d02b)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Blog Certificate Authority, CN=Davy.TW Blog Intermediate CA
        Validity
            Not Before: Nov 30 06:29:00 2019 GMT
            Not After : Nov 29 06:29:00 2020 GMT
        Subject: C=TW, ST=Taiwan, O=Davy.TW, OU=Davy.TW Blog, CN=blog.davy.tw
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         ...
$ openssl verify -CAfile ca.pem -untrusted intermediate.pem endentity.pem
endentity.pem: OK
$

OpenSSL 會在 verify 的時候先驗證 untrusted 憑證的正確性,然後如果會拿來驗證後面的憑證,也就是說如果結果是 OK 的話就表示這些憑證在一個有效的 chain 上了。

題外:CA bundle

大家記得在使用憑證的時候要建立 CA bundle,方法也很簡單,就是把這些 CA 憑證全部接在一起就可以了:

$ cat intermediate.pem ca.pem > ca-bundle.pem
$ openssl verify -CAfile ca-bundle.pem endentity.pem
endentity.pem: OK
$

至於為什麼不在驗證的時候就直接拿 CA bundle 來驗證,背後原因有點複雜,可以看這個 mailing list 的說明: https://mail.python.org/pipermail/cryptography-dev/2016-August/000676.html

後記

其實憑證真的有夠麻煩…… 裡面還有很多東西可以研究,光是 x509 extension 就可以啃很久……
希望大家都可以在需要簽中介憑證的時候都可以簡單做完XD


  1. Let's Encrypt 是由非營利性互聯網安全研究組(ISRG)提供給您的免費,自動化和開放的憑證頒發機構。 詳情請見: https://letsencrypt.org/zh-tw/ ↩︎