﻿# 如何使用 OpenSSL 建立一張含有 Certificate Revocation List (CRL) 的憑證

最近因為公司在建立 ISO 27001 資訊安全管理系統，在文件管理部分就會想說要有一個高強度且具備不可否認性的[數位簽章](https://zh.wikipedia.org/zh-tw/%E6%95%B8%E4%BD%8D%E7%B0%BD%E7%AB%A0)(Digital Signature)。建立一組公司用的 CA 憑證並透過這張 CA 憑證簽發給員工使用的數位憑證其實很簡單，但之前一直沒機會研究如何手工打造 [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) 憑證撤銷清單，今天研究了一下，終於可以自行簽發 CRL 憑證了。

![CRL](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/4f63e48e3efba89c7ecf-187902031-8b65f54d-3021-4884-93d9-9fb716985883.png)

### Big Picture

由於手工打造 PKI 基礎建設牽涉到的知識相對複雜，這邊我試圖用比較簡單的方式來說明。

-   首先，你需要先建立一個最上層、最權威的「受信任的根憑證」(Trusted Root Certification Authorities)
    
    > 以下簡稱 **CA 憑證**
    
-   然後所有人都需要將 CA 憑證安裝到**每個人**的電腦中
    
    > 你可以透過許多方法將 CA 憑證派送到每台電腦中，也可以讓每個人自己下載安裝
    
    > 每個人的 **個人憑證** (Personal Certificate) 都需要透過 CA 憑證進行簽章處理，才能算是一個**受信任的**憑證
    
-   你需要準備一張 CRL 憑證，紀錄著所有「被撤銷」的憑證清單，這張憑證一樣要透過 CA 憑證簽署才是合法的、受信任的憑證
    
    > 你可以先準備好一張空白的 CRL 憑證，並且發佈到一個可公開存取的網站上，取得一個 CRL 網址
    
-   開始讓員工申請個人用的數位憑證
    
    > 基本步驟就是先建立 CSR (Certificate Signing Request) 憑證簽署要求檔，然後交由 CA 進行簽署並獲得正式憑證。本篇文章不會特別講解這個過程，我之前已經有許多 OpenSSL 文章分享過這個過程。
    

### 建立 CA 憑證

建立 CA 憑證相對簡單，一個命令就可以同時建立「私鑰」與「公鑰」，而「公鑰」其實就是 CA 憑證：

```bash
openssl req -new -nodes -sha256 -utf8 -newkey rsa:2048 -keyout ca.key -x509 -days 3650 -out ca.crt
```

過程中要設定一些憑證屬性：

```
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) []:Taipei
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Duotify Inc.
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:Duotify Global Root CA
Email Address []:
```

這個命令會產生兩個檔案：

1.  `ca.key` (簽署憑證時會需要用到的私密金鑰)
2.  `ca.crt` (即 CA 憑證，需派送到每台電腦中)

### 建立一張空的 CRL 憑證

我們要建立一張 CRL 憑證，主要有以下三個步驟：

1.  建立一個空的憑證資料庫檔 (`certindex`)
    
    這個檔案將會保存未來透過 CA 憑證**簽發**個人憑證時的完整記錄，未來要撤銷憑證時，也是去調整這個檔案的內容，你可以把這個檔案當成一個文字格式的資料庫。
    
    我們先建立空白的檔案即可：
    
    ```bash
    touch certindex
    ```
    
2.  建立 `crlnumber` 檔案
    
    每次核發一張新的 CRL 憑證，這個 `crlnumber` 檔案內容的編號就會遞增(`+1`)，你可以視為一個 CRL 憑證的序號。
    
    我們可以從 `01` 開始：
    
    ```bash
    echo 01 > crlnumber
    ```
    
    如果你可能會廢止很多憑證的話，這裡的 CRL 可能會很大，此時你很有可能會將 CRL 分段處理，因此 `crlnumber` 也有可能會從 `1000` 或 `2000` 開始計算，基本上這個 CRL Number 可以自由定義其編號起始點。
    
3.  建立 `certserial` 檔案
    
    其實每張憑證都有個 `Serial number` 憑證序號，如果你不指定的話，每次核發憑證都會給一段很長的亂碼序號。如果你在建立憑證時有設定 `certserial` 的話，透過 OpenSSL 建立新的憑證時，就會自動用你的數字來編號，每次簽署新的憑證都會自動以此序號為主，並且會在簽署後自動累加上去 (`+1`)。
    
    ```bash
    echo 01 > certserial
    ```
    
4.  建立一個 `ca.conf` 設定檔
    
    ```ini
    [ ca ]
    default_ca = myca
    
    [ crl_ext ]
    # issuerAltName=issuer:copy  #this would copy the issuer name to altname
    authorityKeyIdentifier=keyid:always
    
    [ myca ]
    dir = ./
    new_certs_dir = $dir
    unique_subject = no
    certificate = $dir/ca.crt
    database = $dir/certindex
    private_key = $dir/ca.key
    serial = $dir/certserial
    default_days = 730
    default_md = sha1
    policy = myca_policy
    x509_extensions = myca_extensions
    crlnumber = $dir/crlnumber
    default_crl_days = 730
    
    [ myca_policy ]
    commonName = supplied
    stateOrProvinceName = supplied
    countryName = optional
    emailAddress = optional
    organizationName = supplied
    organizationalUnitName = optional
    
    [ myca_extensions ]
    basicConstraints = CA:false
    subjectKeyIdentifier = hash
    authorityKeyIdentifier = keyid:always
    keyUsage = digitalSignature,keyEncipherment
    extendedKeyUsage = serverAuth
    crlDistributionPoints = URI:https://blog.miniasp.com/root.crl
    ```
    
5.  簽發 DER 格式的 CRL 憑證檔 (`root.crl`)
    
    ```bash
    openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem
    openssl crl -inform PEM -in root.crl.pem -outform DER -out root.crl
    rm root.crl.pem
    ```
    
6.  將檔案上傳到 `crlDistributionPoints` 指定的網址
    

### 建立個人憑證並附上 CRL 網址

1.  建立私密金鑰
    
    ```bash
    openssl genrsa -out will.key 4096
    ```
    
2.  建立 CSR 憑證簽署要求檔
    
    建立一個 `openssl.conf` 設定檔，設定憑證的基本資料：
    
    ```ini
    [req]
    prompt = no
    default_md = sha256
    default_bits = 2048
    distinguished_name = dn
    
    [dn]
    C = TW
    ST = Taiwan
    L = Taipei
    O = Duotify Inc.
    OU = IT Department
    emailAddress = will.huang@example.com
    CN = Will 保哥
    ```
    
    產生 CSR 憑證簽署要求檔
    
    ```bash
    openssl req -new -key will.key -out will.csr -config openssl.conf -utf8
    ```
    
3.  建立一個簽署憑證時需要的擴充屬性定義檔
    
    假設檔名為 `cert_template.conf`
    
    ```ini
    basicConstraints = CA:FALSE
    subjectKeyIdentifier = hash
    authorityKeyIdentifier = keyid:always
    keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
    extendedKeyUsage = clientAuth, serverAuth
    nsCertType = client, server, email
    nsComment = "Duotify Personal Certificate"
    crlDistributionPoints = URI:https://blog.miniasp.com/root.crl
    ```
    
4.  使用 CA 憑證核發伺服器憑證
    
    ```bash
    openssl ca -config ca.conf -notext -batch -in will.csr -out will.crt -extfile cert_template.conf
    ```
    
    核發成功後，會在 `certindex` 檔案看到以下內容：
    
    ```
    V       240831110206Z           01      unknown /CN=Will Huang/ST=Taiwan/C=TW/emailAddress=will.huang@example.com/O=Duotify Inc./OU=IT
    ```
    
5.  合併 `will.key` 與 `will.crt` 為 PFX (`PKCS#12`) 憑證檔 (`will.pfx`)
    
    ```bash
    openssl pkcs12 -export -in will.crt -inkey will.key -out will.pfx
    ```
    

### 撤銷現有憑證

1.  指定要撤銷的憑證
    
    ```bash
    openssl ca -config ca.conf -revoke will.crt
    ```
    
    撤銷成功後，會在 `certindex` 檔案看到以下內容：
    
    ```
    R       240831110206Z   220901110514Z   01      unknown /CN=Will Huang/ST=Taiwan/C=TW/emailAddress=will.huang@example.com/O=Duotify Inc./OU=IT
    ```
    
2.  簽發 DER 格式的 CRL 憑證檔 (`root.crl`)
    
    ```bash
    # 先建立 PEM 格式的 `root.crl.pem` 憑證檔
    openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem
    # 再將 PEM 格式轉為 DER 格式的憑證檔
    openssl crl -inform PEM -in root.crl.pem -outform DER -out root.crl
    # 用不到 PEM 格式的檔案就可以砍掉
    rm root.crl.pem
    ```
    
3.  將檔案上傳到 `crlDistributionPoints` 指定的網址
    

### 驗證憑證是否有效

1.  若要驗證憑證是否有效，在 Windows 可能要先設法清除 CRL 快取才行，否則你可能會測試不出效果
    
    ```batch
    certutil -urlcache crl delete
    ```
    
2.  如果你可以直接取得 CA 憑證與 CRL 憑證，就可以直接使用 `openssl` 驗證憑證是否已被撤銷，不需要事先清空快取
    
    ```bash
    # 將 DER 格式轉為 PEM 格式的憑證檔
    openssl crl -inform DER -in root.crl -outform PEM -out root.crl.pem
    # 合併 CA 憑證與 CRL 憑證
    cat ca.crt root.crl.pem > test.pem
    # 檢查 will.crt 憑證是否被撤銷
    openssl verify -extended_crl -verbose -CAfile test.pem -crl_check will.crt
    ```
    

### 相關連結

-   [Revoke certificate and generate CRL OpenSSL \[Step-by-Step\] | GoLinuxCloud](https://www.golinuxcloud.com/revoke-certificate-generate-crl-openssl/#Step-1_Revoke_certificate_using_OpenSSL)
-   [How to revoke missing/lost certificate OpenSSL \[Step-by-Step\] | GoLinuxCloud](https://www.golinuxcloud.com/revoke-missing-lost-certificate-openssl/)
-   [Chapter 8. Implementing a Certification Revocation List Red Hat Update Infrastructure 2.1 | Red Hat Customer Portal](https://access.redhat.com/documentation/en-us/red_hat_update_infrastructure/2.1/html/administration_guide/chap-red_hat_update_infrastructure-administration_guide-certification_revocation_list_crl)
-   [Howto: Make Your Own Cert And Revocation List With OpenSSL | Didier Stevens](https://blog.didierstevens.com/2013/05/08/howto-make-your-own-cert-and-revocation-list-with-openssl/)
-   [Generate certificate with cRLDistributionPoints extension using OpenSSL | Pixelstech.net](https://www.pixelstech.net/article/1445498968-Generate-certificate-with-cRLDistributionPoints-extension-using-OpenSSL)
-   [Certificate revocation lists — OpenSSL Certificate Authority — Jamie Nguyen](https://jamielinux.com/docs/openssl-certificate-authority/certificate-revocation-lists.html)
-   [Missing X509 extensions with an openssl-generated certificate - Information Security Stack Exchange](https://security.stackexchange.com/a/176084/282257)
-   [How To Clear Local CRL and OCSP caches - StackPath Help](https://web.archive.org/web/20240221023559/https://support.stackpath.com/hc/en-us/articles/360001076186-How-To-Clear-Local-CRL-and-OCSP-caches)
-   [Minimal openssl.cnf | Konstantin Shemyak - Tech Notes](http://technotes.shemyak.com/posts/min-openssl-cnf/)
