retrofit遇上https自签名证书

转载自:

https://blog.csdn.net/u013768203/article/details/72874242

 

最近来了家新公司,后台设计在非线上环境用自签名证书,线上环境用CA证书,然后发了份.cer公钥给我.让我在客户端处理一下.

我查了很多博客,只言片语的, 
HTTPS的流程也比较长, 
今天调试好了,贴出连续的代码给大家看一下.

https有2种情况 单向验证和双向验证 
单向认证:客户端通过直接读取后台给的公钥验证握手 
比如直接读取cer文件或者直接把公钥写在代码里. 
双向认证:客户的有公钥,后台也有公钥,互相存储对方的公钥,验证网络通讯, 
这个时候Android端要生成bks.

有很多博客一上来就生成bks,一定要知道什么场景.

而我现在讨论单向验证, 
基于鸿洋大神的这篇做代码补充: 
Android Https相关完全解析 当OkHttp遇到Https

单向验证方法一:简单粗暴,直接信任所有.

在MainActicity.java中: 有个网络请求:loadData()方法

public void loadData() {
        SSLSocketFactoryUtils.MyX509TrustManager myX509TrustManager= new SSLSocketFactoryUtils.MyX509TrustManager();
        OkHttpClient okhttpclient=new OkHttpClient.Builder()
               .sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(), SSLSocketFactoryUtils.createTrustAllManager())
                .hostnameVerifier(new SSLSocketFactoryUtils.TrustAllHostnameVerifier())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://gw.dev.cmrh.com:8888/RH_MAS/")
                .client(okhttpclient)
       .addConverterFactory(
               new Converter.Factory(){
                   public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
                                                                                     Annotation[] annotations, Retrofit retrofit) {
                       return new Converter<ResponseBody, Object>() {
                           @Override
                           public Object convert(ResponseBody value) throws IOException {
                               return value.string();
                           }
                       };
                   }
               })
                .build();

        GitHubService service = retrofit.create(GitHubService.class);

        Call<String> call = service.listRepos();
        call.enqueue(MainActivity.this);

    }

    @Override
    public void onResponse(Call<String> call, Response<String> response) {
        Log.d("body-onResponse",response.body()+";;;;;;");
    }

    @Override
    public void onFailure(Call<String> call, Throwable t) {

        t.printStackTrace();
        Log.d("body-onFailure",call.request().url()+"============");
    }


关键的就是OkHttpClient里的 sslSocketFactory 和 hostnameVerifier设置 
然后就是 Retrofit.client(okhttpclient)使用我们自己设置的client.

SSLSocketFactoryUtils.java的实现内容:

public class SSLSocketFactoryUtils {
    /*
    * 默认信任所有的证书
    * todo 最好加上证书认证,主流App都有自己的证书
    * */
    public static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory sslSocketFactory = null;
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom());
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (Exception e) {

        }
        return sslSocketFactory;
    }


        public static X509TrustManager createTrustAllManager() {
        X509TrustManager tm = null;
        try {
         tm =   new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    //do nothing,接受任意客户端证书
                }

                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    //do nothing,接受任意服务端证书
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
        } catch (Exception e) {

        }
        return tm;
    }
    public  static  class TrustAllHostnameVerifier implements HostnameVerifier{

        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

这样就完成了. 
这个方法简单粗暴,完全不用加任何公钥文件,直接信任所有,安全性当然降低.

哪你会问,后台的cer文件没有用到啊? 
是的,接下来,介绍验证cer文件的代码:

第一步:公钥准备: 
12306网站上直接提供他们的公钥下载,下下来的文件名srca.cer拿他做错误的公钥, 
我们后台的公钥:ca.cer

将他们放入raw文件夹下. 


在上面MainActivity中唯一改动的地方就是: 


只有SSLSocketFactory的获取方式变了.

下面是SSLSocketFactoryUtils的内容:

//TODO 下面为新
    static int keyServerStroreID =R.raw.ca;

    public static SSLSocketFactory createSSLSocketFactory(Context context) {
       SSLSocketFactory mSSLSocketFactory = null;
        if(mSSLSocketFactory==null){
            synchronized (SSLSocketFactoryUtils.class) {
                if(mSSLSocketFactory==null){

                    InputStream trustStream = context.getResources().openRawResource(keyServerStroreID);
                    SSLContext sslContext;
                    try {
                        sslContext = SSLContext.getInstance("TLS");
                    } catch (NoSuchAlgorithmException e) {
                        Log.e("httpDebug","createSingleSSLSocketFactory",e);
                        return null;
                    }
                    //获得服务器端证书
                    TrustManager[] turstManager = getTurstManager(trustStream);

                    //初始化ssl证书库
                    try {
                        sslContext.init(null,turstManager,new SecureRandom());
                    } catch (KeyManagementException e) {
                        Log.e("httpDebug","createSingleSSLSocketFactory",e);
                    }

                    //获得sslSocketFactory
                    mSSLSocketFactory=sslContext.getSocketFactory();
                }
            }
        }
        return mSSLSocketFactory;
    }

    /**获得指定流中的服务器端证书库*/

    public static TrustManager[] getTurstManager(InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null,null);
            int index = 0;
            for (InputStream certificate : certificates) {
                if (certificate == null) {
                    continue;
                }
                Certificate certificate1;
                try {
                    certificate1 = certificateFactory.generateCertificate(certificate);
                }finally {
                    certificate.close();
                }

                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias,certificate1);
            }

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
                    .getDefaultAlgorithm());

            trustManagerFactory.init(keyStore);
            return trustManagerFactory.getTrustManagers();

        } catch (Exception e) {
            Log.e("httpDebug","SSLSocketFactoryUtils",e);
        }

        return getTurstAllManager();
    }

    /**
     * 获得信任所有服务器端证书库
     * */
    public static TrustManager[] getTurstAllManager() {
        return new X509TrustManager[] { new MyX509TrustManager() };
    }

    public static class MyX509TrustManager implements X509TrustManager {

        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            System.out.println("cert: " + chain[0].toString() + ", authType: " + authType);
        }

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

