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裡的 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 直接讀取,但是我們公司的加密了,直接讀取不了