XMPP学习——Android客户端与openfire服务器单双向TLS通讯(加密、安全)

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

  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自签颁发证书详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容