本文從TLS安全傳輸層協議的簡單流程、如何生成自簽名CA證書、自頒發服務器&客戶端證書、openfire服務器安全配置等方面去描述如何建立一個使用TLS加密的XMPP聊天通道。
這里的smack版本是V4.2.3,openfire服務器版本也是V4.2.3
TLS
關于TLS協議,我們可以在網上找到很多相關的文章,這里不對基礎概念作過多介紹。這邊會結合HTTPS對TLS的應用和XMPP中的sasl標簽和starttls去了解TLS。但是我們需要知道TLS1.0是基于SSL3.0的,現在的SSL協議已經不建議使用了。最新的TLS協議是1.3版本,還在完善中,應用最廣的是TLS1.2協議。
TLS并不是一個傳輸層協議,而是一個傳輸層安全協議。而HTTP是一個應用層協議,TLS的作用是對HTTP傳輸的數據進行加密解密處理,以此來保證HTTP傳輸數據的安全,因為HTTP對于數據的傳輸是明文的。
這里對HTTPS連接做簡單描述,其具體作用在TCP三次連接建立以后,由客戶端(這里是C&S模型)發出Client Hello的TLS連接建立請求,然后服務器會告訴客戶端:服務器支持什么加密算法,發給客戶端自己掏大價錢買來的簽名證書的內容。
然后客戶端使用本地預裝或者人為加裝的CA根證書去驗證服務器發來的簽名證書是否有效,如果安全有效那就bingo,建立TLS連接,否則斷開連接。
而XMPP協議,在不使用TLS的時候,是一個明文的傳輸的協議。其中的SASL協議只是起到了對鑒權數據(賬號密碼,或者其他用來確定登錄資源的數據)的保密作用,starttls標簽才是建立TLS連接的關鍵。
客戶端根據openfire服務器hostName,domainName和端口建立的連接也是一個TCP連接。在完成3次握手以后,客戶端使用XMPP協議和openfire服務器進行通訊,服務器會返回一個服務器支持的sasl機制列表和是否強制使用TLS連接的報文數據,如下
<stream:features>
<starttls
xmlns="urn:ietf:params:xml:ns:xmpp-tls">
<required/>
</starttls>
<mechanisms
xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>
</stream:features>
下面的這個標簽內容,表明服務器強制要求使用TLS連接,客戶端跟服務器建立的TLS連接可以是雙向或者單向的,具體配置在文章下面會講到。
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
<required/>
</starttls>
下面這段報文,注明服務器支持的sasl鑒權機制
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>
如果使用TLS連接,那SASL機制可以使用PLAIN,否則的話推薦使用SCRAM-SHA-1,這是關于SASL相關機制的文章。
TLS加密建立
因為上述報文中的值是required,所以客戶端會繼續發送要求進行TLS連接驗證的報文
//客戶端請求建立TLS連接
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>
//建立成功
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>
至此之后,我們的XMPP連接內容使用上了TLS傳輸層安全協議來保駕護航了(新版本的XMPP傳輸報文不是明文的,也有一定的監聽難度)。
關于CA自簽名
了解了TLS之后,因為這篇文章是出于學習目的,所以我們再學一下證書簽名等相關知識吧(貧窮)。
同樣,在網上有很多關于數字簽名的文章,這里同樣不多做闡述,直接講述如何做一個CA機構大佬。操作系統Centos7,需要用到的軟件有openssl和jdk。
生成自簽名證書
首先,了解一下openssl的目錄結構。
openssl安裝成功以后,其目錄在/etc/pki
下,里面有個配置文章特別重要,/etc/pki/tls/openssl.cnf
這個配置文件約定了很多屬性,下文提到的cakey.pem,cacert.pem
等文件名都是/etc/pki/tls/openssl.cnf
規定的,所以下列操作直接復制粘貼就好了。
在此之前,把[ policy_match ]
下面的幾個屬性改成這樣,否則簽名會出錯
# For the CA policy
[ policy_match ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
然后依次進行如下操作,請注意使用sudo權限操作。
# cd /etc/pki/CA
//生成一個長度是2048的私鑰文件
# (umask 077; openssl genrsa -out private/cakey.pem 2048)
//生成一個自簽名證書,有效期是3650天,時長可以自由更改,里面需要填寫很多信息
# opensslreq -new -x509 -key private/cakey.pem -out cacert.pem -days 3650
//生成存儲證書序列號的文件
# touch index.txt serial
//先自增1
# echo 01 > serial
自此,CA自簽名證書搞定了,接下來就要給自己頒發一個openfire服務器證書了
生成openfire服務器證書、自簽名、配置
在此之前,簡單描述一下TLS中,非對稱算法、CA結構和簽名證書的作用。
- 非對稱算法:因為對稱算法傳遞密鑰的不安全性,有了非對稱算法。其特點就是有兩個密鑰,一個公鑰一個私鑰,私鑰加密的內容只有公鑰能解密,公鑰加密的內容也只有私鑰才能解密。
假如B和A通訊,B需要把數據傳輸給A。A有一對密鑰,A保管私鑰,把公鑰告訴B。那么B用A的公鑰加密數據,然后把數據發給A,A用自己的私鑰解密數據就知道B傳輸的數據是什么了。在A的公鑰安全可信的前提下,就保證了B傳輸數據的保密性了,不會被攔截的hack所獲取。但是假如hack把A的公鑰替換成自己的公鑰,B加密的數據就會被hack攔截了,那么如何保證A的私鑰的真實性呢?
- CA:我們的操作系統里面都會預裝一些權威CA機構的根證書,被這些CA機構開光(花錢為自己機構注買一個數字證書)過的通訊對方就是可信機構,會有一個數字證書。只要被系統中預裝的某個CA結構根證書驗證通過了,就證明了通訊對方提供的公鑰是安全的(所以客戶端被做過手腳就不安全了)。
當A和B使用TLS通訊的時候,A花錢去名叫C的CA機構獲得了數字證書,在通訊的時候傳輸給B。B通過內置的CA機構根證書驗證,鑒定這是真的A。之后使用A的公鑰,或者使用A的公鑰加密過的對稱密鑰(節省資源消耗)去加密數據,再發送給A。這樣就保證了A收到的B的數據是安全的,也保證了B傳輸的數據不被hack所監聽更改。
- 簽名證書:申請一個簽名證書,需要生成一個非對稱密鑰對,生成一個簽名請求,這個簽名請求包含了申請簽名證書的機構信息、數字證書有效期、申請機構的域名等信息。CA機構對申請機構的簽名請求進行開光以后,生成一個數字證書。這個數字證書就是用來證明申請機構安全的保證。數字證書中包含了申請機構的公鑰和信息的數字簽名。
openfire證書生成步驟
openfire服務器證書被安裝在/opt/openfire/resource/security/keystore
中(這里的openfire安裝在CentOS_7系統上),我們需要在openfire網站中的服務器標簽-TLS/SSL證書中,刪除自帶兩個證書。系統會自動的在keystore文件中刪除這兩個證書,之后keystore就是一個空的密鑰容器了。接下來我們要使用openssl還有keytool生成還有轉換openfire服務器的證書了
我把openfire服務器放置在/home/keys/openfire
目錄中,同樣按照以下操作敲命令即可,注意敲那些前面帶有#的
//創建`/home/keys/openfire目錄,進入該目錄
//生成openfire證書的密鑰對,接下來有個輸入openfire_key.pem密碼的操作,謹記密碼
# openssl genrsa -des3 -out openfire_key.pem 4096
//生成openfire_key.pem的簽名請求文件,按提示輸入密碼,其中需要注意Common Name是openfire的domain字符串,并不是openfire的hostName,謹記
# openssl req -new -key openfire_key.pem -out openfire.csr -days 3650
Enter pass phrase for openfire_key.pem:
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) [XX]:CN
State or Province Name (full name) []:Guangdong
Locality Name (eg, city) [Default City]:Guangzhou
Organization Name (eg, company) [Default Company Ltd]:openfire
Organizational Unit Name (eg, section) []:wzh.studio
Common Name (eg, your name or your server's hostname) []:openfire's domain
Email Address []:openfire@gmail.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:可以不填寫,按回車就ok
An optional company name []:可以不填寫,按回車就ok
//使用ca簽名,按照提示輸入兩個yes回車就ok了
# openssl ca -in openfire.csr -out openfire.pem
著重注意,在生成簽名請求的時候,Common Name屬性,必須輸入openfire服務器的domain,不要輸入openfire服務器的hostName
成功生成openfire.pem內容以后,需要將服務器的私鑰內容、私鑰密碼還有簽名證書內容配置到/opt/openfire/resource/security/keystore
容器中。如下圖順序配置
配置安卓客戶端,信任自簽名CA根證書
配置完服務器的證書以后,放置到項目中assert文件夾中,在客戶端連接openfire服務器時候開啟TLS連接配置。代碼如下
//要求連接必須使用TLS,builder是XMPPTCPConnectionConfiguration.Builder對象
builder.setSecurityMode(ConnectionConfiguration.SecurityMode.required);
//信任所有簽名證書,開啟這個就不要在客戶端安裝自簽名的CA機構根證書了。這里關閉它
//TLSUtils.acceptAllCertificates(builder);
//****************************** 信任服務器簽發機構的根證書開始 ***************************************//
//證書工廠。此處指明證書的類型
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
//創建一個證書庫
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
//取得SSL的SSLContext實例
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.
getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//cacert.cer是自簽名的cacert.pem根證書轉格式轉換成cer格式的
InputStream tis = getAssets().open("cacert.cer");
keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(tis));
trustManagerFactory.init(keyStore);
//****************************** 信任服務器簽發機構的根證書結束 ***************************************//
//設置客戶端信任的機構根證書
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
builder.setCustomSSLContext(sslContext);
到了這一步,就完成了Android客戶端和openfire服務器的單向TLS安全通訊
雙向TLS通訊
在了解完以上知識以后,實現Android客戶端和openfire服務器的雙向TLS安全通訊也是十分簡單了,只要簽發一個客戶端證書,安裝到Android手機上,最后讓openfire服務器信任這一個機構即可。但是這種做法很少見的,將客戶端的私鑰和簽名證書存儲在手機上是挺危險的。
自簽名客戶端證書
生成Android端使用keytool工具,命令行如下
//創建`/home/keys/client`目錄,進入存放client密鑰文件的文件夾
# cd /home/keys/client
//生成客戶端密鑰對,存儲到client.keystore,我的密碼是123456,自定義的
# keytool -genkey -alias client -keysize 2048 -validity 3650 -keyalg RSA -keystore client.keystore
//生成客戶端簽名申請文件
# keytool -certreq -alias client -sigalg SHA1withRSA -file client.csr -keystore client.keystore
//CA自簽名
# openssl ca -in client.csr -out client.cer -days -3650
//導入CA根證書到客戶端keystore中
# keytool -import -v -trustcacerts -alias ca_root -file /etc/pki/CA/cacert.pem -storepass 123456 -keystore client.keystore
//導入CA頒發的數字證書到keystore中
# keytool -import -v -alias client -file client.cer -keystore client.keystore
導入keystore到客戶端中
因為keystore的版本問題,需要下載一個軟件KeyStore Explorer修改keystore的版本位BKS-V1,因為Android只支持這個。KeyStore Explorer的地址下載地址
然后在SSLContext中添加為客戶端的證書,將client.keystore文件放到assert目錄下
//用來安裝客戶端證書,用戶雙向認證的。必須在服務器中信任該客服端證書的簽發機構根證書
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore kks = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream kis = getAssets().open("client.keystore");
kks.load(kis, "123456".toCharArray());
keyManagerFactory.init(kks, "123456".toCharArray());
//****************************** 安裝客戶端證書結束 ***************************************//
//設置SSLContext本地證書文件,還有信任根證書庫
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
builder.setCustomSSLContext(sslContext);
openfire服務器開啟雙向認證
-
在openfire服務器上開啟強制使用TLS連接,強制雙向認證
在openfire服務器上開啟強制使用TLS連接
在openfire服務器上開啟強制使用TLS連接 -
將用于自簽名的CA根證書設置到信任證書列表中,根證書位于
/etc/pki/CA/cacert.pem
將用于自簽名的CA根證書設置到信任證書列表中
將用于自簽名的CA根證書設置到信任證書列表中
將用于自簽名的CA根證書設置到信任證書列表中 - 重啟openfire服務器,手機客戶端重新登錄,完成與openfire服務器的雙向TLS通訊
參考文章
感謝以下文章作者,排名不分先后
Configure SSL/TLS certificate trust for XMPP with a trusted CA (for client-to-server channel security) the non-UI (stable) way
Weblogic服務器自簽名SSL證書解決iOS7.1企業應用部署問題
總結之:CentOS6.5下openssl加密解密及CA自簽頒發證書詳解