SLB配置自己生成的SSL证书--双向认证
因为公司业务的服务器只提供接口给移动端使用,不需要浏览器访问,而且要求使用双向认证,移动端要预埋证书,这就涉及到证书过期之后必须更新才能访问服务器的问题。使用第三方机构的服务器证书,有效期就1-2年,过期不得不更换,而移动端又不能确保用户及时更新,安全性什么的要求也高。总之,业务的需求决定了最终没有购买第三方机构的服务器证书,而是使用了openssl自己生成的证书。
之前是把证书直接配置在后端服务器上,但考虑到后续业务的扩展以及高可用的要求,还是应该要做负载均衡,所以就尝试在阿里云的SLB服务上做配置。
1.生成证书
首先生成证书,这里我是写了一个build_cert.sh脚本和配置来生成CA证书,以及服务端和客户端证书的,具体编写可以参考这篇文章:https://blog.csdn.net/ustccw/article/details/76691248
我这里生成测试证书使用的域名是:slb.test.mosiliang.top
接着在linux系统终端执行脚本build_cert.sh,就生成了ca.cert,ca.key,client.cert,client.key,server.cert,server.key等后续需要的文件。
2.上传证书
这些证书文件生成之后还不能直接使用,需要做一些格式转换。我这里使用openssl到脚本所在路径下执行如下指令:
openssl pkcs12 -export -in rsa/server.cert -inkey rsa/server.key -out server.pkcs12 -name server -CAfile rsa/ca.cert -caname root -chain
输入密码(随意写,但要记住,因为后续还要用到,我这里是:1523480205018),得到server.pkcs12服务器证书。
但是SLB里要求的证书格式是pem格式,所以要做PKCS#12 到 PEM 的转换:
openssl pkcs12 -in server.pkcs12 -nokeys -out server.pem
接下来上传证书到SLB做配置,先是上传服务器证书,这里要选择第三方签发证书,如下图:
接着下一步选择文件上传,就是server.pem文件,但是这里生成的文件直接上传会提示格式错误,按照里面的样例格式说明(如下图),
打开server.pem文件发现这里生成时多了一些额外的信息,我们只要把BEGIN CERTIFICATE和END CERTIFICATE的两部分单独拷贝到上传证书的输入框那里就可以了。
接着是服务端私钥的上传,也就是前面生成的server.key文件,这里直接上传也会提示格式不对,对比格式样例,我这里是收尾行少了个"RSA",即按照格式,只要把BEGIN PRIVATE KEY改成BEGIN RSA PRIVATE KEY,END PRIVATE KEY改成END RSA PRIVATE KEY就可以了。
上传服务器证书之后就可以选择证书了。另外,因为要实现双向认证,所以还要启用双向认证并上传CA根证书,如下图:
这里直接上传ca.cert文件就可以了。
3.测试验证
验证之前不要忘了在域名解析里把前面生成证书的域名slb.test.mosiliang.top解析到自己SLB服务器的VIP上。
上传好证书,配置SLB的后端服务器,创建虚拟服务器组,并指定访问后端服务器的端口,一般是80,我这里是8080。服务器组的选择,看官网说明:
至于后端里跑的测试服务环境,那就要自己去搭建了,我这里之前就有测试的后台服务环境在,所以这里只要确保后台服务正常能够访问到就行。
接着是客户端的验证,这里我们的客户端其实是移动端,但也可以先验证一下浏览器的访问。
浏览器验证
先http请求,验证下后端服务器访问情况是否正常:http://www.mosiliang.top:8080/xxxx
接着直接通过https访问浏览器:https://slb.test.mosiliang.top/xxxx
这是会报错的,因为我们开启了双向认证,要在电脑上安装对应客户端证书,之后才能访问。
客户端证书就是使用前面生成的client.cert和client.key来生成,同样是在脚本文件所在路径下执行命令:
openssl pkcs12 -export -in rsa/client.cert -inkey rsa/client.key -out client.p12 -name client -CAfile rsa/ca.cert -caname root -chain
输入密码(同样是随意写,这里是:2563481407018),之后得到client.p12。
把client.p12安装到win10上(安装过程要输入对应的密码2563481407018),然后再打开浏览器访问,大概因为是自己生成的证书(没有第三方认证),浏览器会弹框询问,选择对应证书确认就可以访问了,如下图:
如果在SLB上关闭双向认证,那么这里浏览器不用安装客户端证书就可以访问。
移动端验证
最后来验证移动端,这里使用的是Android端。
先生成客户端信任的服务器端证书,即把CA根证书导入,执行如下指令:
keytool -import -keystore client.truststore -keypass 1523480205018 -storepass 1523480205018 -alias ca -trustcacerts -file rsa/ca.cert
其中1523480205018是密码,可以随意的写,然后得到client.truststore文件。
而Android端使用的一个证书格式要求是bks,所以这里还要把client.truststore转成bks格式的。这里就要借助工具portecle.jar来转换,工具下载:https://sourceforge.net/projects/portecle/
大概步骤如下:
运行:java -jar portecle.jar
其中p12转换bks时可能会报错:java.security.invalidKeyException:lllegal key size
原因是限制了密钥长度,参考这里:
https://blog.csdn.net/ainiyiwan123/article/details/796225
解决:
下载JCE:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
得到jce_policy-8.zip,解压,把local_policy.jar和US_export_policy.jar复制到本地JDK对应安装目录下,我这里是/usr/lib/jvm/jdk1.8.0_25/jre/lib/security,替换原来的文件。
再重启portecle正常。
接下来,使用client_slb.bks和client…p12来做Android端验证。
我们写个demo来验证,先把client_slb.bks和client…p12(重命名为client_slb.p12)放到assets目录下;
接着是代码处理,这里连接用的是OkHttp,MainActivity.java的部分代码如下:
public void btnParamTest(View view) throws Exception {
OkHttpClient mOkHttpClient = SSLHelper.getOkHttpClient(this);
Request request = new Request.Builder()
.url("https://slb.test.mosiliang.top/xxx")
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("lyl","onFailure------222--------" + e.toString());
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i("lyl","onResponse------222--------" + response.body().string());
}
});
}
SSLHelper里的方法getOkHttpClient:
public static OkHttpClient getOkHttpClient(Context context) {
OkHttpClient client = null;
try {
// 服务器端需要验证的客户端证书
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
// 客户端信任的服务器端证书
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
try {
keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ksIn.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
tsIn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((trustStore));
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
//密钥管理器
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { trustManager }, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
client = new OkHttpClient.Builder()
.connectTimeout(300*1000,TimeUnit.MILLISECONDS)
.readTimeout(300*1000,TimeUnit.MILLISECONDS)
.writeTimeout(300*1000,TimeUnit.MILLISECONDS)
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new UnSafeHostnameVerifier())
.build();
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
相关配置参数:
private static final String KEY_STORE_TYPE_BKS = "bks";
private static final String KEY_STORE_TYPE_P12 = "PKCS12";
public static final String KEY_STORE_CLIENT_PATH = "client_slb.p12";//P12文件
private static final String KEY_STORE_TRUST_PATH = "client_slb.bks";//truststore文件
public static final String KEY_STORE_PASSWORD = "2563481407018";//P12文件密码
private static final String KEY_STORE_TRUST_PASSWORD = "1523480205018";//truststore文件密码
找个终端机器(如Android手机)来跑demo,运行访问,可以正常访问。
再验证下双向认证是否有效,把客户端证书的配置注释掉,如下图,初始化sslContext时设置km为null。
这时候再请求访问就会报错,返回log信息如下图:
而把SLB服务器上配置的”启用双向认证按钮”关掉,再去请求,验证能够正常访问。说明这里的双向认证配置OK。
总结:在SLB负载均衡服务上可以配置openssl生成的证书,但一般服务器证书都会购买第三方机构的,具体看使用场景吧。不过具体的配置差不多,不过是不用自己生成证书而已。
最后,分享一些阿里云服务活动:
云产品通用红包:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=wbq4iya3
建站优惠:https://www.aliyun.com/jianzhan/?userCode=wbq4iya3
高性能云服务器特惠:https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=wbq4iya3
商标服务:https://tm.aliyun.com/?userCode=wbq4iya3
新用户专享:https://promotion.aliyun.com/ntms/act/shoppingcart.html?userCode=wbq4iya3