Android低版本(4.4)okhttp 网络适配

目录

访问网络时,出现错误:

javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb7eabc88: Failure in SSL library, usually a protocol error    error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0xa4fb8d5c:0x00000000)

SSLSocket的setEnabledProtocols配置支持TLSv1.1,TLSv1.2协议

错误:java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

可以给客户端安装证书

除了给客户端安装证书外,还有其他的解决办法

1、忽略所有证书验证

2、自定义对证书验证(需要将证书提前放到assert文件夹中)


访问网络时,出现错误:

javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb7eabc88: Failure in SSL library, usually a protocol error
    error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0xa4fb8d5c:0x00000000)

通过Https通信,客户端与服务端在SSL/TLS层建立安全连接前,涉及到版本协商过程。

Android 4.4默认支持的SSL/TLS版本是SSLv3(1996发布,2015弃用),TLSv1(1996发布)。但TLSv1.1,TLSv1.2(主流)实际上也是在其支持范围内的,需要人为去配置。

SSLSocketsetEnabledProtocols配置支持TLSv1.1,TLSv1.2协议

在okhttp中,建立Tls连接,创建套接字是通过SSLSocketFactory实现的,重写SSLSocketFactory,通过SSLSocketsetEnabledProtocols(String protocols[]),使其支持TLSv1.1,TLSv1.2。最后将其设置给OkHttpClient

SSLSocketFactory factory = new SSLSocketFactoryCompat(context);

// okhttpClient的builder
builder.sslSocketFactory(factory);
public class SSLSocketFactoryCompat extends SSLSocketFactory{
    private static final String[] TLS_V12_ONLY = {"TLSv1.2"};

    private final SSLSocketFactory delegate;

