1. 程式人生 > >Retrofit繫結證書實現HTTPS單項認證

Retrofit繫結證書實現HTTPS單項認證

客戶端內建伺服器的證書,我們在校驗服務端證書的時候只比對和App內建的證書是否完全相同,如果不同則斷開連線。那麼此時再遭遇中間人攻擊劫持我們的請求時由於黑客伺服器沒有相應的證書,此時HTTPS請求校驗不通過,則無法與黑客的伺服器建立起連線。

那麼接下來我們就結合Retrofit以訪問12306為例來實現HTTPS的單項認證。 
首先從12306網站下載簽名證書,並放置到我們專案資源目錄raw下。然後根據證書構造SSLSocketFactory,程式碼如下:

/**
     * 單項認證
     */
    public static SSLSocketFactory getSSLSocketFactoryForOneWay(InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance(CLIENT_TRUST_MANAGER, CLIENT_TRUST_PROVIDER);
            KeyStore keyStore = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            SSLContext sslContext = SSLContext.getInstance(CLIENT_AGREEMENT);

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

            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

接下來為OKHttpClient設定SslSocketFactory以及hostnameVerifier,程式碼如下

InputStream certificate12306 = Utils.getContext().getResources().openRawResource(R.raw.srca);
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(interceptor)
                .addInterceptor(new HttpHeaderInterceptor())
                .addNetworkInterceptor(new HttpCacheInterceptor())
                .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForOneWay(certificate12306))  
                .hostnameVerifier(new SafeHostnameVerifier())
                .cache(cache)
                .build();

上述程式碼中hostnameVerifier是對伺服器的校驗,SafeHostnameVerifier程式碼如下:

private class SafeHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            if (Constants.IP.equals(hostname)) {//校驗hostname是否正確,如果正確則建立連線
                return true;
            }
            return false;
        }
    }
verify方法中對比了請求的IP和伺服器的IP是否一致,一致則返回true表示校驗通過,否則返回false,檢驗不通過,斷開連線。對於網上有些處理是直接返回true,即不對請求的伺服器IP做校驗,我們不推薦這樣使用。而且現在谷歌應用商店已經對此種做法做了限制,禁止在verify方法中直接返回true的App上線。