顯示具有 Nginx 標籤的文章。 顯示所有文章
顯示具有 Nginx 標籤的文章。 顯示所有文章

2023年6月5日 星期一

Client Certificate 的 OCSP (Online Certificate Status Protocol) 設定

這篇是的
使用 OpenSSL 製做 Client Certificate 做客戶端 TLS 認證 - 配合 Nginx 和 Java 做示範
的 OCSP 設定補充。

如果要看 CRL 的設定可以看我的另一篇文章:

Client Certificate 的 CRL (Certificate Revocation List) 設定

OCSP 的全名是 Online Certificate Status Protocol,
跟 CRL 相同的地方是,它一樣可以用一個 ./db/index 來紀錄憑證的撤銷狀態。
跟 CRL 不同的地方是,它不是提供一個憑證撤銷清單,而是自行啟動一個伺服器,我們把它叫做 OCSP Responder,當想要對憑證狀態進行確認時,可以對 OCSP Responder 發起詢問的請求 (OCSP Request),OCSP Responder 再把憑證的狀態回傳回去 (OCSP Response)。

跟 CRL 比較起來,OCSP 有幾個優點:
  1. 不像 CRL 一次回傳多個憑證狀態,一次只回傳被詢問的憑證狀態,對於網路成本及詢問者的解析成本都比較低。
  2. 因為是由 OCSP Responder Server 即時回應,相較 CRL 可能一段時間才發出更新或才會被人詢問來說,能獲取比較即時的憑證狀態。
缺點也是有:
  1. OCSP Responder 的流量負擔可能比較大,可能可以用多台 Server 做 Revers Proxy 分流解決。
OCSP Responder 可以用任何技術實作,只要它能處理 OCSP Request 並回傳正確格式的 OCSP Response 即可,如何儲存憑證的撤銷狀態也可自行用任何方法實現,其實 CRL 也是一樣,只是上次我們就直接使用 OpenSSL 的 index 檔功能來幫助我們實現。

在這篇文中,我一樣會只使用 OpenSSL 來實現 OCSP Responder,OpenSSL 本身就可以開啟一個 OCSP Responder,並配合 OpenSSL index 檔來處理 OCSP Request。

---------------------------------------------------------------------------------------------
現在要來示範要做 Client Certificate 時,建立 OCSP Responder 和 Nginx 設定的方式。

Nginx 的設定非常簡單,接續這一篇文章的 Nginx 設定 使用 OpenSSL 製做 Client Certificate 做客戶端 TLS 認證 - 配合 Nginx 和 Java 做示範,只要再加上 ssl_ocsp ssl_ocsp_responder 設定即可。
server {
   ......
   ssl_client_certificate "ca.crt";
   ssl_verify_client optional;
   ssl_ocsp leaf; #值可以是 on, leaf, off,leaf 是指只對 client certificate 做 ocsp
   ssl_ocsp_responder http://127.0.0.1:9000; # 自行指定 OCSP Responder 的 url
   ......
}

為了之後管理方便,在這裡我先建了幾個資料夾和檔案 (./crlnumber 和 ./crl 資料夾就不用了):
  1. ./certs:存放憑證的地方。
  2. ./csr:存放 CSR 的地方。
  3. ./db/index:index 為我手動放置的一個空白檔案,作為一個紀錄憑證狀態的純文字檔,其實 CRL 可以不需要,主要給 OCSP 使用,但 CRL 也可從其中產生出 CRL 檔,也方便之後管理憑證狀態。
  4. ./keys:存放 Private Key 的地方。
  5. ./serial/serial:serial 為我手動放置的一個純文字檔案,其內容為一個單純的 serial number 序號,供憑證產生時的累加 Serial Key 使用,serial number 可視為對每一個 client certificate 的辨識唯一序號。
  6. ca.conf:供 openssl ca 指令用的配置檔,用以簡化指令參數長度及方便管理。
檔案內容例如可用以下指定 (自己用編輯器輸入也可以):
touch ./db/index
openssl rand -hex 16 > serial/serial
因為會用到
openssl ca 指令,為了方便我們先來建立一個 config 配置檔供 openssl ca 指令參考,
這樣 openssl ca 指令就可以少打一些參數。

ca.conf:
[default]
name                    = root-ca
default_ca              = ca_config

[ca_config]
database                = db/index
serial                  = serial/serial
crlnumber               = crlnumber/crlnumber
default_crl_days        = 1
certificate             = certs/ca.crt
private_key             = keys/ca.key
new_certs_dir           = certs
default_md              = sha256
policy                  = ca_policy
unique_subject          = no

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

