SLB配置自己生成的SSL证书--双向认证

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做配置,先是上传服务器证书,这里要选择第三方签发证书,如下图:
SLB配置自己生成的SSL证书--双向认证_第1张图片

接着下一步选择文件上传,就是server.pem文件,但是这里生成的文件直接上传会提示格式错误,按照里面的样例格式说明(如下图),

SLB配置自己生成的SSL证书--双向认证_第2张图片

打开server.pem文件发现这里生成时多了一些额外的信息,我们只要把BEGIN CERTIFICATE和END CERTIFICATE的两部分单独拷贝到上传证书的输入框那里就可以了。

接着是服务端私钥的上传,也就是前面生成的server.key文件,这里直接上传也会提示格式不对,对比格式样例,我这里是收尾行少了个"RSA",即按照格式,只要把BEGIN PRIVATE KEY改成BEGIN RSA PRIVATE KEY,END PRIVATE KEY改成END RSA PRIVATE KEY就可以了。

上传服务器证书之后就可以选择证书了。另外,因为要实现双向认证,所以还要启用双向认证并上传CA根证书,如下图:
SLB配置自己生成的SSL证书--双向认证_第3张图片

这里直接上传ca.cert文件就可以了。

3.测试验证

  验证之前不要忘了在域名解析里把前面生成证书的域名slb.test.mosiliang.top解析到自己SLB服务器的VIP上。

上传好证书,配置SLB的后端服务器,创建虚拟服务器组,并指定访问后端服务器的端口,一般是80,我这里是8080。服务器组的选择,看官网说明:
SLB配置自己生成的SSL证书--双向认证_第4张图片

至于后端里跑的测试服务环境,那就要自己去搭建了,我这里之前就有测试的后台服务环境在,所以这里只要确保后台服务正常能够访问到就行。

接着是客户端的验证,这里我们的客户端其实是移动端,但也可以先验证一下浏览器的访问。

浏览器验证

先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配置自己生成的SSL证书--双向认证_第5张图片

如果在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

打开client.truststore文件,如下图:
SLB配置自己生成的SSL证书--双向认证_第6张图片

输入前面设置的对应密码,之后转换格式,如下图:
SLB配置自己生成的SSL证书--双向认证_第7张图片

再保存文件,如下图:最终得到client_slb.bks文件。
SLB配置自己生成的SSL证书--双向认证_第8张图片
SLB配置自己生成的SSL证书--双向认证_第9张图片

其中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。
SLB配置自己生成的SSL证书--双向认证_第10张图片

这时候再请求访问就会报错,返回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

你可能感兴趣的:(运维)