XMPP学习——Android客户端与openfire服务器单双向TLS通讯

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/WBig_Red/article/details/82981082

本文从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 []:[email protected]

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服务器开启双向认证

  1. 在openfire服务器上开启强制使用TLS连接,强制双向认证
    在openfire服务器上开启强制使用TLS连接
    在openfire服务器上开启强制使用TLS连接
  2. 将用于自签名的CA根证书设置到信任证书列表中,根证书位于/etc/pki/CA/cacert.pem
    将用于自签名的CA根证书设置到信任证书列表中
    将用于自签名的CA根证书设置到信任证书列表中
    将用于自签名的CA根证书设置到信任证书列表中
  3. 重启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自签颁发证书详解

猜你喜欢

转载自blog.csdn.net/WBig_Red/article/details/82981082