[ocsp_ext]
basicConstraints        = critical,CA:false
extendedKeyUsage        = OCSPSigning
noCheck                 = yes
keyUsage                = critical,digitalSignature
subjectKeyIdentifier    = hash
接著各項指令如下,
#產生 CA private key
openssl genrsa -out ./keys/ca.key 4096

#產生 CA CSR
openssl req -new -key ./keys/ca.key -out ./csr/ca.csr

#產生 CA 憑證
openssl x509 -req -days 365 -in ./csr/ca.csr -signkey ./keys/ca.key -CAcreateserial -out ./certs/ca.crt

# OCSP Responder 也需要自己的憑證
#產生 OCSP Responder 的 private key
openssl genrsa -out ./keys/ocsp.key 4096

#產生 OCSP Responder 的 CSR
openssl req -new -key ./keys/ocsp.key -out ./csr/ocsp.csr

#產生 OCSP Responder Server 的 Certificate,這裡使用了 ocsp_ext 區塊
#不使用 config 檔裡的 ocsp_ext 區塊設定的話,之後向 OCSP Responder Server 驗證 Certificate 時會有 missing ocspsigning usage 錯誤訊息)
#此時 ./db/index 會被更新,可以看到有一筆 OCSP Certificate 的憑證資料被寫入
openssl ca -config ca.conf -in ./csr/ocsp.csr -out ./certs/ocsp.crt -extensions ocsp_ext -days 365

#啟動 OCSP Responder Server,這邊使用了 locahost 的 9000 port
#啟動成功後應該可以看到 console 顯示
#ocsp: waiting for OCSP client connections...
#的訊息
openssl ocsp -port 9000 -index ./db/index -rsigner ./certs/ocsp.crt -rkey ./keys/ocsp.key -CA ./certs/ca.crt -text

#產生 Client private key:
openssl genrsa -out ./keys/client.key 4096

#產生 Client CSR:
openssl req -new -key ./keys/client.key -out ./csr/client.csr

#產生 Client Certificate,
#此時 ./db/index 會被更新,可以看到有一筆 Client Certificate 的憑證資料被寫入
openssl ca -config ca.conf -in ./csr/client.csr -out ./certs/client.crt -days 365

#用 OpenSSL 對 OCSP Responder Server 發起 OCSP Request 進行憑證的驗證
#對 OCSP Certificate 做驗證
openssl ocsp -issuer ./certs/ca.crt -CAfile ./certs/ca.crt -cert ./certs/ocsp.crt -url http://127.0.0.1:9000
#對 Client Certificate 做驗證
openssl ocsp -issuer ./certs/ca.crt -CAfile ./certs/ca.crt -cert ./certs/client.crt -url http://127.0.0.1:9000
#如果驗證結果是合法 (Valid) 的話,應該會看到如下訊息:
Response verify OK
./certs/client.crt: good
        This Update: Jun
#被 Revoke 的憑證查詢結果會像這樣:
#(Reason 只會在 Revoke 時有加入 crl_reason 時才會出現,手動修改 ./db/index 內容也可以,就是在 Revoke date 欄位加上 Reason 並用逗號分隔)
Response verify OK
./certs/client.crt: revoked
        This Update: Jun  6 04:55:19 2023 GMT
		Reason: unspecified
        Revocation Time: Jun  6 04:53:37 2023 GMT

#撤銷 (Revoke) 一個 Certificate (可以看到跟 CRL 的 Revoke 指令相同,-crl_reason 參數可自選要不要加上)
openssl ca -config ca.conf -revoke ./certs/client.crt

#加進一個未撤銷 (Valid) 狀態的 Certificate 到 OCSP DB 中 (自己要先手動把 OCSP DB 中相同序號的 Certificate 紀錄刪掉),
#或是自己打開 OCSP DB 檔把 Certificate 的狀態從 R (Revoke 的意思) 改成 V (Valid 的意思) 也可以
openssl ca -valid ./certs/client.crt -config ca.conf

參考資料:

Client Certificate 的 CRL (Certificate Revocation List) 設定

 這篇是
使用 OpenSSL 製做 Client Certificate 做客戶端 TLS 認證 - 配合 Nginx 和 Java 做示範
這篇文章的 CRL 設定補充。

如果要看 OCSP 的設定可以看我的另一篇文章:

Client Certificate 的 OCSP (Online Certificate Status Protocol) 設定

