Android WebView实现HTTPS证书校验
WebViewClient onReceivedSslError
Android中使用WebView加载html,在html中使用ajax请求Https服务,如何进行证书的安全校验呢?本文主要讲解WebView实现Https自制证书的校验。
我们使用Android的WebView的时候会设置一个WebViewClient
,而如果请求Https发生错误的时候,就会调用WebViewClient
的onReceivedSslError
方法,如下所示:
WebView webView = new WebView(getContext());
webView.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
}
});
super.onReceivedSslError(view, handler, error);
默认继承父类的实现,父类的实现是handler.cancel();
取消通信。所以我们要做的就是在onReceivedSslError
方法中进行证书校验。
onReceivedSslError方法参数分析
WebView view
参数
即当前的webview对象
SslErrorHandler handler
参数
点进源码可以看到,就一个构造方法和两个类方法。proceed()
方法是允许所有网络访问,cancel()
方法是取消所有网络访问。
public class SslErrorHandler extends Handler {
/**
* @hide Only for use by WebViewProvider implementations.
*/
@SystemApi
public SslErrorHandler() {}
/**
* Proceed with the SSL certificate.
*/
public void proceed() {}
/**
* Cancel this request and all pending requests for the WebView that had
* the error.
*/
public void cancel() {}
}
- SslError error参数
点进SslError源码可以看到这个类的属性和方法,主要属性有:
SSL_NOTYETVALID
:证书不合法
SSL_EXPIRED
:证书超出有效期
SSL_IDMISMATCH
:域名不匹配
SSL_UNTRUSTED
:不受信的证书
SSL_DATE_INVALID
:证书日期无效
SSL_INVALID
:一般性错误
除了几个构造方法外,主要方法有:
getUrl()
:获取当前请求的url
getPrimaryError()
:获取错误类型
getCertificate()
:获取当前证书
证书的sha256值校验
判断逻辑是:
1、获取当前webview的证书的sha256值
2、获取客户端证书的sha256值
3、对比两个证书的sha256值,如果相等,则调用handler.proceed()
方法,如果不相等,则调用弹出提示框,并退出应用程序。
WebView webView = new WebView(getContext());
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
String certSha256 = SSLSocketCert.getSSLCertSHA256FromCert(view.getContext().getAssets().open("client.crt"));
String serverSha256 = SSLSocketCert.getSSLCertFromServer(error.getCertificate());
if (certSha256.equalsIgnoreCase(serverSha256)) {
handler.proceed();
} else {
DialogUtil.showSingleDialog(view.getContext(), "警告", "证书校验失败", false, "退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
});
}
}
});
SSLSocketCert.java
import android.net.http.SslCertificate;
import android.os.Bundle;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
public class SSLSocketCert {
//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory(InputStream cerIn) {
try {
SSLSocketFactory factory = setCertificates(cerIn);
return factory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 载入证书
*/
private static SSLSocketFactory setCertificates(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
if (certificate != null) {
certificate.close();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
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;
}
}
//获取HostnameVerifier
public static HostnameVerifier getHostnameVerifier(final JSONArray trustHosts) {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String host, SSLSession sslSession) {
if (trustHosts == null || trustHosts.length() == 0) {
return false;
}
try {
for (int i = 0; i < trustHosts.length(); i++) {
String trustHost = trustHosts.getString(i);
if (host.equalsIgnoreCase(trustHost)) {
return true;
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
};
return hostnameVerifier;
}
/**
* SSL证书错误,手动校验https证书
*
* @param cert https证书
* @return true通过,false失败
*/
public static String getSSLCertFromServer(SslCertificate cert) {
Bundle bundle = SslCertificate.saveState(cert);
if (bundle != null) {
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes != null) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(new ByteArrayInputStream(bytes));
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(ca.getEncoded());
return bytesToHex(key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
public static String getSSLCertSHA256FromCert(InputStream cerIn) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(cerIn);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(ca.getEncoded());
String sha256Str = bytesToHex(key);
return sha256Str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 十六进制转字节数组
*
* @param hex
* @return
*/
public static byte[] hexToBinary(String hex) {
String string = "0123456789ABCDEF";
char[] ch = hex.toCharArray();
int len = ch.length / 2;
byte byts[] = new byte[len];
for (int i = 0; i < len; i++) {
byts[i] = (byte) ((((byte) string.indexOf(Character
.toUpperCase(ch[i * 2])) & 0xFF) << 4)
| ((byte) string.indexOf(Character.toUpperCase(ch[i * 2 + 1])) & 0xFF));
}
return byts;
}
/**
* 字节数组转十六进制字符串
*
* @param bytes
* @return
*/
public static String bytesToHex(byte[] bytes) {
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/25/implement-https-certificate-verification-in-android-webview/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论