XMPP學(xué)習(xí)——Android客戶端與openfire服務(wù)器單雙向TLS通訊(加密、安全)

本文從TLS安全傳輸層協(xié)議的簡(jiǎn)單流程、如何生成自簽名CA證書、自頒發(fā)服務(wù)器&客戶端證書、openfire服務(wù)器安全配置等方面去描述如何建立一個(gè)使用TLS加密的XMPP聊天通道。
這里的smack版本是V4.2.3,openfire服務(wù)器版本也是V4.2.3

TLS

關(guān)于TLS協(xié)議,我們可以在網(wǎng)上找到很多相關(guān)的文章,這里不對(duì)基礎(chǔ)概念作過(guò)多介紹。這邊會(huì)結(jié)合HTTPS對(duì)TLS的應(yīng)用和XMPP中的sasl標(biāo)簽和starttls去了解TLS。但是我們需要知道TLS1.0是基于SSL3.0的,現(xiàn)在的SSL協(xié)議已經(jīng)不建議使用了。最新的TLS協(xié)議是1.3版本,還在完善中,應(yīng)用最廣的是TLS1.2協(xié)議。

TLS并不是一個(gè)傳輸層協(xié)議,而是一個(gè)傳輸層安全協(xié)議。而HTTP是一個(gè)應(yīng)用層協(xié)議,TLS的作用是對(duì)HTTP傳輸?shù)臄?shù)據(jù)進(jìn)行加密解密處理,以此來(lái)保證HTTP傳輸數(shù)據(jù)的安全,因?yàn)镠TTP對(duì)于數(shù)據(jù)的傳輸是明文的。
這里對(duì)HTTPS連接做簡(jiǎn)單描述,其具體作用在TCP三次連接建立以后,由客戶端(這里是C&S模型)發(fā)出Client Hello的TLS連接建立請(qǐng)求,然后服務(wù)器會(huì)告訴客戶端:服務(wù)器支持什么加密算法,發(fā)給客戶端自己掏大價(jià)錢買來(lái)的簽名證書的內(nèi)容。
然后客戶端使用本地預(yù)裝或者人為加裝的CA根證書去驗(yàn)證服務(wù)器發(fā)來(lái)的簽名證書是否有效,如果安全有效那就bingo,建立TLS連接,否則斷開連接。

而XMPP協(xié)議,在不使用TLS的時(shí)候,是一個(gè)明文的傳輸?shù)膮f(xié)議。其中的SASL協(xié)議只是起到了對(duì)鑒權(quán)數(shù)據(jù)(賬號(hào)密碼,或者其他用來(lái)確定登錄資源的數(shù)據(jù))的保密作用,starttls標(biāo)簽才是建立TLS連接的關(guān)鍵。
客戶端根據(jù)openfire服務(wù)器hostName,domainName和端口建立的連接也是一個(gè)TCP連接。在完成3次握手以后,客戶端使用XMPP協(xié)議和openfire服務(wù)器進(jìn)行通訊,服務(wù)器會(huì)返回一個(gè)服務(wù)器支持的sasl機(jī)制列表和是否強(qiáng)制使用TLS連接的報(bào)文數(shù)據(jù),如下

<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>

下面的這個(gè)標(biāo)簽內(nèi)容,表明服務(wù)器強(qiáng)制要求使用TLS連接,客戶端跟服務(wù)器建立的TLS連接可以是雙向或者單向的,具體配置在文章下面會(huì)講到。

<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
        <required/>
</starttls>

下面這段報(bào)文,注明服務(wù)器支持的sasl鑒權(quán)機(jī)制

<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
        <mechanism>PLAIN</mechanism>
        <mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>

如果使用TLS連接,那SASL機(jī)制可以使用PLAIN,否則的話推薦使用SCRAM-SHA-1,這是關(guān)于SASL相關(guān)機(jī)制的文章。

TLS加密建立

因?yàn)樯鲜鰣?bào)文中的值是required,所以客戶端會(huì)繼續(xù)發(fā)送要求進(jìn)行TLS連接驗(yàn)證的報(bào)文

//客戶端請(qǐng)求建立TLS連接
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'></starttls>
//建立成功
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

至此之后,我們的XMPP連接內(nèi)容使用上了TLS傳輸層安全協(xié)議來(lái)保駕護(hù)航了(新版本的XMPP傳輸報(bào)文不是明文的,也有一定的監(jiān)聽難度)。