CRL 的全名是 Certificate Revocation List,
顧名思意是一個憑證撤銷狀態的列表,
有時雖然憑證的日期沒有過期、簽名驗證也都正確,
但是因為一些其他因素,例如 Private Key 被駭客偷走的憑證安全性問題等,
憑證可能會被 CA 認為需要強制撤銷 (Revoke),
這時我們除了認證憑證合法以外,還需要有方法來確認憑證的 Revoke 狀態,
CRL 就是其中一種方法,其他還有像是 OCSP 的方法,會在我的另一篇文XXXXX 中做介紹。

在 CRL 方法中,CA 會定期更新憑證的撤銷列表到一個 CRL 檔案中,通常會以 pem 格式儲存,並且以 CA 來進行簽章 (Sign) 來證明其是由 CA 頒發的。

當我們要確認憑證的撤銷狀態時,通常會在憑證中找到 CRL 檔案的位置 (通常是一個網路 url 的位置),憑證中也有可能沒有寫上 CRL 資訊,這時有可能是 client, server 端已經彼些講好的位置,
拿到 CRL 檔案後,就可以在其中查找要確認的憑證撤銷狀態。

CRL 檔案或是憑證中可能還會有其他資訊,例如有效期限、下一次 CRL 更新日期等,我們可以在期限到後主動查找新發佈版本的 CRL。

CRL 有一些優點:
  1. 其中一個優點就是架構簡單、實現方便,CA 只要定時的更新 CRL 即可。
  2.  CRL 檔案容易取得,可能只是一個網路上的檔案,使用 HTTP Request 即可取得,取得後甚至可以把它在本地端,不用每次都去網路上抓取,等下次更新日或 CA 發表重大更新要求時再抓新版的 CRL。
但相對地其反面就是它的缺點:
  1. CA 如果不及時更新 CRL 檔 或是 詢問者不即時取得最新的 CRL 的話,即意味著 CRL 可能不是最新的,如果有憑證有安全問題即時的被 CA 公告需要 Revoke,沒更新 CRL 可能會無法即时知道。
  2. 另一個缺點是,因為 CRL 可能包含許多其他可能用不到的憑證狀態資訊,如果 CRL 檔很大,網路的傳送成本就會提高,也會讓我們在其中查找要確認的憑證時花較多的時間。

---------------------------------------------------------------------------------------------
現在要來示範要做 Client Certificate 時,產生 CRL 檔案和 Nginx 設定的方式。

Nginx 的設定非常簡單,接續這一篇文章的 Nginx 設定 使用 OpenSSL 製做 Client Certificate 做客戶端 TLS 認證 - 配合 Nginx 和 Java 做示範,只要再加上 ssl_crl 設定即可。
server {
   ......
   ssl_client_certificate "ca.crt";
   ssl_verify_client optional;
   ssl_crl "D:\xxx\xxx\ca.crl"; # CRL 檔案的位置,也可以是網路上的 url
   ......
}

接下來要來產生 CRL 檔案,

為了之後管理方便,在這裡我先建了幾個資料夾和檔案:
  1. ./certs:存放憑證的地方。
  2. ./csr:存放 CSR 的地方。
  3. ./crlnumber:存放 CRL 檔版本序號的地方。
  4. ./crl:存放 CRL 檔的地方。
  5. ./db/index:index 為我手動放置的一個空白檔案,作為一個紀錄憑證狀態的純文字檔,其實 CRL 可以不需要,主要給 OCSP 使用,但 CRL 也可從其中產生出 CRL 檔,也方便之後管理憑證狀態。
  6. ./keys:存放 Private Key 的地方。
  7. ./serial/serial:serial 為我手動放置的一個純文字檔案,其內容為一個單純的 serial number 序號,供憑證產生時的累加 Serial Key 使用,serial number 可視為對每一個 client certificate 的辨識唯一序號。
  8. ca.conf:供 openssl ca 指令用的配置檔,用以簡化指令參數長度及方便管理。
檔案內容例如可用以下指定 (自己用編輯器輸入也可以):
touch ./db/index
openssl rand -hex 16 > serial/serial
echo 1001 > crlnumber/crlnumber
因為會用到
openssl ca 指令,為了方便我們先來建立一個 config 配置檔供 openssl ca 指令參考,
這樣 openssl ca 指令就可以少打一些參數。

ca.conf:
[default]
name                    = root-ca
default_ca              = ca_config

[ca_config]
database                = db/index
serial                  = serial/serial
crlnumber               = crlnumber/crlnumber
default_crl_days        = 1
certificate             = certs/ca.crt
private_key             = keys/ca.key
new_certs_dir           = certs
default_md              = sha256
policy                  = ca_policy
unique_subject          = no

[ca_policy]
countryName             = match
stateOrProvinceName     = optional
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
接著各項指令如下,
#產生 CA private key
openssl genrsa -out ./keys/ca.key 4096

