oynix

于无声处听惊雷,于无色处见繁花

Android使用netty框架配置SSL适配7.0以上的系统

最近项目在使用的netty框架加上了SSL安全设置,SSL可单项验证也可双向验证,我使用的是双向验证,即Client验证Server同时Server也验证Client。
以下只说明Client(Android)端的实现方式。

  • 首先需要两个文件,client.p12cacert.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.p12server.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
    30
    public 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
    20
    class ClientInitializer extends ChannelInitializer<SocketChannel> {

    private SSLContext mSslContext;

    public ClientInitializer(SSLContext sslContext) {
    mSslContext = sslContext;
    }

    @Override
    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
    10
    javax.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
    37
    public 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的创建方式,其他不需要改动。经过以上修改,程序在所有版本的机器上都可以正常运行了!棒

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2018/05/126e07ee5590/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道