自RabbitMQ 3.4.0起, 為防止 POODLE attack 攻擊,已經(jīng)自動(dòng)禁用了SSLv3.
使用TLS時(shí),推薦安裝的Erlang/OTP版本為17.5或以上版本. R16版本在某些證書中可以工作,但存在major limitations.
必須安裝Erlang加密程序,并且保證它能工作.對(duì)于那些從源碼進(jìn)行Erlang編譯的Windows用戶來說,可能會(huì)出現(xiàn)一些問題.
對(duì)于將RabbitMQ作為服務(wù)來運(yùn)行的Windows XP用戶而言: 要在Windows XP上結(jié)合OpenSSL 0.9.8及其之后版本,并使用SSL來將RabbitMQ作為服務(wù)運(yùn)行是不可行的.此bug已在Windows XP SP3和OpenSSL v0.9.8r以及v1.0.0d版本上經(jīng)過了確認(rèn). 如果你想以服務(wù)來運(yùn)行RabbitMQ,建議將其升級(jí)為Windows 7或者將OpenSSL降級(jí)為更早的版本 (v0.9.7e可以正常工作).
對(duì)于那些從源碼編譯Erlang而言:必須確保配置能找到OpenSSL,并且能構(gòu)建加密程序.
密鑰,證書和CA證書
OpenSSL 是一個(gè)龐大而復(fù)雜的主題. 如何更全面地了解OpenSSL,以及如何好好使用,我們建議參考其它資源,如Network Security with OpenSSL.
OpenSSL可用來簡(jiǎn)單地建立一個(gè)加密通道, 還可以用來在通道的各個(gè)端點(diǎn)(end points)之間交換簽名證書,并可驗(yàn)證這些證書. 證書驗(yàn)證需要從知名,可信的root證書處建立一條信任鏈. root certificate 是自簽名證書,可提供一個(gè)證書頒發(fā)機(jī)構(gòu).也存在一些收費(fèi)的商業(yè)公司,它們可以簽名你已經(jīng)生成的SSL證書.
對(duì)于本指南,我們會(huì)創(chuàng)建我們自己的證書頒發(fā)機(jī)構(gòu).一旦完成這個(gè)步驟,我們就可以為server和clients生成多種格式的簽名證書,它們將被用于Java,.Net以及Erlang AMQP 客戶端.
注意,Mono對(duì)于OpenSSL證書(存在一些bug)有更嚴(yán)格的要求,所以我們將使用更嚴(yán)格的關(guān)鍵約束是必要的.
# mkdir testca # cd testca # mkdir certs private # chmod 700 private # echo 01 > serial # touch index.txt
現(xiàn)在將下面的內(nèi)容放置到openssl.cnf(其位置在我們剛創(chuàng)建的testca目錄之下)中:
[ ca ] default_ca = testca [ testca ] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial default_crl_days = 7 default_days = 365 default_md = sha256 policy = testca_policy x509_extensions = certificate_extensions [ testca_policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha256 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions [ root_ca_distinguished_name ] commonName = hostname [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign [ client_ca_extensions ] basicConstraints = CA:false keyUsage = digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2 [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment extendedKeyUsage = 1.3.6.1.5.5.7.3.1
現(xiàn)在我們將使用我們自己的test證書頒發(fā)機(jī)構(gòu)來生成密鑰和證書,仍然在testca目錄中執(zhí)行:
# openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365
\ -out cacert.pem -outform PEM -subj /CN=MyTestCA/ -nodes # openssl x509 -in cacert.pem -out cacert.cer -outform DER
這就是生成我們自己test證書頒發(fā)機(jī)構(gòu)所需的操作.root證書位于testca/cacert.pem,也存在于testca/cacert.cer. 這兩個(gè)文件包含相同的信息,但格式是不同的. 雖然大多數(shù)都使用PEM 格式, Microsoft和Mono則喜歡使用不同的格式DER格式.
在設(shè)置了證書頒發(fā)機(jī)構(gòu)后,現(xiàn)在我們需要為clients和server生成密鑰和證書.Erlang client和RabbitMQ broker都可以直接使用PEM 文件. 它們可通過三個(gè)文件來通知: 隱式可信任的root證書, 用于證明公共證書的所有權(quán)的私有密鑰,以及標(biāo)識(shí)對(duì)等的公共密鑰自身.
為了方便起見,我們?yōu)镴ava和.Net clients提供了PKCS #12 存儲(chǔ)(包含client證書和密鑰). PKCS存儲(chǔ)通常用密碼進(jìn)行自我保護(hù),因此必須提供密碼.
創(chuàng)建server和client證書的過程是相似的.唯一的區(qū)別是簽名證書時(shí)添加的keyUsage字段.首先從server開始:
# cd .. # ls testca # mkdir server # cd server # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \ -subj /CN=$(hostname)/O=server/ -nodes # cd ../testca # openssl ca -config openssl.cnf -in ../server/req.pem -out \ ../server/cert.pem -notext -batch -extensions server_ca_extensions # cd ../server # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
現(xiàn)在是client:
# cd .. # ls server testca # mkdir client # cd client # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \ -subj /CN=$(hostname)/O=client/ -nodes # cd ../testca # openssl ca -config openssl.cnf -in ../client/req.pem -out \ ../client/cert.pem -notext -batch -extensions client_ca_extensions # cd ../client # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
在RabbitMQ中啟用SSL支持
要在RabbitMQ中啟用SSL/TLS支持,我們必須為RabbitMQ提供root證書, server證書文件,以及server密鑰的位置. 我們還需要告訴它監(jiān)聽SSL連接的套接字,我們需要告訴它是否應(yīng)該要求客戶提供證書,如果客戶端沒有提供一個(gè)證書,如果我們不能建立信任鏈,我們是否應(yīng)該接受證書. RabbitMQ通過兩個(gè)參數(shù)來配置:
-rabbit ssl_listeners
用于監(jiān)聽SSL連接的端口列表.要在單個(gè)網(wǎng)絡(luò)接口上監(jiān)聽,需要在列表中添加{"127.0.0.1", 5671}這樣的配置.
-rabbit ssl_options
這是new_ssl 選項(xiàng)的元組列表. 完整可用的ssl_options信息可通過erl -man new_ssl手冊(cè)頁(yè)面查看,但最重要的東西是cacertfile, certfile and keyfile.
設(shè)置這些選項(xiàng)最簡(jiǎn)單的方式是編輯configuration file.配置文件的示例如下,它會(huì)在此主機(jī)機(jī)的所有網(wǎng)絡(luò)接口上的5671端口上監(jiān)聽SSL連接:
[ {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"}, {certfile,"/path/to/server/cert.pem"}, {keyfile,"/path/to/server/key.pem"}, {verify,verify_peer}, {fail_if_no_peer_cert,false}]} ]} ].
Windows用戶注意: 配置文件中的反斜杠("\")會(huì)被解釋為轉(zhuǎn)義序列- 因此如果將CA證書的路徑指定為c:\cacert.pem,那么你需要輸入{cacertfile, "c:\\cacert.pem"}或{cacertfile, "c:/cacert.pem"}.
當(dāng)web瀏覽器連接到一個(gè)HTTPSweb服務(wù)器時(shí), 服務(wù)器會(huì)提供它的公共證書,web瀏覽器會(huì)試圖在根證書和服務(wù)器證書之間建立一個(gè)信任鏈,如果一切進(jìn)行得很好的話,加密通信通道就建立了.盡管在web瀏覽器和web服務(wù)器不常用, SSL 允許服務(wù)器要求客戶端提供證書. 通過這種方式,服務(wù)器可以驗(yàn)證客戶端的身份.
服務(wù)器是否要求客戶端提供證書以及它們是否相信證書的策略,是由verify和fail_if_no_peer_certarguments 控制的. 通過{fail_if_no_peer_cert,false}選項(xiàng),我們聲明了我們準(zhǔn)備接受客戶端,它們可以不向我們發(fā)送證書,但通過{verify,verify_peer}選項(xiàng),我們聲明了如果客戶端沒有向我們發(fā)送證書, 我們必須能建立信任鏈. 注意,這些值會(huì)隨著Erlang/OTP中的ssl版本變化, 因此檢查erl -man new_ssl 來確保你有正確值.
注意,如果使用了 {verify, verify_none}, 在客戶端和服務(wù)器之間將不會(huì)發(fā)生證書交換,rabbitmqctl list_connections 將輸出對(duì)等證書信息項(xiàng)的空字符串.
在啟動(dòng)broker后,在rabbit.log中,你會(huì)看到下面的輸出:
=INFO REPORT==== 9-Aug-2010::15:10:55 === started TCP Listener on 0.0.0.0:5672 =INFO REPORT==== 9-Aug-2010::15:10:55 === started SSL Listener on 0.0.0.0:5671
同時(shí),注意最后一行,它將展示RabbitMQ 服務(wù)器正在運(yùn)行,且監(jiān)聽了ssl連接.
信任Client的Root CA
目前,我們告知RabbitMQ查看testca/cacert.pem 文件. 這包含了我們test認(rèn)證中心的公共證書. 我們可能存在由多個(gè)不同認(rèn)證中心簽發(fā)的證書,我們希望RabbitMQ全部信任它們.因此,我們可以簡(jiǎn)單地將這些證書追加到另一個(gè)新文件中,以作為RabbitMQ的cacerts參數(shù):
# cat testca/cacert.pem >> all_cacerts.pem # cat otherca/cacert.pem >> all_cacerts.pem
提供證書密碼
可使用password選項(xiàng)來為私有密鑰提供密碼:
[ {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {password, "t0p$3kRe7"} ]} ]} ].
了解 TLS 漏洞: POODLE, BEAST, etc
POODLE
POODLE 是一個(gè)已知的能破壞SSLv3的SSL/TLS攻擊.從3.4.0版本開始, RabbitMQ服務(wù)器拒絕接受SSLv3連接. 在2014年12月,一個(gè)經(jīng)過改良的能影響TLSv1.0的POODLE攻擊被 宣布. 因此,建議使用Erlang 18.0+的版本( 消除了TLS 1.0 POODLE攻擊漏洞)或者禁用TLSv1.0支持 (參考下面的章節(jié)).
BEAST
BEAST attack 是一個(gè)能攻擊TLSv1.0的漏洞. 為了減輕它的攻擊,禁用TLSv1.0支持(參考下面的章節(jié)).
通過配置來禁用 SSL/TLS 版本
為了限制SSL/TLS協(xié)議版本,使用版本選項(xiàng):
%% Disable SSLv3.0 support, leaves TLSv1.0 enabled. [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}]}, {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]} ]} ]} ].
%% Disable SSLv3.0 and TLSv1.0 support. [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}, {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {versions, ['tlsv1.2', 'tlsv1.1']} ]} ]} ].
要驗(yàn)證,可使用
openssl s_client:
# connect using SSLv3 openssl s_client -connect 127.0.0.1:5671 -ssl3
# connect using TLSv1.0 through v1.2 openssl s_client -connect 127.0.0.1:5671 -tls1
可以看到下面的輸出:
SSL-Session: Protocol : TLSv1
JDK 和 .NET支持的TLS版本
禁用tlsv1.0限制客戶端平臺(tái)的支持。下面是一個(gè)表,說明JDK和.NET支持的TLS版本 。
配置密碼套件
可配置RabbitMQ使用的密碼套件.注意現(xiàn)在所有的套件都可以在所有系統(tǒng)上使用. 例如,使用橢圓曲線密碼,請(qǐng)運(yùn)行最新的Erlang發(fā)布。下面的例子演示了如何使用TLS的密碼選項(xiàng)。
%% List allowed ciphers [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}, {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {versions, ['tlsv1.2', 'tlsv1.1']}, {ciphers, [{ecdhe_ecdsa,aes_128_cbc,sha256}, {ecdhe_ecdsa,aes_256_cbc,sha}]} ]} ]} ].
要列出Erlang 運(yùn)行時(shí)安裝的所有密碼套件,可使用:
rabbitmqctl eval 'ssl:cipher_suites(openssl).'
信任級(jí)別
設(shè)置SSL連接時(shí),協(xié)議中有兩個(gè)重要的階段.第一個(gè)階段發(fā)生對(duì)等端點(diǎn)交換證書(可選)的時(shí)候.當(dāng)證書交換后,對(duì)等端點(diǎn)可選擇在根證書和其它存在的證書之間建立信任鏈. 這用來驗(yàn)證對(duì)等端點(diǎn)的身份(提供的私有密鑰不會(huì)被偷!).
第二階段是協(xié)商對(duì)等節(jié)點(diǎn)的加密密鑰,該密鑰將用于通信的其余部分。如果交換證書,公鑰/私鑰將在密鑰協(xié)商中使用。
因此,您可以創(chuàng)建一個(gè)加密的SSL連接,而無需驗(yàn)證證書。java客戶端支持兩種操作模式。
密鑰管理器,信任管理器,以及密鑰庫(kù)
在Java安全框架中,有三個(gè)需要注意的組件: 密鑰管理, 信任管理以及密鑰庫(kù).
密鑰管理器用于對(duì)待節(jié)點(diǎn)管理其證書.也就是說,在會(huì)話設(shè)置時(shí),密鑰管理會(huì)控制發(fā)送哪些證書給遠(yuǎn)端對(duì)待節(jié)點(diǎn).
信任管理器用于對(duì)等節(jié)點(diǎn)管理遠(yuǎn)程證書.也就是說,在會(huì)話設(shè)置時(shí),信任管理遠(yuǎn)端節(jié)點(diǎn)的那些證書是可信任的.
密鑰庫(kù)是證書的Java封裝. Java會(huì)將證書轉(zhuǎn)換為Java特定的二進(jìn)制格式或PKCS#12格式. 這些格式是使用Key Store類來管理的.對(duì)于服務(wù)端證書,我們會(huì)使用Java二進(jìn)制格式,但對(duì)于客戶端 密鑰/證書對(duì),我們將使用 PKCS#12格式.
沒有驗(yàn)證證書的連接
我們的第一個(gè)示例將展示一個(gè)簡(jiǎn)單的client,通過不驗(yàn)證證書以及不存在任何客戶端證書的SSL來連接RabbitMQ服務(wù)器.
import java.io.*; import java.security.*; import com.rabbitmq.client.*; publicclass Example1 { public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setPort(5671); factory.useSslProtocol(); // Tells the library to setup the default Key and Trust managers for you
// which do not do any form of remote server trust verification Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); //non-durable, exclusive, auto-delete queue channel.queueDeclare("rabbitmq-java-test", false, true, true, null); channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes()); GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false); if(chResponse == null) { System.out.println("No message retrieved"); } else { byte[] body = chResponse.getBody(); System.out.println("Recieved: " + new String(body)); } channel.close(); conn.close(); } }
這個(gè)簡(jiǎn)單的示例只是一個(gè)echo test.它會(huì)創(chuàng)建一個(gè)rabbitmq-java-test隊(duì)列,并向默認(rèn)direct交換器中發(fā)送消息,然后讀回發(fā)布的消息,并進(jìn)行回顯. 注意,我們使用的是專用的,非持久化,自動(dòng)刪除的隊(duì)列,因此我們不需要擔(dān)心后期的手動(dòng)清除.
存在和驗(yàn)證證書
首先,我們需要設(shè)置密鑰庫(kù).我們假設(shè)有要連接的服務(wù)器的證書,所以我們現(xiàn)在需要將它添加到我們的密鑰庫(kù)中(信任管理器會(huì)使用).
# keytool -import -alias server1 -file /path/to/server/cert.pem -keystore /path/to/rabbitstore
上面的命令會(huì)將cert.pem導(dǎo)入到rabbitstore中,并在內(nèi)部稱其為server1. alias參數(shù)用于有多個(gè)證書或密鑰的時(shí)候用于指定別名,因?yàn)樵趦?nèi)部必須有不同的名稱.
在關(guān)于是否相信證書的問題上,必須回答yes, 并要選擇一個(gè)密碼. 在這個(gè)例子中,我的密碼為rabbitstore。
然后我們要用PKCS#12文件中的客戶端證書和密鑰,已經(jīng)在上面展示過了.
下面的例子會(huì)修改上面的代碼, 以使用我們的密鑰庫(kù),密鑰管理器,以及信任管理器:
import java.io.*; import java.security.*; import javax.net.ssl.*; import com.rabbitmq.client.*; publicclass Example2 { publicstaticvoid main(String[] args) throws Exception { char[] keyPassphrase = "MySecretPassword".toCharArray(); KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(new FileInputStream("/path/to/client/keycert.p12"), keyPassphrase); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, passphrase); char[] trustPassphrase = "rabbitstore".toCharArray(); KeyStore tks = KeyStore.getInstance("JKS"); tks.load(new FileInputStream("/path/to/trustStore"), trustPassphrase); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(tks); SSLContext c = SSLContext.getInstance("TLSv1.1"); c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); factory.setPort(5671); factory.useSslProtocol(c); Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); channel.queueDeclare("rabbitmq-java-test", false, true, true, null); channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes()); GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false); if(chResponse == null) { System.out.println("No message retrieved"); } else { byte[] body = chResponse.getBody(); System.out.println("Recieved: " + new String(body)); } channel.close(); conn.close(); } }
為了確保上述代碼能在其它情況下工作,可使用未導(dǎo)入到密鑰庫(kù)中的安全證書來嘗試,這時(shí)你可能會(huì)看到屏幕上的難異常信息.
配置 .Net 客戶端
為了能讓服務(wù)器安全證書能在.Net平臺(tái)上使用, 它們可以是多種格式,包括DER 和PKCS #12, 但不能是PEM. 對(duì)于DER格式, .Net希望它們能存儲(chǔ)在.cer擴(kuò)展名的文件中.在上面的步驟中,當(dāng)創(chuàng)建test認(rèn)證機(jī)構(gòu)時(shí),我們會(huì)把PEM 轉(zhuǎn)換成DER格式,命令如下:
# openssl x509 -in /path/to/testca/cacert.pem -out /path/to/testca/cacert.cer -outform DER
PEM是base64編碼的DER格式, 使用分隔符進(jìn)行封閉。此編碼通常是使它更容易在7位有限協(xié)議傳輸數(shù)據(jù),如電子郵件(SMTP)。
RFC 5280, Certificate Key Usage and Mono
正如上面所提到的,Mono是相當(dāng)嚴(yán)格的,強(qiáng)制證書只能應(yīng)用于它聲明的特定目的.
SSL 證書和密鑰可應(yīng)用于各種各樣的用途, 例如, 電子郵件簽名,代碼簽名, 通信加密等等. (我們這里的目的是TCP 通信加密). RFC 5280 指定了許多不同的目的, 并允許一個(gè)證書為特定的一組目的進(jìn)行簽名.
SSL v3 證書可包含許多不同的擴(kuò)展.處理證書如何使用的擴(kuò)展被稱為Key Usage Extension. 不同使用一般來說,都支持得很好,即使是定義良好,它們的使用也是廣泛地解釋. 一些關(guān)鍵的用法已經(jīng)廢棄,大部分已經(jīng)完全忽視。
這里有一個(gè)更進(jìn)一步的擴(kuò)展, 它也指定了使用用途, 但它選擇O.I.Ns來進(jìn)行, 如"1.3.6.1.5.5.7.3.1".顯然,英語缺乏一些明顯的用于添加的隨機(jī)數(shù)字。這是一個(gè)擴(kuò)展密鑰使用擴(kuò)展,序列的對(duì)象標(biāo)識(shí)符,進(jìn)一步定義了那些證書的使用是允許的。
而Mono,看上去認(rèn)為這些擴(kuò)展都是重要的, 需要有待進(jìn)一步的觀察. 如果證書遺漏了Key Usage Extension,Mono會(huì)讓證書無效.默認(rèn)情況下, OpenSSL 對(duì)于自簽名證書省略了Key Usage Extension ,因?yàn)樗M绻麤]有找到Key Usage Extension, 該證書是有效的,可用于任何目的。
這就是為什么在樣例openssl.cnf文件的列舉中,root_ca_extensions, client_ca_extensions 和 server_ca_extensions 都指定了keyUsage,并且最后兩個(gè)還有extendedKeyUsage定義.因此上面生成的證書對(duì)于Mono的使用是有效的; keyEncipherment 指定了證書可通過SSL服務(wù)器來使用, digitalSignature指定了證書可由SSL客戶端使用. extendedKeyUsage 字段中的值都說了同樣的事情.
你可以使用this small tool 來檢查Mono是否接受RabbitMQ提供的證書. 注意,你需要使用合適的OpenSSL命令來轉(zhuǎn)換server/cert.pem 到tserver/cert.cer:
# openssl x509 -in /path/to/server/cert.pem -out /path/to/server/cert.cer -outform DER # mono certcheck.exe /path/to/server/cert. Checking if certificate is SSLv3... Ok Checking for KeyUsageExtension (2.5.29.15)... Ok Checking if KeyEncipherment flag is set... Ok This certificate CAN be used by Mono for Server validation
信任 .Net
在 .NET 平臺(tái)上, 遠(yuǎn)程證書是通過任意數(shù)量的存儲(chǔ)庫(kù)來管理的.這些存儲(chǔ)庫(kù)的管理是通過 'certmgr'(Microsoft .Net 實(shí)現(xiàn)和Mono都可用)工具來完成的.
NB: 在某些Windows平臺(tái)上,有兩種版本的命令-一個(gè)是隨著操作系統(tǒng)自帶的,只提供了圖形界面,另一個(gè)Windows SDK自帶的,提供了圖形界面和命令行界面. 兩者都可以很好的完成工作,但示例將以后者為基礎(chǔ).
對(duì)于我們的情況,因?yàn)槲覀兲峁┑目蛻舳薱ertificate/key對(duì)都在單獨(dú)的PKCS #12 文件中,我們要做的只是導(dǎo)入根證書機(jī)構(gòu)的證書到Root (Windows) / Trust(Mono) 存儲(chǔ)庫(kù)中.在存儲(chǔ)庫(kù)中所有簽名的證書都是自動(dòng)可信的.
對(duì)比java客戶端,會(huì)樂于使用SSL連接而不驗(yàn)證服務(wù)器的證書,該.Net客戶端需要驗(yàn)證成功。為阻止驗(yàn)證,應(yīng)用程序可在SslOptions.AcceptablePolicyErrors中設(shè)置System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable 以及System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors 標(biāo)志.
Certmgr證書管理
certmgr 允許Add, Delete, List 以及在特定的Store上執(zhí)行其它操作.這些stores 可以是用戶stores, 或機(jī)器范圍的.只有管理員用戶有機(jī)器范圍stores的寫訪問權(quán)限.
為了將證書加入users Root (Windows) / Trust (Mono) store,我們可以運(yùn)行:
(Windows) > certmgr -add -all \path\to\cacert.cer -s Root (Mono) $ certmgr -add -c Trust /path/to/cacert.cer
將證書加入機(jī)器證書存儲(chǔ)庫(kù),我們要運(yùn)行
(Windows) > certmgr -add -all \path\to\cacert.cer -s -r localMachine Root (Mono) $ certmgr -add -c -m Trust /path/to/cacert.cer
在將證書添加到store后,我們可以用-list命令來展示證書內(nèi)容:
(Windows) > certmgr -all -s Root (Mono) $ certmgr -list -c Trust Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed. Self-signed X.509 v3 Certificate Serial Number: AC3F2B74ECDD9EEA00 Issuer Name: CN=MyTestCA Subject Name: CN=MyTestCA Valid From: 25/08/2009 14:03:01 Valid Until: 24/09/2009 14:03:01 Unique Hash: 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E
正如我們看到的,在信任庫(kù)里有一個(gè)自簽名的X.509 v3 證書. Unique Hash在store里是唯一的標(biāo)識(shí)符.
要?jiǎng)h除這個(gè)證書,可使用unique hash:
(Windows) > certmgr -del -c -sha1 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E -s Root (Mono) $ certmgr -del -c Trust 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed. Certificate removed from store.
使用相同的步驟,我們可以在客戶端上也可以執(zhí)行add/delete/list 我們根證書的操作.
創(chuàng)建連接
要?jiǎng)?chuàng)建RabbitMQ的SSL連接,我們需要在ConnectionFactory參數(shù)字段中設(shè)置一些新字段.為了讓事情更簡(jiǎn)單,這里有一新字段Parameters.Ssl,它可作為需要設(shè)置所有其它字段的命名空間 . 這些字段是:
- Ssl.CertPath: 如果你的服務(wù)器希望驗(yàn)證客戶端的話,這是PKCS#12格式的客戶端證書的路徑.這是可選的.
- Ssl.CertPassphrase:如果你正在使用PKCS#12 格式的客戶端證書,那么可能會(huì)需要一個(gè)密碼,你可以在這個(gè)字段中進(jìn)行指定.
- Ssl.Enabled: 這是一個(gè)boolean字段,用以打開或關(guān)閉SSL支持.默認(rèn)是off.
- Ssl.ServerName: 記住 .Net希望這匹配發(fā)送證書的CN.
例子
這與Java部分的例子是一樣的.它創(chuàng)建了一個(gè)channel, rabbitmq-dotnet-test隊(duì)列,并使用默認(rèn)direct交換器來發(fā)布,然后再讀回發(fā)送的消息并進(jìn)行回顯.注意,我們使用的是exclusive, non-durable, auto-delete隊(duì)列,因此我們不必?fù)?dān)憂事后的手動(dòng)清除.
using System; using System.IO; using System.Text; using RabbitMQ.Client; using RabbitMQ.Util; namespace RabbitMQ.Client.Examples { public class TestSSL { public static int Main(string[] args) { ConnectionFactory cf = new ConnectionFactory(); cf.Ssl.ServerName = System.Net.Dns.GetHostName(); cf.Ssl.CertPath = "/path/to/client/keycert.p12"; cf.Ssl.CertPassphrase = "MySecretPassword"; cf.Ssl.Enabled = true; using (IConnection conn = cf.CreateConnection()) { using (IModel ch = conn.CreateModel()) { ch.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null); ch.BasicPublish("", "rabbitmq-dotnet-test", null, Encoding.UTF8.GetBytes("Hello, World")); BasicGetResult result = ch.BasicGet("rabbitmq-dotnet-test", true); if (result == null) { Console.WriteLine("No message received."); } else { Console.WriteLine("Received:"); DebugUtil.DumpProperties(result, Console.Out, 0); } ch.QueueDelete("rabbitmq-dotnet-test"); } } return 0; } } }
注意,在Windows XP上,運(yùn)行此例,可能會(huì)出現(xiàn)失敗
CryptographicException: Key not valid for use in specified state.
在這種情況下,你需要從證書存儲(chǔ)庫(kù)加載證書并直接設(shè)置
ConnectionFactory的Ssl.Certs參數(shù)才能成功運(yùn)行.
R16B01之前的Erlang版本
有可能在老版本的Erlang中使用SSL.這些老版本可以與某些證書工作,其它則很困難.同時(shí),它們也包含OTP-10905 bug,這使得它不能禁用掉任何協(xié)議版本.實(shí)際上,由于它不能禁用SSLv3, 使用SSL的老版本Erlang 對(duì)于POODLE attack (PDF link)是不安全的.
如果探測(cè)到老版本的Erlang,RabbitMQ 3.4.0會(huì)自動(dòng)禁用SSL監(jiān)聽器.如果這不是你所希望的,你可以設(shè)置ssl_allow_poodle_attack rabbit 配項(xiàng)為true.
ssl_allow_poodle_attack 是一個(gè)全局設(shè)置;在rabbit程序中的設(shè)置會(huì)控制所有SSL監(jiān)聽器的行為(AMQP, management, STOMP, etc).
下面的例子進(jìn)行了說明:
[ {rabbit, [ {ssl_listeners, [5671]}, {ssl_allow_poodle_attack, true}, {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"}, {certfile,"/path/to/server/cert.pem"}, {keyfile,"/path/to/server/key.pem"}, {verify,verify_peer}, {fail_if_no_peer_cert,false}]} ]} ].
證書鏈和驗(yàn)證深度
使用由中間人CA簽名的客戶端證書時(shí),可能需要配置RabbitMQ服務(wù)器使用更高的驗(yàn)證深度。該深度是非自頒發(fā)的中間證書的最大數(shù)量,可以按照有效的證書路徑中的對(duì)等證書。因,如果depth為0,對(duì)等節(jié)點(diǎn)(如.client)證書 必須由CA直接簽發(fā),如果為1,路徑可以是"對(duì)等節(jié)點(diǎn), CA, 信任的CA",如果設(shè)置為2,則路徑可以是 "peer, CA, CA, trusted CA"等等.下面的例子演示了如何來配置RabbitMQ server的證書驗(yàn)證深度:
[ {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"}, {certfile,"/path/to/server/cert.pem"}, {keyfile,"/path/to/server/key.pem"}, {depth, 2}, {verify,verify_peer}, {fail_if_no_peer_cert,false}]} ]} ].
當(dāng)在RabbitMQ 插件中使用SSL時(shí),如federation或 shovel,有可能需要為Erlang客戶端配置證書的驗(yàn)證深度,正如下面所描述的.
配置Erlang client
在RabbitMQ Erlang client 中啟用SSL是相當(dāng)直接的. 在#amqp_params_network記錄,我們只需要在ssl_options字段中提供值. 你會(huì)認(rèn)識(shí)到我們指定RabbitMQ的選項(xiàng)。
Erlang SSL 選項(xiàng)
必須提供三個(gè)重要選項(xiàng):
- cacertfile 選項(xiàng)用于指定明確信任的根證書機(jī)構(gòu)的證書.
- certfile是client擁有的PEM格式的證書
- keyfile是客戶端PEM格式的私有密鑰文件
作為RabbitMQ自身, verify 和fail_if_no_peer_cert 選項(xiàng)可用來指定當(dāng)服務(wù)器未提供證書或我們不能根據(jù)服務(wù)器證書建立信任鏈時(shí)下的動(dòng)作. depth配置了證書驗(yàn)證的深度(參考上面部分).
代碼
Params = #amqp_params_network { port = 5671, ssl_options = [{cacertfile, "/path/to/testca/cacert.pem"}, {certfile, "/path/to/client/cert.pem"}, {keyfile, "/path/to/client/key.pem"}, %% only necessary with intermediate CAs %% {depth, 2}, {verify, verify_peer}, {fail_if_no_peer_cert, true}] }, {ok, Conn} = amqp_connection:start(Params),
現(xiàn)在你就可以像普通連接一樣來使用Conn了.
TLS/SSL故障排除
介紹
本頁(yè)收集一些技巧來幫助診斷SSL錯(cuò)誤。策略是使用其它的SSL實(shí)現(xiàn)來測(cè)試必要的組件,在故障排除的過程中識(shí)別故障.
請(qǐng)記住,如果兩個(gè)特定的組件之間有相互作用的話,這個(gè)過程不保證識(shí)別問題.
我們也解釋了在日志中可能出現(xiàn)的一些常見錯(cuò)誤消息.
Erlang中檢測(cè)SSL支持
建立與broker的SSL連接的第一個(gè)要求是在broker中要有SSL支持. 可通過運(yùn)行erl(或Windows上的werl.exe)來確認(rèn)Erlang VM是否支持SSL,命令為:
ssl:versions().
其輸出看起來像這樣(版本號(hào)可能有所不同):
[{ssl_app,"5.3.6"}, {supported,['tlsv1.2','tlsv1.1',tlsv1]}, {available,['tlsv1.2','tlsv1.1',tlsv1]}]
相反,如果你收到了錯(cuò)誤信息,那么就要確認(rèn)Erlang是否使用了OpenSSL來構(gòu)建.在基于Debian的系統(tǒng)上,你需要安裝erlang-ssl包.
使用OpenSSL來檢查密鑰和證書
我們現(xiàn)在用其它的SSL實(shí)現(xiàn)來驗(yàn)證配置文件中指定的證書和密鑰.這個(gè)例子使用 OpenSSL s_client 和 s_server. 我們將確認(rèn)用于連接兩端的證書和密鑰.
針對(duì)下面的例子,我們假設(shè)你有下面的信息:
Item | Location |
CA certificate | testca/cacert.pem |
Server certificate | server/cert.pem |
Server key | server/key.pem |
Client certificate | client/cert.pem |
Client key | client/key.pem |
在一端執(zhí)行下面的命令:
openssl s_server -accept 8443 -cert server/cert.pem -key server/key.pem \ -CAfile testca/cacert.pem
在另一端執(zhí)行
openssl s_client -connect localhost:8443 -cert client/cert.pem -key client/key.pem \ -CAfile testca/cacert.pem
如果證書和密鑰創(chuàng)建正確,SSL連接建立序列將出現(xiàn),并且終端會(huì)連接.一端的輸入會(huì)出現(xiàn)在另一端.如果建立了信任鏈,第二個(gè)終端將顯示下面的信息:
Verify return code: 0 (ok)
如果收到錯(cuò)誤,需要確認(rèn)證書和密鑰是否正確創(chuàng)建.
檢查broker正在監(jiān)聽
此步驟可檢查broker是否正在期望的AMQPS端口上進(jìn)行監(jiān)聽.當(dāng)你使用有效的SSL配置文件進(jìn)行啟動(dòng)時(shí), broker會(huì)在日志文件中報(bào)告SSL監(jiān)聽的地址.你可以類似于下面的東西:
=INFO REPORT==== 8-Aug-2011::11:51:47 === started SSL Listener on 0.0.0.0:5671
如果你設(shè)置了"ssl_listeners"配置指令但沒有看到這樣的信息,那么可能是你的配置文件沒有被broker讀取. 確認(rèn)配置文件引用的broker日志中包含SSL配置選項(xiàng).
嘗試與broker的SSL連接
一旦RabbitMQ broker監(jiān)聽于SSL端口,你可使用OpenSSL s_client來驗(yàn)證SSL 連接, 這次針對(duì)的是broker. 這可以檢測(cè)broker是否配置正確,不必配置AMQPS client.此例假設(shè)broker使用"ssl_listeners"指令配置監(jiān)聽SSL連接的端口5671:
openssl s_client -connect localhost:5671 -cert client/cert.pem -key client/key.pem \ -CAfile testca/cacert.pem
輸出類似于端口8443的情況. 當(dāng)連接建立的時(shí)候,broker日志文件應(yīng)該包含一個(gè)新的條目:
=INFO REPORT==== 8-Aug-2011::11:55:13 === accepting AMQP connection <0.223.0> (127.0.0.1:58954 -> 127.0.0.1:5671)
如果你現(xiàn)在為broker提供8?jìng)€(gè)隨機(jī)字節(jié),broker將會(huì)使用字符串"AMQP"緊跟編碼的版本號(hào)數(shù)字進(jìn)行回復(fù). 如果你認(rèn)出了"AMQP"字符串,那么你能確定你已經(jīng)連接上了AMQP broker.
使用stunnel來驗(yàn)證客戶端連接
最后的檢查是驗(yàn)證AMQPS clients. 我們將使用stunnel 來提供SSL能力.要這個(gè)配置中,AMQPS clients會(huì)使用stunnel來作安全連接,它將傳遞解密后的數(shù)據(jù)給broker的AMQP端口. 這提供了一些信心,客戶端的SSL配置是獨(dú)立于broker的SSL配置的正確配置.
stunnel會(huì)以守護(hù)進(jìn)程的方式運(yùn)行在與broker的同一臺(tái)機(jī)器上.在下面的討論中,假定只會(huì)暫時(shí)使用stunnel.
在下面的討論中,假定只會(huì)暫時(shí)使用stunnel。當(dāng)然可能使用Stunnel提供SSL能力更永久,但是隨著broker的集成缺乏,意味著管理報(bào)告功能和認(rèn)證的插件(使用SSL信息)將無法這樣做。
在這個(gè)例子中,stunne會(huì)連接到未加密的AMQP端口(5672) ,并接受具有SSL能力的客戶端5679端口上的連接:
cat client/key.pem client/cert.pem > client/key-cert.pem stunnel -r localhost:5672 -d 5679 -f -p client/key-cert.pem -D 7
stunnel 需要證書和相應(yīng)的密鑰.
生成的客戶端證書和相應(yīng)的密鑰應(yīng)該使用和如上面所示的cat命令連接。Stunnel要求的密鑰不需要密碼保護(hù)。SSL能力客戶端現(xiàn)在可以連接端口5679,任何SSL錯(cuò)誤會(huì)在stunnel啟動(dòng)時(shí)出現(xiàn)在控制臺(tái).
連接client和broker
如果上面的步驟沒有出錯(cuò),那么你可以測(cè)試AMQPS客戶端已經(jīng)連接到broker的 AMQPS端口, 首先須確保停止任何運(yùn)行的OpenSSL或stunnel會(huì)話.
證書鏈和驗(yàn)證深度
當(dāng)使用第三方CA簽發(fā)的客戶端證書時(shí),有必要配置RabbitMQ server使用更高的驗(yàn)證深度. 該depth是非自頒發(fā)的中間證書的最大數(shù)量,可以按照有效的證書路徑中的對(duì)等證書。
參考TLS/SSL guide來了解如何配置驗(yàn)證深度.
理解 SSL logs
上述步驟會(huì)產(chǎn)生新broker日志文件條目.這些條目與診斷輸出一起可以幫助確認(rèn)SSL錯(cuò)誤的原因.
下面的是一些常見的錯(cuò)誤條目:
- Entries containing {ssl_upgrade_error, ekeyfile} or {ssl_upgrade_error, ecertfile}
這意味著broker的 keyfile 或certificate文件是無效的.須確認(rèn)keyfile匹配證書,且兩者都是PEM 格式的. PEM格式是一種可打印的編碼與識(shí)別的分隔符. 證書以 -----BEGIN CERTIFICATE----- 并以-----END CERTIFICATE----- 結(jié)束. keyfile 會(huì)以-----BEGIN RSA PRIVATE KEY----- 開始,并以-----END RSA PRIVATE KEY----- 結(jié)束.
- Entries containing {ssl_upgrade_failure, ... certify ...}
- 此錯(cuò)誤與客戶端驗(yàn)證有關(guān).客戶端提供了一個(gè)無效的證書或無證書. 如果ssl_options設(shè)置了verify 選項(xiàng)為verify_peer,那么將使用臨時(shí)使用verify_none.必須確保客戶端證書已經(jīng)正確生成了,并且客戶端提交正確的證書。
- Entries containing {ssl_upgrade_error, ...}
這是一個(gè)通用的錯(cuò)誤,可能有許多原因。確保你使用的是Erlang的推薦版本。
- Entries containing {tls_alert,"bad record mac"}
服務(wù)器已嘗試驗(yàn)證它所接收的數(shù)據(jù)的完整性和檢查失敗。這可能是由于有問題的網(wǎng)絡(luò)設(shè)備, 無意的客戶端socket共享(如.因?yàn)槭褂昧?/span>fork(2))或者客戶端實(shí)現(xiàn)的TLS的bug.
posted on 2016-08-02 22:25
胡小軍 閱讀(11947)
評(píng)論(0) 編輯 收藏 所屬分類:
RabbitMQ