#產生 CA CSR
openssl req -new -key ./keys/ca.key -out ./csr/ca.csr

#產生 CA 憑證
openssl x509 -req -days 365 -in ./csr/ca.csr -signkey ./keys/ca.key -CAcreateserial -out ./certs/ca.crt

#產生 CRL
openssl ca -gencrl -config ./ca.conf -out ./crl/ca.crl

#觀看 CRL 檔中的資訊
openssl crl -in ./crl/ca.crl -text -noout

#產生 Client private key
openssl genrsa -out ./keys/client.key 4096

#產生 Client CSR
openssl req -new -key ./keys/client.key -out ./csr/client.csr

#使用 ca.conf 產生 CA 憑證,這時 client 憑證可得到一個可識別的唯一序號, serial 裡的 serial number 也會改變,
#此時也會發現 db/index 被加進了一條此 client 憑證的資料,db/index 有變動時可以手動下指令再製作一次新版本的 CRL
openssl ca -config ./ca.conf -in ./csr/client.csr -out ./certs/client.crt -days 365

#向 CRL 檢查憑證是否 Valid or Revoke
openssl verify -crl_check -CRLfile ./crl/ca.crl -CAfile ./certs/ca.crt ./certs/client.crt

#撤銷 (Revoke) Client 憑證 -crl_reason 參數是 Revoke reason
#撤銷後 db/index 會更新,憑證資訊欄位的 V (Valid 的意思) 會變成 R (Revoke) 的意思,並且會被標上 Revoke Date 訊息
#記得再下指令產生一下新版本的 CRL
openssl ca -config ca.conf -revoke ./certs/client.crt -crl_reason unspecified
# Revoke reason 可選列表如下:
unspecified
keyCompromise
CACompromise
affiliationChanged
superseded
cessationOfOperation
certificateHold
removeFromCRL

參考資料:

2023年5月18日 星期四

使用 OpenSSL 製做 Client Certificate 做客戶端 TLS 認證 - 配合 Nginx 和 Java 做示範

在這裡我要來記錄一下使用 OpenSSL 製做 Client Certificate 做 客戶端 TLS 認證
(可參考 Mutual TLS (mTLS 或雙向認證) ) 認證的一些步驟事項。

首先第一件事是要安裝 OpenSSL 這個 tool,
使用以下指令安裝:
安裝 OpenSSL:

apt install openssl

在這裡我們為了方便演示及測試,
我們把自己當成憑證頒發機構 (Certificate authority,或稱 CA),
首先先產生 CA 的 private key,
取名為 my-ca-key.pem (或 my-ca.key,檔名可自取,副檔名一般會用 .pem 或 .key 來代表是一個 private key):

openssl genrsa -out my-ca-key.pem 4096

接下來產生自用的 Certificate Signing Request (CSR),在這邊命名為 my-ca.csr,
執行指令後被要求填入一些資訊,
要注意的是 Common Name 這個欄位不可以和等下 CA Certificate 要簽名的 Client CSR 一樣,
就是在 Certificate 的簽署鍵中各 Certificate 的 Common Name 不能一樣:

openssl req -new -key my-ca-key.pem -out my-ca.csr

再來我們執行以下指令用 CA 的 private key 對 CA CSR 來進行簽名,產生 CA 用的憑證 CA Certificate,檔名為 my-ca.crt,期限設為 365 天:

openssl x509 -req -days 365 -in my-ca.csr -signkey my-ca-key.pem -CAcreateserial -out my-ca.crt

這樣 CA 的東西都準備好了,我們現在應該會有三個檔案:

  1. my-ca-key.pem:CA 的 private key
  2. my-ca.csr:CA 的 CSR
  3. my-ca.crt:CA 的 certificate 憑證

CA 都準備好了以後,
接下來就是要來產生 Client 端的 Client private key、Client CSR、Client certificate,
基本上做法跟 CA 差不多,
差在是在要做 Client Certificate 時,Client CSR 要用 CA private key 和 CA Certificate 來簽名產生,不用給 Client private key。

首先是產生 Client private key,命名為 client-key.pem:

openssl genrsa -out client-key.pem 4096

再來是產生 Client CSR,命名為 client.csr,記得要填資訊欄位時,Common Name 不可以和 CA CSR 的一樣:

openssl req -new -key client-key.pem -out client.csr

最後我們使用 CA private key 和 CA Certificate 對  Client CSR 進行簽名產生 Client Certificate,
命名為 client.crt,期限設定 365 天:

openssl x509 -req -days 365 -in client.csr -CA my-ca.crt -CAkey my-ca-key.pem -CAcreateserial -out client.crt

