最近公司项目需要,网络协议支持HTTPS,之前接触不多,所以这次想总结一下https在Android开发中的相关内容
一、HTTPS 证书
对于HTTPS和证书的概念,大家可以自行搜索百度。
证书分两种:
1、花钱向认证机构购买的证书。服务器如果使用了此类证书的话,那对于移动端来说,直接可以忽略此证书,直接用https访问。与之不同的是ios内置了很多信任的证书,所以他们不需要做任何操作
2、另一种是自己制作的证书,使用此类证书的话是不受信任的,也不需要花钱,所以需要我们在代码中将此类证书设置为信任证书
二、如何忽略证书
注意,下面的操作在线上环境强烈不建议采纳,只能是在测试环境中解决测试问题的时候才能使用。
1、服务器如果加上了证书的话,那么你们的网络请求的url将从http:xx改成https:xx,如果你直接也将http改成https的话而什么也不做的话,客户端将直接报错,如图:
意思就是没有找到本地证书,那就开始构建一个SSL来信任所有的证书,忽略证书其实就是如此。
2、新建一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SSLSocketClient { //获取这个SSLSocketFactory public static SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, getTrustManager(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { throw new RuntimeException(e); } } //获取TrustManager private static TrustManager[] getTrustManager() { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; return trustAllCerts; } } |
通过这个类我们可以获得SSLSocketFactory
,这个东西就是用来管理证书和信任证书的。
但是有一点需要注意:
然后我们在okhttp
中设置SSLSocketFactory
,如图:
运行之后,发现还是会报错,如图:
意思是我们的请求证书和服务器的证书不一致,这是因为我们还需要配置一个HostnameVerifier来忽略host验证
三、在SSLSocketClient的类中再加入一个方法:
1 2 3 4 5 6 7 8 9 10 |
//获取HostnameVerifier public static HostnameVerifier getHostnameVerifier() { HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }; return hostnameVerifier; } |
然后再okhttp中配置一下 HostnameVerifier:
在运行,可以正常访问了。
四、整体配置
copy整个工具类到你的项目中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import java.security.SecureRandom; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SSLSocketClient { //获取这个SSLSocketFactory public static SSLSocketFactory getSSLSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, getTrustManager(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { throw new RuntimeException(e); } } //获取TrustManager private static TrustManager[] getTrustManager() { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; return trustAllCerts; } //获取HostnameVerifier public static HostnameVerifier getHostnameVerifier() { HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }; return hostnameVerifier; } } |
然后再okhttp中配置
1 2 3 4 5 6 7 8 9 |
mHttpClient = new OkHttpClient().newBuilder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .addInterceptor(new LogInterceptor()) .addInterceptor(new TokenInterceptor()) .sslSocketFactory(SSLSocketClient.getSSLSocketFactory())//配置 .hostnameVerifier(SSLSocketClient.getHostnameVerifier())//配置 .build(); |
如果你用的是retrofit,在retrofit中配置一下okhttp即可
1 2 3 4 5 6 7 |
retrofitAPI = new Retrofit.Builder() .baseUrl(AppConfig.baseUrl) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(mHttpClient)//配置okhttp .build() .create(RetrofitAPI.class); |
这样你就可以忽略https证书正常访问你的网络了。
注意,上述的操作在线上环境强烈不建议采纳,只能是在测试环境中解决测试问题的时候才能使用。
如果我们不需要忽略HTTPS证书,而是在HTTPS证书的验证过程中增加检查项,则参考如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import androidx.annotation.NonNull; import java.security.KeyStore; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import okhttp3.internal.tls.OkHostnameVerifier; public class SSLSocketClient { //获取这个SSLSocketFactory public static SSLSocketFactory getSSLSocketFactory() { try { final SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, getTrustManager(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { throw new RuntimeException(e); } } //获取TrustManager private static TrustManager[] getTrustManager() throws Exception { final TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init((KeyStore) null); final TrustManager[] managers = factory.getTrustManagers(); final X509TrustManager trustManager = (X509TrustManager) managers[0]; return new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } @Override public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } } }; } //获取HostnameVerifier public static HostnameVerifier getHostnameVerifier() { return new CustomHostnameVerifier(); } private static class CustomHostnameVerifier implements HostnameVerifier { // implementation 'com.squareup.okhttp3:okhttp:${version}' @NonNull private final OkHostnameVerifier okHostnameVerifier = OkHostnameVerifier.INSTANCE; @Override public boolean verify(String hostname, SSLSession session) { return okHostnameVerifier.verify(hostname, session); } } } |
如果我们需要防范HTTPS中间人攻击,在应用内部携带一个根证书,在系统证书的验证完成后,增加一个根证书的校验过程,则参考如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
import android.support.annotation.NonNull; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.regex.Pattern; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import okhttp3.internal.tls.OkHostnameVerifier; public class SSLSocketClient { private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)"); private static String caFile = "ca.cer"; private static String caPassword = "password"; //获取这个SSLSocketFactory public static SSLSocketFactory getSSLSocketFactory() { try { final SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, getTrustManager(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception e) { throw new RuntimeException(e); } } //获取TrustManager private static TrustManager[] getTrustManager() throws Exception { final TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init((KeyStore) null); final TrustManager[] managers = factory.getTrustManagers(); final X509TrustManager trustManager = (X509TrustManager) managers[0]; return new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); KeyStore ks; try { ks = KeyStore.getInstance(KeyStore.getDefaultType()); } catch (KeyStoreException keyStoreException) { throw new CertificateException("root keystore init failed"); } if (null == ks) { throw new CertificateException("root keystore load failed"); } try (InputStream in = new FileInputStream(caFile)) { ks.load(in, caPassword.toCharArray()); } catch (Exception e) { throw new CertificateException("root keystore file load failed"); } boolean isValidRootCA = checkX509CertificateRootCA(ks, chain); if (!isValidRootCA) { throw new CertificateException("check root CA failed"); } } @Override public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } } }; } /** * 校验根证书 * * @param trustStore * @param x509Certificates * @return */ public static boolean checkX509CertificateRootCA(@NonNull final KeyStore trustStore, @NonNull final X509Certificate[] x509Certificates) { boolean trusted = false; try { int size = x509Certificates.length; trusted = trustStore.getCertificateAlias(x509Certificates[size - 1]) != null; } catch (KeyStoreException e) { e.printStackTrace(); } return trusted; } //获取HostnameVerifier public static HostnameVerifier getHostnameVerifier() { return new CustomHostnameVerifier(); } private static class CustomHostnameVerifier implements HostnameVerifier { // implementation 'com.squareup.okhttp3:okhttp:${version}' @NonNull private final OkHostnameVerifier okHostnameVerifier = OkHostnameVerifier.INSTANCE; @Override public boolean verify(String hostname, SSLSession session) { return okHostnameVerifier.verify(hostname, session); } } } |
但是,实际上,上述代码都不够完美,都会被Android Studio在编译的时候警告:
1 |
Implementing a custom X509TrustManager is error-prone and likely to be insecure. It is likely to disable certificate validation altogether, and is non-trivial to implement correctly without calling Android's default implementation. |
1 |
verify always returns true, which could cause insecure network traffic due to trusting TLS/SSL server certificates for wrong hostnames |
那么更加合规的做法是怎么操作呢?
其实,需要在 TrustManager 中执行的操作,完全可以在 HostnameVerifier 中更加方便的实现。
不妨参考如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import android.util.Log; import androidx.annotation.NonNull; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import okhttp3.internal.tls.OkHostnameVerifier; public class CustomHostnameVerifier implements HostnameVerifier { @NonNull private final static String TAG = "CustomHostnameVerifier"; @NonNull private final static String caFile = "ca.cer"; @NonNull private final static String caPassword = "password"; // implementation 'com.squareup.okhttp3:okhttp:${version}' @NonNull private final OkHostnameVerifier okHostnameVerifier = OkHostnameVerifier.INSTANCE; /** * 校验根证书 * * @param store keyStore * @param certs 证书链 * @return 验证成功返回 true 否则返回false */ private boolean checkCertificateRootCA(@NonNull final KeyStore store, @NonNull final Certificate[] certs) { boolean trusted = false; try { final int size = certs.length; final Certificate cert = certs[size - 1]; trusted = null != store.getCertificateAlias(cert); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); } return trusted; } @Override public boolean verify(@NonNull final String hostname, @NonNull final SSLSession session) { // 首先要求系统帮我们进行基本验证 final boolean pass = okHostnameVerifier.verify(hostname, session); if (pass) { try { final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); try (final InputStream in = new FileInputStream(caFile)) { ks.load(in, caPassword.toCharArray()); } // 如果不仅需要校验根证书,还需要校验证书颁发机构信息 // 则可以从 session.getPeerPrincipal() 中获取到与 TrustManager 相同颁发机构信息 // 从而完成证书颁发机构信息的判断 return checkCertificateRootCA(ks, session.getPeerCertificates()); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); } } return false; } } |