验证一下: 
当static int keyServerStroreID =R.raw.ca;用我们自己的证书申请的效果是:

当static int keyServerStroreID =R.raw.srca;用12306访问公司接口 


这样就达到我们的目的了.

最后.加个 GitHubService的内容吧

public interface GitHubService {
    @GET("base/v1.0/downloadPage?appId=moa")
    Call<String> listRepos();
}

MyApplication

public class MyApplication  extends Application{

    public static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context=this.getApplicationContext();

    }
}

这篇文章并没有太多https的讨论,只是简单的贴出了https在retrofit网络请求下的实现代码. 
抛砖引玉,至少没做过这方面的同学可以很快上手.

补充一下读取 cer文件信息,特别是cer文件的有效时间:

     import android.content.Context;

        import java.io.File;
        import java.io.FileInputStream;
        import java.io.InputStream;
        import java.security.cert.CertificateFactory;
        import java.security.cert.X509Certificate;
        import java.text.SimpleDateFormat;
        import java.util.Date;

        import junit.framework.TestCase;

public class CertManager extends TestCase{


    /***
     * 读取*.cer公钥证书文件, 获取公钥证书信息
     * @author xgh
     */
    public static  void testReadX509CerFile(Context context) throws Exception{


        try {
            // 读取证书文件

            File file = new File("src/GYGSCB2100000500.cer");
            InputStream inStream = new FileInputStream(file);

            // 创建X509工厂类
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            //CertificateFactory cf = CertificateFactory.getInstance("X509");
            // 创建证书对象
            X509Certificate oCert = (X509Certificate) cf
                    .generateCertificate(inStream);
            inStream.close();
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd");
            String info = null;
            // 获得证书版本
            info = String.valueOf(oCert.getVersion());
            System.out.println("证书版本:" + info);
            // 获得证书序列号
            info = oCert.getSerialNumber().toString(16);
            System.out.println("证书序列号:" + info);
            // 获得证书有效期
            Date beforedate = oCert.getNotBefore();
            info = dateformat.format(beforedate);
            System.out.println("证书生效日期:" + info);
            Date afterdate = oCert.getNotAfter();
            info = dateformat.format(afterdate);
            System.out.println("证书失效日期:" + info);
            // 获得证书主体信息
            info = oCert.getSubjectDN().getName();
            System.out.println("证书拥有者:" + info);
            // 获得证书颁发者信息
            info = oCert.getIssuerDN().getName();
            System.out.println("证书颁发者:" + info);
            // 获得证书签名算法名称
            info = oCert.getSigAlgName();
            System.out.println("证书签名算法:" + info);

        } catch (Exception e) {
            System.out.println("解析证书出错!");
            e.printStackTrace();
        }
    }

}
 


证书内容12306的确实可以用keytool 直接读取,但是我们公司的加密了,直接读取不了
————————————————
版权声明:本文为CSDN博主「丑丑鱼1992」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013768203/article/details/72874242

发布了15 篇原创文章 · 获赞 25 · 访问量 14万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章