關(guān)于CA自簽名

了解了TLS之后,因?yàn)檫@篇文章是出于學(xué)習(xí)目的,所以我們?cè)賹W(xué)一下證書簽名等相關(guān)知識(shí)吧(貧窮)。

同樣,在網(wǎng)上有很多關(guān)于數(shù)字簽名的文章,這里同樣不多做闡述,直接講述如何做一個(gè)CA機(jī)構(gòu)大佬。操作系統(tǒng)Centos7,需要用到的軟件有openssl和jdk。

生成自簽名證書

首先,了解一下openssl的目錄結(jié)構(gòu)。
openssl安裝成功以后,其目錄在/etc/pki下,里面有個(gè)配置文章特別重要,/etc/pki/tls/openssl.cnf這個(gè)配置文件約定了很多屬性,下文提到的cakey.pem,cacert.pem等文件名都是/etc/pki/tls/openssl.cnf規(guī)定的,所以下列操作直接復(fù)制粘貼就好了。
在此之前,把[ policy_match ]下面的幾個(gè)屬性改成這樣,否則簽名會(huì)出錯(cuò)

# For the CA policy
[ policy_match ]
countryName     = optional 
stateOrProvinceName = optional
organizationName    = optional
organizationalUnitName  = optional
commonName      = supplied
emailAddress        = optional

然后依次進(jìn)行如下操作,請(qǐng)注意使用sudo權(quán)限操作。

# cd /etc/pki/CA
//生成一個(gè)長(zhǎng)度是2048的私鑰文件
# (umask 077; openssl genrsa -out private/cakey.pem 2048) 
//生成一個(gè)自簽名證書,有效期是3650天,時(shí)長(zhǎng)可以自由更改,里面需要填寫很多信息
# opensslreq -new -x509 -key private/cakey.pem -out cacert.pem -days 3650   
//生成存儲(chǔ)證書序列號(hào)的文件
# touch index.txt serial
//先自增1
# echo 01 > serial

自此,CA自簽名證書搞定了,接下來(lái)就要給自己頒發(fā)一個(gè)openfire服務(wù)器證書了

生成openfire服務(wù)器證書、自簽名、配置

在此之前,簡(jiǎn)單描述一下TLS中,非對(duì)稱算法、CA結(jié)構(gòu)和簽名證書的作用。

  • 非對(duì)稱算法:因?yàn)閷?duì)稱算法傳遞密鑰的不安全性,有了非對(duì)稱算法。其特點(diǎn)就是有兩個(gè)密鑰,一個(gè)公鑰一個(gè)私鑰,私鑰加密的內(nèi)容只有公鑰能解密,公鑰加密的內(nèi)容也只有私鑰才能解密。

    假如B和A通訊,B需要把數(shù)據(jù)傳輸給A。A有一對(duì)密鑰,A保管私鑰,把公鑰告訴B。那么B用A的公鑰加密數(shù)據(jù),然后把數(shù)據(jù)發(fā)給A,A用自己的私鑰解密數(shù)據(jù)就知道B傳輸?shù)臄?shù)據(jù)是什么了。在A的公鑰安全可信的前提下,就保證了B傳輸數(shù)據(jù)的保密性了,不會(huì)被攔截的hack所獲取。但是假如hack把A的公鑰替換成自己的公鑰,B加密的數(shù)據(jù)就會(huì)被hack攔截了,那么如何保證A的私鑰的真實(shí)性呢?

  • CA:我們的操作系統(tǒng)里面都會(huì)預(yù)裝一些權(quán)威CA機(jī)構(gòu)的根證書,被這些CA機(jī)構(gòu)開光(花錢為自己機(jī)構(gòu)注買一個(gè)數(shù)字證書)過(guò)的通訊對(duì)方就是可信機(jī)構(gòu),會(huì)有一個(gè)數(shù)字證書。只要被系統(tǒng)中預(yù)裝的某個(gè)CA結(jié)構(gòu)根證書驗(yàn)證通過(guò)了,就證明了通訊對(duì)方提供的公鑰是安全的(所以客戶端被做過(guò)手腳就不安全了)。

    當(dāng)A和B使用TLS通訊的時(shí)候,A花錢去名叫C的CA機(jī)構(gòu)獲得了數(shù)字證書,在通訊的時(shí)候傳輸給B。B通過(guò)內(nèi)置的CA機(jī)構(gòu)根證書驗(yàn)證,鑒定這是真的A。之后使用A的公鑰,或者使用A的公鑰加密過(guò)的對(duì)稱密鑰(節(jié)省資源消耗)去加密數(shù)據(jù),再發(fā)送給A。這樣就保證了A收到的B的數(shù)據(jù)是安全的,也保證了B傳輸?shù)臄?shù)據(jù)不被hack所監(jiān)聽更改。

  • 簽名證書:申請(qǐng)一個(gè)簽名證書,需要生成一個(gè)非對(duì)稱密鑰對(duì),生成一個(gè)簽名請(qǐng)求,這個(gè)簽名請(qǐng)求包含了申請(qǐng)簽名證書的機(jī)構(gòu)信息、數(shù)字證書有效期、申請(qǐng)機(jī)構(gòu)的域名等信息。CA機(jī)構(gòu)對(duì)申請(qǐng)機(jī)構(gòu)的簽名請(qǐng)求進(jìn)行開光以后,生成一個(gè)數(shù)字證書。這個(gè)數(shù)字證書就是用來(lái)證明申請(qǐng)機(jī)構(gòu)安全的保證。數(shù)字證書中包含了申請(qǐng)機(jī)構(gòu)的公鑰和信息的數(shù)字簽名。

