最近项目在使用的netty框架加上了SSL安全设置,SSL可单项验证也可双向验证,我使用的是双向验证,即Client验证Server同时Server也验证Client。
以下只说明Client(Android)端的实现方式。
- 首先需要两个文件,
client.p12
和cacert.pem
,由服务器端提供。 - 使用java的
keytool
工具将cacert.pem
导入到keystore中1
keytool -import -trustcacerts -keystore /Users/xxx/server.bks -file /Users/xxx/cacert.pem -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
记住这个命令执行后要求输入的密码。
(其中的org.bouncycastle.jce.provider.BouncyCastleProvider如何添加使用自行百度即可。)
现在已经有了两个文件
client.p12
和server.bks
准备完成,下面进行java实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public SSLContext createSSLContext(Context context) {
SSLContext sslContext = null;
try {
// 该密码为生成client.p12时设置的密码
String keyPassword = "";
// 该密码为生成server.bks时设置的密码
String trustPassword = "";
// key store manager
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream keyInput = context.getResources().openRawResource(R.raw.client);
keyStore.load(keyInput, keyPassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
// trust store manager
KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream trustInput = context.getResources().openRawResource(R.raw.server);
trustStore.load(trustInput, trustPassword.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// assemble
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}接下来配置netty中的SSL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class ClientInitializer extends ChannelInitializer<SocketChannel> {
private SSLContext mSslContext;
public ClientInitializer(SSLContext sslContext) {
mSslContext = sslContext;
}
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine sslEngine = mSslContext.createSSLEngine();
sslEngine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(sslEngine));
pipeline.addLast("decoder", new ClientDecoder());
pipeline.addLast("encoder", new ClientEncoder());
pipeline.addLast("handler", new ClientHandler());
}
}至此,所有工作就完成了,以上标准配置在android7.0以下的机器上均可正常运行,但是一旦运行到7.0及以上的机器上就会报错
1
2
3
4
5
6
7
8
9
10javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
经过一番查询验证,在官网上找到了解决办法,原链接-不需翻墙即可访问。
解决方式如下
- 和服务器端再要一个文件
server.crt
。 - 准备完成,下面修改java实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public SSLContext createSSLContext(Context context) {
SSLContext sslContext = null;
try {
String keyPassword = "";
// key store manager
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream keyInput = context.getResources().openRawResource(R.raw.client);
keyStore.load(keyInput, keyPassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
// trust store manager
CertificateFactory cf = CertificateFactory.getInstance("X509");
InputStream caInput = context.getResources().openRawResource(R.raw.server);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
trustStore.setCertificateEntry("CA", ca);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// assemble
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
只需要修改trust store
的创建方式,其他不需要改动。经过以上修改,程序在所有版本的机器上都可以正常运行了!棒