客户端认证自签名HTTPS证书

  在最近的项目中,与某服务器连接的请求采用的是https协议,但是该服务器的证书又不是经过权威机构认证的证书,因此采用普通的方式直接连接是不行。本文给出解决方案。
  HTTPS是HTTP之下、TCP之上的安全密码层,可以使用SSL或TSL,本文用SSL来描述SSL或TSL。大部分困难的编码和解码工作都在SSL中完成了,客户端和服务器端在使用HTTPS进行通信的时候不需要实现复杂的加解密算法之类的。SSL使用证书来创建安全连接,一般有两种验证方式。第一种是客户端认证服务器端的提供的证书,一般的HTTPS网站都采用这种方式。第二种是客户端和服务器端都要认证对方的证书,一般是APP应用和它的服务器可能会采用这样的方式。本文说的就是第一种情况,并且当该服务器的证书不是权威机构认证的证书(一般是为了省钱。。。因为权威机构认证的证书可不便宜。。。)。
  以Android客户端为例,在目前最新的SDK 23中,已经完全移除了HttpClient,所有的网络请求均要采用HttpUrlConnection来实现。那么对于这种自签名的证书,HttpUrlConnection其实只需要多设置一个属性就可以了,其实就是setSSLSocketFactory()方法就可以了。SSLSocketFactory这个类在我的理解就是这一次HTTPS连接SSL层的抽象,并且这个类是通过SSLContext的getSocketFactory()来得到。那么问题就变成了如何去构造SSLContext就可以了。
  SSLContext只需要通过以下代码就可以实例化。

SSLContext sslContext = SSLContext.getInstance("SSL");
ssl.init(null, null, null);

  注意到init方法的第二个参数传递是TrustManager的数组。那么问题就在于如何使用服务器提供的证书去生成TrustManager数组了。直接上代码,在Android里,将证书放到asset目录中,通过AssetManager读取它。

    public static TrustManager[] getTrustManagers(Context cxt) {
        InputStream caInput = null;
        try {
            caInput = new BufferedInputStream(
                    cxt.getResources().getAssets().open("srca.cer"));
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate ca = cf.generateCertificate(caInput);

            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            return tmf.getTrustManagers();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (caInput != null) caInput.close();
        }
        return getTrustAnyManager();
    }

    public static TrustManager[] getTrustAnyManager() {
        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        return new TrustManager[]{tm};
    }

  以上就是具体的代码实现。第二个getTrustAnyManager方法提供的是一个信任任意证书的TrustManager数组,会有安全性问题。只有当第一个方法发生异常的时候才调用第二个方法。
  经过以上方法,就可以获得一个SSLContext,只需要判断如果connection对象是HttpsUrlConnect的话,调用connection.setSocketFactory(sslContext.getSocketFactory()) 就可以了.

你可能感兴趣的:(Android,计算机网络)