openfire證書生成步驟

openfire服務(wù)器證書被安裝在/opt/openfire/resource/security/keystore中(這里的openfire安裝在CentOS_7系統(tǒng)上),我們需要在openfire網(wǎng)站中的服務(wù)器標(biāo)簽-TLS/SSL證書中,刪除自帶兩個(gè)證書。系統(tǒng)會(huì)自動(dòng)的在keystore文件中刪除這兩個(gè)證書,之后keystore就是一個(gè)空的密鑰容器了。接下來(lái)我們要使用openssl還有keytool生成還有轉(zhuǎn)換openfire服務(wù)器的證書了
我把openfire服務(wù)器放置在/home/keys/openfire目錄中,同樣按照以下操作敲命令即可,注意敲那些前面帶有#的

//創(chuàng)建`/home/keys/openfire目錄,進(jìn)入該目錄
//生成openfire證書的密鑰對(duì),接下來(lái)有個(gè)輸入openfire_key.pem密碼的操作,謹(jǐn)記密碼
# openssl genrsa -des3 -out openfire_key.pem 4096
//生成openfire_key.pem的簽名請(qǐng)求文件,按提示輸入密碼,其中需要注意Common Name是openfire的domain字符串,并不是openfire的hostName,謹(jǐn)記
# 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簽名,按照提示輸入兩個(gè)yes回車就ok了
# openssl ca -in openfire.csr -out openfire.pem

著重注意,在生成簽名請(qǐng)求的時(shí)候,Common Name屬性,必須輸入openfire服務(wù)器的domain,不要輸入openfire服務(wù)器的hostName
成功生成openfire.pem內(nèi)容以后,需要將服務(wù)器的私鑰內(nèi)容、私鑰密碼還有簽名證書內(nèi)容配置到/opt/openfire/resource/security/keystore容器中。如下圖順序配置

配置服務(wù)器證書

配置服務(wù)器證書

配置服務(wù)器證書

配置安卓客戶端,信任自簽名CA根證書

配置完服務(wù)器的證書以后,放置到項(xiàng)目中assert文件夾中,在客戶端連接openfire服務(wù)器時(shí)候開啟TLS連接配置。代碼如下

    //要求連接必須使用TLS,builder是XMPPTCPConnectionConfiguration.Builder對(duì)象
    builder.setSecurityMode(ConnectionConfiguration.SecurityMode.required);
    //信任所有簽名證書,開啟這個(gè)就不要在客戶端安裝自簽名的CA機(jī)構(gòu)根證書了。這里關(guān)閉它
    //TLSUtils.acceptAllCertificates(builder);
    //******************************      信任服務(wù)器簽發(fā)機(jī)構(gòu)的根證書開始      ***************************************//
    //證書工廠。此處指明證書的類型
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    //創(chuàng)建一個(gè)證書庫(kù)
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null);
    //取得SSL的SSLContext實(shí)例
    SSLContext sslContext = SSLContext.getInstance("TLS");
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);
    //cacert.cer是自簽名的cacert.pem根證書轉(zhuǎn)格式轉(zhuǎn)換成cer格式的
    InputStream tis = getAssets().open("cacert.cer");
    keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(tis));
    trustManagerFactory.init(keyStore);
    //******************************      信任服務(wù)器簽發(fā)機(jī)構(gòu)的根證書結(jié)束      ***************************************//
    //設(shè)置客戶端信任的機(jī)構(gòu)根證書
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    builder.setCustomSSLContext(sslContext);
    

