在這裡我要來記錄一下使用 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 的東西都準備好了,我們現在應該會有三個檔案:
- my-ca-key.pem:CA 的 private key
- my-ca.csr:CA 的 CSR
- 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 端的部份我們應該會有以下四個檔案:
- client-key.pem:Client private key
- client.csr:Client CSR
- client.crt:由 CA Certificate 和 CA private 簽名的 Client Certificate
- 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
}
}
參考資料:
沒有留言 :
張貼留言