1. 程式人生 > >retrofit遇上https自簽名證書

retrofit遇上https自簽名證書

最近來了家新公司,後臺設計在非線上環境用自簽名證書,線上環境用CA證書,然後發了份.cer公鑰給我.讓我在客戶端處理一下.

我查了很多部落格,隻言片語的,
HTTPS的流程也比較長,
今天除錯好了,貼出連續的程式碼給大家看一下.

https有2種情況 單向驗證雙向驗證
單向認證:客戶端通過直接讀取後臺給的公鑰驗證握手
比如直接讀取cer檔案或者直接把公鑰寫在程式碼裡.
雙向認證:客戶的有公鑰,後臺也有公鑰,互相儲存對方的公鑰,驗證網路通訊,
這個時候Android端要生成bks.

有很多部落格一上來就生成bks,一定要知道什麼場景.

單向驗證方法一:簡單粗暴,直接信任所有.

在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裡的 sslSocketFactoryhostnameVerifier設定
然後就是 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 直接讀取,但是我們公司的加密了,直接讀取不了