Android WebView实现Https证书校验

WebViewClient onReceivedSslError

Android中使用WebView加载html,在html中使用ajax请求Https服务,如何进行证书的安全校验呢?本文主要讲解WebView实现Https自制证书的校验。

我们使用Android的WebView的时候会设置一个WebViewClient,而如果请求Https发生错误的时候,就会调用WebViewClientonReceivedSslError方法,如下所示:

1
2
3
4
5
6
7
8
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()方法是取消所有网络访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()方法,如果不相等,则调用弹出提示框,并退出应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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);
}
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :