TA的每日心情 | 开心 2021-12-13 21:45 |
---|
签到天数: 15 天 [LV.4]偶尔看看III
|
一.Https介绍
HTTP协议是没有加密的明文传输协议,如果APP采用HTTP传输数据,则会泄露传输内容,可能被中间人劫持,修改传输的内容。
HTTPS是HTTP over SSL,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL。
安全套接字层 (SSL)现在技术上称为传输层安全协议(TLS)。
SSL/TLS层负责客户端和服务器之间的加解密算法协商、密钥交换、通信连接的建立。
在典型的 HTTPS 使用场景中,会使用一个包含公钥及与其匹配的私钥的证书配置服务器。作为 SSL 客户端与服务器握手的一部分,服务器将通过使用公钥加密签署其证书来证明自己具有私钥。
不过,任何人都可以生成他们自己的证书和私钥,因此,一个简单的握手只能说明服务器知道与证书公钥匹配的私钥,除此之外什么都证明不了。解决此问题的一个方法是让客户端拥有其信任的一个或多个证书集。如果证书不在此集合中,则不会信任服务器。
但这个简单的方法有几个缺点。服务器应能够随时间的推移升级到更强的密钥(“密钥旋转”),使用新的公钥替换证书中的公钥。遗憾的是,客户端应用现在必须根据服务器配置发生的变化进行更新。如果服务器不在应用开发者的控制下(例如,如果服务器是一个第三方网络服务),则很容易出现问题。如果应用必须与网络浏览器或电子邮件应用等任意服务器通信,那么,此方法也会带来问题。
为弥补这些缺点,通常使用来自知名颁发者(证书颁发机构(CA))发放的证书配置服务器。主机平台一般包含其信任的知名 CA 的列表。从 Android 4.2 开始,Android 目前包含在每个版本中更新的 100 多个 CA。CA 具有一个证书和一个私钥,这点与服务器相似。为服务器发放证书时,CA 使用其私钥签署服务器证书。然后,客户端可以验证该服务器是否具有平台已知的 CA 发放的证书。
不过,在解决一些问题的同时,使用 CA 也会引发其他问题。因为 CA 为许多服务器发放证书,因此,您仍需要某种方式来确保您与您需要的服务器通信。为解决这个问题,CA 发放的证书通过类似 gmail.com 等具体名称或 *.Google.com 等通配型主机集识别服务器。
以下示例会让这些概念更具体。下面的代码段来自命令行,工具的命令将查看维基百科( Wikipedia) 的服务器证书信息。它指定端口 443,因为此端口是 HTTPS的默认端口。此命令将的输出发送到,后者将根据 X.509 标准 格式化与证书有关的信息。具体而言,此命令获取subject和issuer信息,分别包含服务器名称信息 和 可认证 CA 的颁发结构。如下:
- $ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
- subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
- issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
复制代码 你会看到证书是由 RapidSSL CA 为与 *.wikipedia.org 匹配的服务器发放的。
file:///C:/Users/Administrator/Documents/My%20Knowledge/temp/82419978-ffb9-4b83-9eba-7fd1073904d4/128/index_files/f6dbfd63-060b-44fa-92a7-6ce1b66ab855.png
HTTPS通信所用到的证书由CA提供,需要在服务器中进行相应的设置才能生效。另外在我们的客户端设备中,只要访问的HTTPS的网站所用的证书是知名CA根证书签发的,如果这些CA又在浏览器或者操作系统的根信任列表中,就可以直接访问。而如
http://12306.cn
网站,它的证书是非可信CA提供的,是自己签发的,所以在用谷歌浏览器打开时,会提示“您的连接不是私密连接”,证书是非可信CA颁发的:

