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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章