到了這一步,就完成了Android客戶端和openfire服務(wù)器的單向TLS安全通訊

雙向TLS通訊

在了解完以上知識(shí)以后,實(shí)現(xiàn)Android客戶端和openfire服務(wù)器的雙向TLS安全通訊也是十分簡(jiǎn)單了,只要簽發(fā)一個(gè)客戶端證書,安裝到Android手機(jī)上,最后讓openfire服務(wù)器信任這一個(gè)機(jī)構(gòu)即可。但是這種做法很少見的,將客戶端的私鑰和簽名證書存儲(chǔ)在手機(jī)上是挺危險(xiǎn)的。

自簽名客戶端證書

生成Android端使用keytool工具,命令行如下

//創(chuàng)建`/home/keys/client`目錄,進(jìn)入存放client密鑰文件的文件夾
# cd /home/keys/client
//生成客戶端密鑰對(duì),存儲(chǔ)到client.keystore,我的密碼是123456,自定義的
# keytool -genkey -alias client -keysize 2048 -validity 3650 -keyalg RSA -keystore client.keystore
//生成客戶端簽名申請(qǐng)文件
# keytool -certreq -alias client -sigalg SHA1withRSA -file client.csr -keystore client.keystore
//CA自簽名
# openssl ca -in client.csr -out client.cer -days -3650
//導(dǎo)入CA根證書到客戶端keystore中
# keytool -import -v -trustcacerts -alias ca_root -file /etc/pki/CA/cacert.pem -storepass 123456 -keystore client.keystore
//導(dǎo)入CA頒發(fā)的數(shù)字證書到keystore中
# keytool -import -v -alias client -file client.cer -keystore client.keystore

導(dǎo)入keystore到客戶端中

因?yàn)閗eystore的版本問(wèn)題,需要下載一個(gè)軟件KeyStore Explorer修改keystore的版本位BKS-V1,因?yàn)锳ndroid只支持這個(gè)。KeyStore Explorer的地址下載地址
然后在SSLContext中添加為客戶端的證書,將client.keystore文件放到assert目錄下

    //用來(lái)安裝客戶端證書,用戶雙向認(rèn)證的。必須在服務(wù)器中信任該客服端證書的簽發(fā)機(jī)構(gòu)根證書
    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());
    //******************************      安裝客戶端證書結(jié)束     ***************************************//
    //設(shè)置SSLContext本地證書文件,還有信任根證書庫(kù)
    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
    builder.setCustomSSLContext(sslContext);

openfire服務(wù)器開啟雙向認(rèn)證

  1. 在openfire服務(wù)器上開啟強(qiáng)制使用TLS連接,強(qiáng)制雙向認(rèn)證
    在openfire服務(wù)器上開啟強(qiáng)制使用TLS連接

    在openfire服務(wù)器上開啟強(qiáng)制使用TLS連接
  2. 將用于自簽名的CA根證書設(shè)置到信任證書列表中,根證書位于/etc/pki/CA/cacert.pem
    將用于自簽名的CA根證書設(shè)置到信任證書列表中

    將用于自簽名的CA根證書設(shè)置到信任證書列表中

    將用于自簽名的CA根證書設(shè)置到信任證書列表中
  3. 重啟openfire服務(wù)器,手機(jī)客戶端重新登錄,完成與openfire服務(wù)器的雙向TLS通訊

參考文章

感謝以下文章作者,排名不分先后
Configure SSL/TLS certificate trust for XMPP with a trusted CA (for client-to-server channel security) the non-UI (stable) way
Weblogic服務(wù)器自簽名SSL證書解決iOS7.1企業(yè)應(yīng)用部署問(wèn)題
總結(jié)之:CentOS6.5下openssl加密解密及CA自簽頒發(fā)證書詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容