    public SSLSocketFactoryCompat() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, null, null);
        delegate = sc.getSocketFactory();
    }

    public SSLSocketFactoryCompat(SSLSocketFactory delegate) {
        if (delegate == null) {
            throw new NullPointerException();
        }
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    private Socket enableTls12(Socket socket) {
        if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 20) {
            if (socket instanceof SSLSocket) {
                ((SSLSocket) socket).setEnabledProtocols(TLS_V12_ONLY);
            }
        }
        return socket;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return enableTls12(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        return enableTls12(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        return enableTls12(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return enableTls12(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTls12(delegate.createSocket(address, port, localAddress, localPort));
    }
}

然而,通过上述配置后,有些url还是无法访问,会出现

错误:java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

这里我遇到的问题是客户端没有对应的根CA证书导致的

可以给客户端安装证书

在浏览器网站上,输入在Android上访问出错的url,点击链接-连接-证书有效-详细信息,证书链汇中选择最上面一个,导出根证书

导出后将文件传入Androidsd卡中,然后在设置中找到安装证书的选项。

或者,在代码中打开安装证书的设置

    /**
     * 安裝证书
     */
    public void installCert(Context context) {
        InputStream assetsIn = null;
        Intent intent = KeyChain.createInstallIntent();
        try {
            //获取证书流,注意参数为assets目录文件全名
            assetsIn = context.getAssets().open("a.cer");
            byte[] cert = new byte[10240];
            assetsIn.read(cert);
            javax.security.cert.X509Certificate x509 = null;
            try {
                x509 = javax.security.cert.X509Certificate.getInstance(cert);
                //将证书传给系统
                intent.putExtra(KeyChain.EXTRA_CERTIFICATE, x509.getEncoded());
            } catch (CertificateException e) {
                e.printStackTrace();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        //此处为给证书设置默认别名,第二个参数可自定义,设置后无需用户输入
        intent.putExtra("name", "gz");
        startActivity(intent);
    }

  

安装成功后,尝试是否可以访问链接。

除了给客户端安装证书外,还有其他的解决办法

1、忽略所有证书验证

2、自定义对证书验证(需要将证书提前放到assert文件夹中)

改造一下SSLSocketFactory


import android.content.Context;
import android.os.Build;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;


public class SSLSocketFactoryCompat extends SSLSocketFactory{
    private static final String[] TLS_V12_ONLY = {"TLSv1.1"};
    private static final String TAG = "TAG";

    private final SSLSocketFactory delegate;
    private X509TrustManager trustManager;
    private Context context;

    public SSLSocketFactoryCompat(Context context) throws KeyManagementException, NoSuchAlgorithmException {
        this.context = context;
        /*
            a.cer是浏览器导出的CA根证书(这里没有用到,另外一个解决方法 安装到设备中的)
            j.pem证书中存储了服务器的公钥等信息,并没有经过CA机构的加密,一般是由服务器导出
         */

//        delegate = getSslSocketFactoryTrustALL();
        delegate = getSslSocketFactorySpecificVerify();
    }

    // 默认创建代理方式
    private SSLSocketFactory getSslSocketFactoryDefault() throws NoSuchAlgorithmException, KeyManagementException {
        final SSLSocketFactory delegate;
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, null, null);
        delegate = sc.getSocketFactory();
        return delegate;
    }

    // 自定义证书校验
    private SSLSocketFactory getSslSocketFactorySpecificVerify() throws KeyManagementException, NoSuchAlgorithmException {
        final SSLSocketFactory delegate;
        SSLContext sc = SSLContext.getInstance("TLS");

        // 自定义证书验证
        trustManager = new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                if (chain == null || chain.length == 0) {
                    throw new CertificateException("checkServerTrusted: X509Certificate array is null");
                }

                if (!(null != authType && authType.equals("ECDHE_RSA"))) {
                    throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");
                }
                //判断证书是否是本地信任列表里颁发的证书(系统默认的验证)
                try {
                    TrustManagerFactory factory = TrustManagerFactory.getInstance("X509");
                    factory.init((KeyStore) null);
                    for (TrustManager trustManager : factory.getTrustManagers()) {
                        ((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
                    }
                    Log.i("TAG", "checkServerTrusted: 本地");
                    return;//用系统的证书验证服务器证书,验证通过就不需要继续验证证书信息;也可以注释掉,继续走自己的服务器证书逻辑
                } catch (Exception e) {
                    Log.i(TAG, "checkServerTrusted: " + e.getMessage());
                    e.printStackTrace();
                    //注意这个地方不能抛异常,用系统的证书验证服务器证书,没通过就用自己的验证规则
//                        throw new CertificateException(e);
                }

                //获取本地证书中的信息
                String clientEncoded = "";//公钥
                String clientSubject = "";//颁发给
                String clientIssUser = "";//颁发机构
                try (InputStream inputStream = getAssetFileInputStream(context, "j.pem")) {
                    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                    X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                    clientEncoded = new BigInteger(1, clientCertificate.getPublicKey().getEncoded()).toString(16);
                    clientSubject = clientCertificate.getSubjectDN().getName();
                    clientIssUser = clientCertificate.getIssuerDN().getName();
                    Log.i("TAG", "clientEncoded: " + clientEncoded);
                    Log.i("TAG", "clientSubject: " + clientSubject);
                    Log.i("TAG", "clientIssUser: " + clientIssUser);

                } catch (Exception e) {
                    e.printStackTrace();
                    throw new CertificateException(e);
                }

                //获取网络中的证书信息
                X509Certificate certificate = chain[0];
                PublicKey publicKey = certificate.getPublicKey();
                String serverEncoded = new BigInteger(1, publicKey.getEncoded()).toString(16);

                if (!clientEncoded.equals(serverEncoded)) {
                    Log.i("TAG", "checkServerTrusted: server's PublicKey is not equals to client's PublicKey");
                    throw new CertificateException("server's PublicKey is not equals to client's PublicKey");
                }
                String subject = certificate.getSubjectDN().getName();
                if (!clientSubject.equals(subject)) {
                    Log.i("TAG", "checkServerTrusted: server's SubjectDN is not equals to client's SubjectDN");
                    throw new CertificateException("server's SubjectDN is not equals to client's SubjectDN");
                }
                String issuser = certificate.getIssuerDN().getName();
                if (!clientIssUser.equals(issuser)) {
                    Log.i("TAG", "checkServerTrusted: server's IssuerDN is not equals to client's IssuerDN");
                    throw new CertificateException("server's IssuerDN is not equals to client's IssuerDN");
                }
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        sc.init(null, new TrustManager[]{trustManager}, new SecureRandom());
        delegate = sc.getSocketFactory();

        return delegate;
    }



    // 信任所有证书
    private SSLSocketFactory getSslSocketFactoryTrustALL() throws NoSuchAlgorithmException, KeyManagementException {
        final SSLSocketFactory delegate;
        trustManager = new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

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

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                Log.i("TAG", "getAcceptedIssuers: ");
                return new X509Certificate[0];
            }
        };
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
        delegate = sslContext.getSocketFactory();

        return delegate;
    }

    public SSLSocketFactoryCompat(SSLSocketFactory delegate) {
        if (delegate == null) {
            throw new NullPointerException();
        }
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        Log.i(TAG, "getDefaultCipherSuites: ");
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        Log.i(TAG, "getSupportedCipherSuites: ");
        return delegate.getSupportedCipherSuites();
    }

    private Socket enableTls12(Socket socket) {
        Log.i(TAG, "enableTls12: ");
        if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 20) {
            if (socket instanceof SSLSocket) {
                ((SSLSocket) socket).setEnabledProtocols(TLS_V12_ONLY);
            }
        }
        return socket;
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        Log.i(TAG, "createSocket: ");
        return enableTls12(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        Log.i(TAG, "createSocket: ");
        return enableTls12(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        Log.i(TAG, "createSocket: ");
        return enableTls12(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        Log.i(TAG, "createSocket: ");
        return enableTls12(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        Log.i(TAG, "createSocket: ");
        return enableTls12(delegate.createSocket(address, port, localAddress, localPort));
    }
    private static InputStream getAssetFileInputStream(Context context, String assetsFileName) {
        try {
            return context.getAssets().open(assetsFileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值