为了实现TLS socket 消息认证。
Server需要:
1)KeyStore: 其中保存服务端的私钥
2)Trust KeyStore:其中保存客户端的授权证书
同样,Client需要:
1)KeyStore:其中保存客户端的私钥
2)Trust KeyStore:其中保存服务端的授权证书
但是Android 确实只能识别BKS 格式的证书,如果为其他格式的证书需要转换,
其中用到的转换工具为Portecle(免费):
官方下载地址为:https://sourceforge.net/projects/portecle/
百度网盘下载地址:链接: https://pan.baidu.com/s/1r4viK9X6wvnqYXrOzaVhZQ 提取码: kahh
一、证书格式转换
1.Portecle的使用步骤如下:
如果在步骤2中无法完成导入.pem证书文件 和选取.key私钥文件,那就需要将.pem证书文件 和.key私钥文件合并要一个密钥库中,如jks或pkcs12(.pfx文件)中。
2.将PEM格式转为jks或pfx
方式一:通过openssl和keytool转换
openssl可以到官网下载,openssl暂时没有windows版,大伙可以在此处下载Win32OpenSSLwindows版本
下载完记得配置环境变量,同时也记得配置jdk的环境变量,因为后面需要用到keytool命令
现在假设你拿到了客户端证书client.crt和client.key(或者client.pem和client.key)
通过以下命令生成pfx文件,期间会让输入密码,输入你想要的密码即可,假如这里输入的是123456
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.pem
或者
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
执行成功则可以看到client.pfx文件,
如果需要得到jks格式的证书可以接着再执行如下命令
keytool -importkeystore -srckeystore client.pfx -destkeystore client.jks -srcstoretype PKCS12 -deststoretype JKS
执行成功即可看到生成了client.jks,拿到生成的jks就可以通过Portecle工具转换成bks了。
注:Openssl 使用操作可以参考:https://blog.csdn.net/liao20081228/article/details/77159039
方式二:在线转换
你可以到这里进行格式转换:https://myssl.com/cert_convert.html,在线转换真的超方便。
二、双向认证(客户端实现)
添加了SSLHelper.java工具类用来生成SSLSocketFactory对象
package com.yf.ssl.client;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/**
* @description: <功能简述>
* @Author: YunFeng
* @CreateTime: 2020-2-13
*/
public class SSLHelper {
private static final String TAG = "SSLHelper";
private final static String CLIENT_PRI_KEY = "client_bks.bks";
private final static String TRUSTSTORE_PUB_KEY = "service_bks.bks";
private final static String CLIENT_BKS_PASSWORD = "123456";
private final static String TRUSTSTORE_BKS_PASSWORD = "123456";
private final static String KEYSTORE_TYPE = "BKS";
private final static String PROTOCOL_TYPE = "TLS";
private final static String CERTIFICATE_STANDARD = "X.509";
private static AssetManager mAssetManager = null;
public static SSLSocketFactory getSSLCertifcation(Context context) {
if(context == null){
Log.w(TAG, "getSSLCertifcation () context is null");
return null;
}
if (mAssetManager == null) {
mAssetManager = context.getAssets();
}
Log.d(TAG, "getSSLCertifcation () ");
try {
SSLContext sslContext = SSLContext.getInstance(PROTOCOL_TYPE);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyClient = KeyStore.getInstance(KEYSTORE_TYPE);
InputStream in_client_bks = mAssetManager.open(CLIENT_PRI_KEY);
keyClient.load(in_client_bks, null);//该证书未设置密码 以下类似
try {
keyManagerFactory.init(keyClient, null);
} catch (UnrecoverableKeyException e) {
Log.e(TAG, "getSSLCertifcation() UnrecoverableKeyException:" + e.toString());
}
KeyStore tClient = KeyStore.getInstance(KEYSTORE_TYPE);
InputStream trustClient = mAssetManager.open(TRUSTSTORE_PUB_KEY);
tClient.load(trustClient, null);
// 另一种加载方式,不需要是bks的格式,但是仅仅可以加载trust
// InputStream trustIn = mAssetManager.open("service_cert.pem");
// CertificateFactory cf = CertificateFactory.getInstance(CERTIFICATE_STANDARD);
// Certificate clientC = cf.generateCertificate(trustIn);
// tClient.load(null,null);
// tClient.setCertificateEntry("clientT",clientC);
// 创建一个 TrustManager 仅把 Keystore 中的证书 作为信任的锚点
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(tClient);
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
// 配置所有证书的 认证管理器 未使用
// javax.net.ssl.TrustManager[] trustAllCerts = {new TrustAllTrustManager()};
// sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}catch (Exception e){
Log.e(TAG, "getSSLCertifcation() Exception:" + e.toString());
}
return null;
}
}
然后即可以得到SSLSocket
SSLSocket sslSocket = (SSLSocket) SSLHelper.getSSLCertifcation(context).createSocket(IP, PORT)
由于该项目中用到的是OKSocket框架,其实这里更简单:
ConnectionInfo mInfo = new ConnectionInfo(ip, port);
SSLSocketFactory sSLSocketFactory= SSLHelper.getSSLCertifcation(context);
if(sSLSocketFactory == null){
Log.w(TAG,"没有加载到证书");
}
OkSocketSSLConfig mOkSocketSSLConfig =new OkSocketSSLConfig.Builder()
.setProtocol("TLS")
.setCustomSSLFactory(sSLSocketFactory)//配置上SSLSocketFactory即可
.build();
OkSocketOptions mOkOptions = new OkSocketOptions.Builder()
.setReconnectionManager(new NoneReconnect())
.setConnectTimeoutSecond(5)//超时时间
.setCallbackThreadModeToken(new OkSocketOptions.ThreadModeToken() {
@Override
public void handleCallbackEvent(ActionDispatcher.ActionRunnable runnable) {
handler.post(runnable);
}
}).setSSLConfig(mOkSocketSSLConfig)//配置OkSocketSSLConfig即可
.build();
IConnectionManager mManager = OkSocket.open(mInfo).option(mOkOptions);
三、双向认证(服务点实现)
服务端就不详细介绍,有兴趣的可以参考他人的文章,写的非常好:https://blog.csdn.net/albb_/article/details/90637338