在這邊我們可以用以下指令來檢查 Client Certificate 是不是由 CA Certificate 簽名的:

openssl verify -CAfile my-ca.crt client.crt

如果顯示 OK 的話,就代表正確。

基本上這樣 CA 和 Client 端的東西都準備好了,
不過有一些環境可能還會需要特別的 KeyStore 檔,KeyStore 裡會存放 private key 和 certificate 等資訊,例如 Java 就可能會需要 PKCS12 格式的 .p12 KeyStore 檔,
因為等下會用 Java 做 Client Certificate TLS 示範,
所以我們在這用以下指令將 Client private key 和 Client Certificate 打包成一個 .p12 的 KeyStore 檔案,這裡命名為 client-keystore.p12,
注意這裡會要求為 KeyStore 設定一個密碼,
等下 Java 程式會用到:

openssl pkcs12 -export -inkey client-key.pem -in client.crt -out client-keystore.p12

到這裡 Client 端的部份我們應該會有以下四個檔案:

  1. client-key.pem:Client private key
  2. client.csr:Client CSR
  3. client.crt:由 CA Certificate 和 CA private 簽名的 Client Certificate
  4. client-keystore.p12:裝了 Client private key 和 Client Certificate 的 KeyStore

先來示範在 Server 的 Nginx 設定要求 Client Server 要提供 Client Certifcate,
在 Nginx 的設定檔中,server 區塊的設定可以加上
ssl_client_certificate 和
ssl_verify_client
的 Client Certificate 設定:

server {
   ......
   ssl_client_certificate "CA 憑證路徑,例如上述產生的 my-ca.crt";
   ssl_verify_client on; #強制開啟 Client Certificate 要求
   ......
}

或是 ssl_verify_client 也可以不要設定 on,改設定
optional,
也就是不強制要求 Client Certificate,
但還是會檢查 Client 端有無帶上 Client Certificate,
 ,然後用 $ssl_client_verify 的值是不是 SUCCESS 來判斷有沒有認證成功,
範例如下:

server {
   ......
   ssl_client_certificate "CA 憑證路徑,例如上述產生的 my-ca.crt";
   ssl_verify_client optional; #不強制要求 Client Certificate
   ......
   #只有 /xx/xx/xx1 的 url 請求才檢查 Client Certificate
   location /xx/xx/xx1 { 
      ......
      if ($ssl_client_verify != SUCCESS) { #用 $ssl_client_verify 判斷 Client 端有無帶上 Client Certificate
         return 401;
      }
      ......
   }

   # /xx/xx/xx2 的 url 請求不檢查 Client Certificate
   location /xx/xx/xx2 {
      ......
   }
}

在 Client 端的部份,我們使用 Java 帶上 Client Certificate 來對 Server 端發出 Https 請求,
如果請求成功,應該會收到 Server 端的 200 HTTP response status:

Maven Dependency 配置:

 <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
 <dependency>
     <groupId>org.apache.httpcomponents</groupId>
     <artifactId>httpclient</artifactId>
     <version>4.5.13</version>
 </dependency>

Java 程式碼:

package test;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

public class ClientCertificateSSLTest {

	public static void main(String[] args) throws ClientProtocolException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException, KeyStoreException {
		String keyStorePassphrase = "12345"; // Client KeyStore 的密碼
		String keyStorePath = "D:\\xxx\\client-keystore.p12"; // Client KeyStore 的路徑

		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		keyStore.load(new FileInputStream(keyStorePath), keyStorePassphrase.toCharArray());
		
		SSLContext sslContext = SSLContexts.custom()
		        .loadKeyMaterial(keyStore, keyStorePassphrase.toCharArray())
                //如果 keystore 中存有多個憑證,可自行用 Alias 選擇特定憑證
//		        .loadKeyMaterial(keyStore, keyPassphrase.toCharArray(), (aliases, socket) -> {return "chosenAlias";})
		        .build();

//		CloseableHttpClient httpClient = HttpClients.createDefault(); // 如果不帶上 Client Certificate,Server 端就會拒絕連線
		CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
		
//		HttpPost httpPost = new HttpPost("https://xxx.xxx.xxx/xx/xx/xx2"); // 這條 url 可以不用帶上 Client Certificate
		HttpPost httpPost = new HttpPost("https://xxx.xxx.xxx/xx/xx/xx1"); // 這條 url 需要帶上 Client Certificate
		HttpResponse response = httpClient.execute(httpPost);
		System.out.println(response.getStatusLine()); // 成功連線應該要回 200 HTTP response status
	}

}


參考資料:

  1. OpenSSL create client certificate & server certificate with example