file:///C:/Users/Administrator/Documents/My%20Knowledge/temp/82419978-ffb9-4b83-9eba-7fd1073904d4/128/index_files/27e286d4-2d8c-49c0-a048-9787987b2142.png
所以在
http://12306.cn
的网站首页会提示“
为保障您顺畅购票,请下载安装
根证书
”,操作系统安装后,就不会再有上图的提示了。
二. Https使用
1. 访问知名CA发放证书的Https服务器
- URL url = new URL("https://wikipedia.org");
- URLConnection urlConnection = url.openConnection();
- InputStream in = urlConnection.getInputStream();
- copyInputStreamToOutputStream(in, System.out);
复制代码
假设要访问一个由知名 CA 发放证书的网络服务器,那么,可以使用上述简单代码发起安全的请求,因为Android平台已经包含了该CA。
而使用私有CA签发的证书的服务器使用上述方式就无法访问。
2.访问使用未知CA或私有CA签发证书的服务器
如果要使用私有CA或未知CA签发的证书,因为Android系统还不信任该CA的证书,会出现异常javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found。
对服务器证书进行校验
必须重写校验证书链TrustManager中的方法
checkServerTrusted()
。
- private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
- TrustManager[] trustAllCerts = new TrustManager[]{
- new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
- }
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
- //校验服务器证书
- if (chain == null) {
- throw new IllegalArgumentException("Check Server X509Certificates is null");
- }
- if (chain.length < 0) {
- throw new IllegalArgumentException("Check Server X509Certificates is empty");
- }
- for (X509Certificate cert : chain) {
- cert.checkValidity();
- try {
- String certName = "abc.crt"; //一般将下载的证书放到项目中的assets目录下
- InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- X509Certificate serverCert = (X509Certificate) certificateFactory.generateCertificate(certInput);
- cert.verify(serverCert.getPublicKey());
- } catch (IOException e) {
- e.printStackTrace();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- e.printStackTrace();
- } catch (NoSuchProviderException e) {
- e.printStackTrace();
- } catch (SignatureException e) {
- e.printStackTrace();
- }
- }
- }
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
- }
- };
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, trustAllCerts, null);
- HostnameVerifier hostnameVerifier = new HostnameVerifier() {
- @Override
- public boolean verify(String hostname, SSLSession session) {
- //校验服务器证书的域名是否相符(若不校验服务器证书域名则直接return true)
- HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
- Boolean result = hv.verify("*.xxx.com", session);
- return result;
- }
- };
- URL url = new URL(https_url);
- HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
- httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
- httpsURLConnection.setHostnameVerifier(hostnameVerifier);
- // httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //信任所有主机(注意:此处SSLSocketFactory与sslContext.getSocketFactory()返回的是不同的类)
- InputStream in = httpsURLConnection.getInputStream();
- //网络返回的数据处理...
- }
复制代码
另一种实现方式
通过保存在assets路径下的证书文件获取特定的证书,用该 CA 创建
,然后用后者创建和初始化
。
TrustManager
是系统用于从服务器验证证书的工具,可以使用一个或多个 CA 从
KeyStore
创建,而创建的
TrustManager
将仅信任这些 CA。
如果是新的
TrustManager
,此示例将初始化一个新的
,后者可以提供一个
,您可以通过
用它来替换默认的
SSLSocketFactory
。这样一来,网络请求时将使用您的 CA 验证证书,
系统能够验证您的服务器证书是否来自值得信任的颁发者。
- [i] /**
- [/i][i] * 获取校验服务器端证书的SocketFactory
- [/i][i] *
- [/i][i] * [/i][b][i]@param [/i][/b][i]context
- [/i][i] [/i][i]* [/i][b][i]@param [/i][/b][i]certName [/i][i]保存在assets路径下的服务器端证书文件名,如abc.crt
- [/i][i] * [/i][b][i]@return
- [/i][/b][b][i] [/i][/b][i]*/
- [/i][i] [/i]public static javax.net.ssl.SSLSocketFactory getSocketFactory(Context context, String certName) throws IOException, CertificateException,
- KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
- InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
- //以X.509格式获取证书
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- Certificate cert = certificateFactory.generateCertificate(certInput);
- //生成一个包含服务器端证书的KeyStore
- String keyStoreType = KeyStore.getDefaultType();
- KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(null, null);
- keyStore.setCertificateEntry("cert", cert);
- //用包含服务器端证书的KeyStore生成一个TrustManager
- String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
- trustManagerFactory.init(keyStore);
- //生成一个使用我们TrustManager的SSLContext
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
- return sslContext.getSocketFactory();
- }
- public static void test(final Context context) {
- new Thread() {
- @Override
- public void run() {
- super.run();
- String https_url = "https://www.xxx.com";
- try {
- URL url = new URL(https_url);
- HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
- httpsURLConnection.setSSLSocketFactory(getSocketFactory(context, "abc.cer"));
- InputStream in = httpsURLConnection.getInputStream();
- ...
-
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (CertificateException e) {
- e.printStackTrace();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (KeyStoreException e) {
- e.printStackTrace();
- } catch (KeyManagementException e) {
- e.printStackTrace();
- }
- }
- }.start();
- }
复制代码
3.使用OKHttp访问https服务器
- private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
- X509TrustManager[] trustAllCerts = new X509TrustManager[]{
- new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
- [code] //不校验客户端证书
复制代码 }
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//不校验服务器证书
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, null);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//不校验服务器证书的域名
return true;
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(hostnameVerifier)
.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]).build();
Request request = new Request.Builder().url(https_url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}[/code]
上述方法中未校验服务器证书和域名,可以参考前面的方法进行具体校验。
4.WebView访问https服务器
webview加载H5页面,在webView.setWebViewClient(webviewClient)时重载WebViewClient的onReceivedSslError(),如果出现证书错误会回调该方法,在其中直接调用handler.proceed()会忽略错误继续加载证书有问题的页面,如果调用handler.cancel()可以终止加载证书有问题的页面,证书出现问题了,可以提示用户风险,让用户选择加载与否。
- public class MyWebViewClient extends WebViewClient {
- ...
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
- handler.proceed();
- }
- }
复制代码
参考
[1]
https://developer.android.google.cn/training/articles/security-ssl.html?hl=zh-cn#Concepts
[2]
https://developer.android.google.cn/training/articles/security-ssl.html
[3]
https://zhuanlan.zhihu.com/p/22816331
|
|