在安全實踐領域,TLS身份驗證作為一種技術手段,通常能夠保證任何使用者通過證書,瀏覽到真實、安全的Web應用。而在此基礎上發展而來的雙向TLS(Two-Way TLS),則可以僅允許部分使用
2021-06-08 14:25:33
在安全實踐領域,TLS身份驗證作為一種技術手段,通常能夠保證任何使用者通過證書,瀏覽到真實、安全的Web應用。而在此基礎上發展而來的雙向TLS(Two-Way TLS),則可以僅允許部分使用者訪問或呼叫目標應用。
下面,我將以示例的形式,並配合自動化指令碼,依次向您展示:搭建伺服器,向伺服器傳送未加密的hello訊息,以HTTPS的方式在伺服器上啟用單向TLS,要求客戶端通過雙向TLS來標識自己,基於可信的CA(證書頒發機構)實現雙向TLS,以及對HTTP客戶端進行相關測試。
身份標識(Identity):一對私鑰與公鑰,通常被存放在信任儲存庫中。
信任儲存庫(TrustStore):庫中包含了一個或多個可信證書(也稱為公鑰)的列表。
單向認證(也稱為單向tls、單向ssl):客戶端在驗證對方證書時用到的https連線。
雙向認證(也稱為雙向tls、雙向 ssl、雙向認證):客戶端與對方相互驗證證書時的https 連線。
通過如下的統一參考頁,我向社群里正在使用Apache http、Java、Kotlin、以及Scala等開發人員,提供了包含40多個http客戶端的配置示例。
Keytool參考頁
Openssl參考頁
http客戶端配置參考頁
Spring應用程式屬性概述
在處理http請求的過程中,它們可能會導致應用在初始構建時,需要花費一定的時間來下載大量依賴項。因此,我也通過GitHub - SSLContext Kickstart來簡化客戶端的配置。由於每一個http客戶端都可能需要不同的ssl物件來啟用ssl,因此程式碼庫需要提供基本的ssl配置。
首先,我們需要做好如下準備:
Java 11
Maven 3.5.0
Eclipse、Intellij IDEA(或任何其他文字編輯器,如 VIM)
一個終端
從https://github.com/Hakky54/mutual-tls處克隆項目
由於該項目已經包含了一個maven包裝器(wrapper),因此您可以在無需額外安裝的情況下,運行該項目。同時,下面將涉及到的各種包含了maven包裝器的命令,都已被預設包含在mvn命令中。
如果您想使用Java 8來運行該項目,則可以使用git命令:git checkout tags/java-8-compatible,來運行一個相容的版本。
為了啟動服務端,您可以在服務端的項目中運行App類的main方法,或者在終端的根目錄下運行命令:cd server/ && mvn spring-boot:run,以及使用maven包裝器:cd server-with-spring-boot/ && ./../mvnw spring-boot:run。
由於當前運行在預設埠8080上的伺服器端是未經加密的,因此您可以在終端中使用以下curl命令:curl -i -XGET http://localhost:8080/api/hello,來呼叫hello:
其響應內容如下(純文字):
HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 5 Date: Sun, 11 Nov 2018 14:21:50 GMT Hello
您還可以使用客戶端目錄中所提供的客戶端應用,去呼叫伺服器。由於客戶端依賴於本項目的其他元件,所以您需要先在根目錄下運行mvn install或./mvnw install。
此處的客戶端是基於Cucumber的整合測試。您可以通過從IDE處運行ClientRunnerIT類、或從根目錄中的終端運行:cd client/ && mvn exec:java、亦或使用maven的包裝器命令:cd client/ && ./../mvnw exec:java,來啟動之。您可以在客戶端項目的測試資源中,通過Hello.feature檔案,來獲悉整合測試的具體步驟。
為了同時運行伺服器和客戶端中的方法,您可以在根目錄中使用命令:mvn clean verify,或使用maven的包裝器:./mvnw clean verify。如果服務端與客戶端同處一臺伺服器,那麼客戶端會預設向localhost傳送請求;如果它們在不同的主機上運行,您需要為客戶端提供帶有VM參數:-Durl=http://[HOST]:[PORT]的定製化的url。
下面,我們來討論如何通過啟用TLS,來保護伺服器端。您可以通過將如下所需的屬性(YAML),新增到名為application.yml的應用屬性檔案中來實現:
server: port: 8443 ssl: enabled: true
在此,您可能會對為何將埠設定為 8443表示疑惑。其原因在於:帶有https的tomcat服務的約定埠為8443,而對於http則是8080。雖然我們可以使用埠8080進行https連線,但這並不是一種推薦的做法。
您可以通過重啟伺服器,來生效那些對於應用的更改。當然,您也可能會收到異常資訊:IllegalArgumentException: Resource location must not be null。該訊息的產生,是因為伺服器需要帶有伺服器證書的金鑰庫,以確保與外界的安全連線。
如果您提供的VM參數為:
Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake,那麼伺服器可以為您提供更多的資訊。
顯然,為了解決此問題,您需要創建一個帶有伺服器公鑰和私鑰的金鑰庫。其中的公鑰可與使用者共享,以便加密彼此之間的通訊;而伺服器的私鑰則可用來解密。值得注意的是,我們絕對不可以共享伺服器的私鑰,以避免被其他人用來破解截獲到的通訊,進而獲悉被加密的通訊內容。
因此,若要創建帶有公鑰和私鑰的金鑰庫,請在終端中執行以下命令(純文字):
keytool -v -genkeypair -dname "CN=Hakan,OU=Amsterdam,O=Thunderberry,C=NL" -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias server -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi.local,IP:12 0.1
為了告知伺服器金鑰庫的位置,以及具體的密碼,請將如下YAML內容貼上到您的application.yml檔案中:
server: port: 8443 ssl: enabled: true key-store: classpath:identity.jks key-password: secret key-store-password: secret
至此,您已成功啟用了伺服器和客戶端之間的TLS加密連線。您可以嘗試著使用curl命令:curl -i --insecure -v -XGET https://localhost:8443/api/hello,去呼叫伺服器。
當您在ClientRunnerIT類中運行客戶端時,您可能會看到一條錯誤訊息:java.net.ConnectException: Connection refused (Connection refused)。從字面上看,它是指客戶端試圖向伺服器建立連線,可以被拒絕了。其深層原因是:客戶端試圖使用的是埠8080,而伺服器只在埠8443上處於活躍狀態。因此,您需要進行如下修改,並將其應用到Constants類中,即從:
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
改為:
private static final String DEFAULT_SERVER_URL = "https://localhost:8443";
在完成修改之後,讓我們再次運行客戶端。您會看到另一條訊息:「javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target」。這意味著客戶端希望通過HTTPS進行通訊,但是在它握手的過程中,收到了無法識別的伺服器證書。可見,您還需要創建一個包含了各種受信任證書的信任庫,以方便客戶端在SSL握手過程中,將收到的證書與其信任庫裡的證書內容進行比較。如果相匹配的話,則可以繼續SSL的握手過程。當然,在創建信任庫之前,您需要事先獲得伺服器的證書。
您可以使用如下命令,匯出伺服器的證書:
keytool -v -exportcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -rfc
接著,您可以為客戶端創建一個信任庫,並使用如下命令匯入伺服器的證書:
keytool -v -importcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt
為了讓客戶端知曉信任庫的存在,您還需要告知其信任庫的正確位置、密碼、以及身份驗證已啟用。您可以在客戶端的application.yml檔案中,提供如下屬性:
client: ssl: one-way-authentication-enabled: true two-way-authentication-enabled: false trust-store: truststore.jks trust-store-password: secret
接下來,伺服器端需要驗證客戶端的身份,以判斷其是否可信。其實現方式為:通過client-auth屬性放入伺服器的application.yml中,以告知伺服器去驗證客戶端。
server: port: 8443 ssl: enabled: true key-store: classpath:identity.jks key-password: secret key-store-password: secret client-auth: need
當然,如果您直接運行它,則會因為客戶端根本沒有證書,而產生錯誤訊息:javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate(無效的證書資訊)。因此,我們需要通過如下命令,來創建證書:
keytool -v -genkeypair -dname "CN=Suleyman,OU=Altindag,O=Altindag,C=NL" -keystore client/src/test/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -別名客戶端 -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth
同時,您還需要為伺服器創建一個信任庫。不過,在創建信任庫之前,您需要通過如下命令獲取客戶端的證書:
keytool -v -exportcert -file client/src/test/resources/client.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret -rfc
下一步便是使用客戶端的證書,來創建伺服器的信任庫:
keytool -v -importcert -file client/src/test/resources/client.cer -alias client -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret -noprompt
同樣,為了讓客戶端獲悉該金鑰庫的存在,您還需要告知其信任庫的正確位置、密碼、以及身份驗證已啟用。您可以在客戶端的application.yml檔案中,提供如下屬性:
client: ssl: one-way-authentication-enabled: false two-way-authentication-enabled: true key-store: identity.jks key-password: secret key-store-password: secret trust-store: truststore.jks trust-store-password: secret
對應地,為了讓伺服器知曉新創建的信任庫,我們需要將當前屬性替換為以下屬性:
server: port: 8443 ssl: enabled: true key-store: classpath:identity.jks key-password: secret key-store-password: secret trust-store: classpath:truststore.jks trust-store-password: secret client-auth: need
至此,您已完成了雙向TLS的安裝。如果再次運行客戶端,您將會發現客戶端能夠以安全的方式,從伺服器端接收到hello訊息了。
有了前面的基礎,我們便可以採用基於可信CA的雙向(mutual)認證了。我們首先來看看它的優缺點:
客戶端不需要自行新增伺服器的證書。
伺服器不需要新增客戶端的所有證書。
由於是由CA管控著證書的有效期,因此本地運維工作會大幅減少。
您無法細粒度地控制哪些客戶端可以呼叫自己的應用,哪些不可以。任何客戶端,只要持有CA頒發的證書,即可訪問您的應用程式。
其具體實現步驟如下:
通常,您需要向某個已有的證書頒發機構,提供自己的證書以獲取其簽名。下面,我們將創建一個自己的CA,並用它去簽發客戶端和伺服器的證書。
keytool -v -genkeypair -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -keystore root-ca/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias root-ca -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3
當然,您也可以使用儲存庫預設提供的那個。
為了簽發證書,您需要通過如下命令,提供一個證書籤名請求 (.csr) 檔案。其中,伺服器的證書籤名請求為:
keytool -v -genkeypair -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -keystore root-ca/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias root-ca -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3
而客戶端的證書籤名請求為:
keytool -v -certreq -file client/src/test/resources/client.csr -keystore client/src/test/resources/identity.jks -alias client -keypass secret -storepass secret -keyalg rsa
簽發客戶證書:
keytool -v -gencert -infile client/src/test/resources/client.csr -outfile client/src/test/resources/client-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -rfc
簽發伺服器證書:
keytool -v -gencert -infile shared-server-resources/src/main/resources/server.csr -outfile shared-server-resources/src/main/resources/server-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi.local,IP:127.0.0.1 -rfc
由於我們無法直接用金鑰工具(keytool)去匯入已簽名的證書,因此我們需要將由CA簽發的證書儲存到identity.jks中。先匯出CA證書:
keytool -v -exportcert-檔案root-ca / root-ca.pem -alias root-ca -keystore root-ca / identity.jks -storepass secret -rfc
然後是客戶端:
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret -noprompt keytool -v -importcert -file client/src/test/resources/client-signed.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret keytool -v -delete -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret
最後是伺服器端:
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -noprompt keytool -v -importcert -file shared-server-resources/src/main/resources/server-signed.cer -alias server -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret keytool -v -delete -alias root-ca -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret
為了將客戶端和伺服器配置為僅信任某個CA,我們需要通過將CA證書匯入客戶端和伺服器的信任庫來實現。其中在客戶端,我們可以使用如下操作命令:
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt
在伺服器端則為:
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret -noprompt
同時,由於信任庫仍包含客戶端和伺服器的原有特定證書,因此我們需要將其刪除。其中在客戶端,我們可以使用如下操作命令:
keytool -v -delete -alias server -keystore client/src/test/resources/truststore.jks -storepass secret
在伺服器端則為:
keytool -v -delete -alias client -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret
至此,如果您再次運行客戶端,將能夠順利通過測試。客戶端將會接收到來自伺服器的hello訊息,而且其中的證書是由該CA所頒發的。
其實,您還可以使用該項目的指令碼目錄裡的各種指令碼,來自動化執行上述步驟。例如,對於單向認證而言,可以輸入:./configure-one-way-authentication;而對於雙向認證而言,則可以輸入:./configure-two-way-authentication-by-trusting-each-other my-company-name;對於通過可信CA進行的雙向身份驗證,可以輸入:./configure-two-way-authentication-by-trusting-root-ca my-company-name。
下面是已經通過測試的客戶端列表。您可以在ClientConfig類中找到基於純Java的http客戶端配置。該服務目錄包含了單個的http客戶端請求示例。其中,基於Kotlin和Scala的http客戶端配置是作為巢狀類被包含在內的。而且,所有客戶端示例都使用的是在SSLConfig類中創建的相同的ssl基本配置。
Apache HttpClient -> Client configuration | Example request
Apache HttpAsyncClient -> Client configuration | Example request
Apache 5 HttpClient -> Client configuration | Example request
Apache 5 HttpAsyncClient -> Client configuration | Example request
JDK HttpClient -> Client Configuration | Example request
Old JDK HttpClient -> Client Configuration & Example request
Netty Reactor -> Client Configuration | Example request
Jetty Reactive HttpClient -> Client Configuration | Example request
Spring RestTemplate -> Client Configuration | Example request
Spring WebFlux WebClient Netty -> Client Configuration | Example request
Spring WebFlux WebClient Jetty -> Client Configuration | Example request
OkHttp -> Client Configuration | Example request
Jersey Client -> Client Configuration | Example request
Old Jersey Client -> Client Configuration | Example request
Google HttpClient -> Client Configuration | Example request
Unirest -> Client Configuration | Example request
Retrofit -> Client Configuration | Example request
Async Http Client -> Client Configuration | Example request
Feign -> Client Configuration | Example request
Methanol -> Client Configuration | Example request
Vertx Webclient -> Client Configuration & Example request
RPC -> Client/Server Configuration & Example request
ElasticSearch -> RestHighLevelClient Configuration & example request
Fuel -> Client Configuration & Example request
Http4k with Apache 4 -> Client Configuration | Example request
Http4k with Async Apache 4 -> Client Configuration | Example request
Http4k with Apache 5 -> Client Configuration | Example request
Http4k with Async Apache 5 -> Client Configuration | Example request
Http4k with Java Net -> Client Configuration | Example request
Http4k with Jetty -> Client Configuration | Example request
Http4k with OkHttp -> Client Configuration | Example request
Kohttp -> Client Configuration & Example request
Ktor with Android engine -> Client Configuration | Example request
Ktor with Apache engine -> Client Configuration | Example request
Ktor with CIO (Coroutine-based I/O) engine -> Client Configuration | Example request
Ktor with Okhttp engine -> Client Configuration | Example request
Twitter Finagle -> Client Configuration | Example request
Twitter Finagle Featherbed -> Client Configuration & Example request
Akka Http Client -> Client Configuration | Example request
Dispatch Reboot -> Client Configuration & Example request
ScalaJ / Simplified Http Client -> Client Configuration & Example request
Sttp -> Client Configuration & Example request
Requests-Scala -> Client Configuration & Example request
Http4s Blaze Client -> Client Configuration | Example request
Http4s Java Net Client -> Client Configuration | Example request
相關文章
在安全實踐領域,TLS身份驗證作為一種技術手段,通常能夠保證任何使用者通過證書,瀏覽到真實、安全的Web應用。而在此基礎上發展而來的雙向TLS(Two-Way TLS),則可以僅允許部分使用
2021-06-08 14:25:33
#蘋果WWDC釋出會#向 FB、Google開嗆?蘋果全新隱私功能「超級狠」蘋果今(8日)凌晨舉辦其線上 WWDC開發者大會,除了公佈 iOS 15、iPadOS 15等自家作業系統的最新版功能,也延續一貫
2021-06-08 14:25:12
5月27日,OPPO釋出了全新Reno6系列產品,包括Reno6、Reno6 Pro以及Reno6 Pro+三個版本。其中Reno6 Pro和Reno6 Pro+已於6月5日開售,Reno6也將在6月11日正式首銷。已經開售的Reno6
2021-06-08 14:24:32
機器之心報道編輯:小舟、陳萍最近谷歌和 Facebook 兩大公司頻繁檢測到 CPU 在一些情況下會以無法預測的方式出現計算錯誤。CPU 一直都不是完全可靠的,自問世以來就一直存在出
2021-06-08 14:23:02
#谷歌地圖#抓穩少數對 Google Maps優勢!蘋果地圖大升級「導航」功能蘋果今(8日)凌晨舉辦其線上 WWDC開發者大會,並公佈 iOS 15在內的最新版自家作業系統。而在新版的 iOS 15、ma
2021-06-08 14:02:54
英特爾在2021年3月推出的11代酷睿可謂是重磅產品,無論是CPU產品或是與之搭配的主機板晶片組都帶來了很多充滿革新意味的元素。對於追求價效比的玩家而言,B560主機板解鎖XM
2021-06-08 14:00:24