我在Android中实现了一个加载https网站的WebView。在这个网站上我想做证书固定,这意味着我想检查服务器服务的证书的某些方面。但是我发现WebViewClient中没有方法可以拦截请求并检索证书。
在互联网上有很多人说它无法完成,证书固定在Androids WebView上。所以我希望这里有人知道更多。
答案 0 :(得分:6)
this文章对Android上的证书固定进行了很好的描述。
由于Network Security Configuration允许应用程序定义自己的规则集,因此Android在证书固定方面的主要里程碑是Android 7.0牛轧糖(SDK 24)。
我建议使用TrustKit Android library来管理Android上的证书固定:它支持大多数库(OkHttp等),并且与Android SDK 24设置兼容。
但是请记住,在Android 7.0之前,无论您使用什么库,都无法真正管理固定在Webview上,遵循以下所有方法都是解决方法。
因此,如果您高于7.0,则只需使用上面的库,就无需执行其他任何操作。
牛轧糖预配Webview的最佳选择是实现自己的WebviewClient
并手动执行调用,并使用shouldInterceptRequest()
方法进行证书固定。
您可以在this project中找到一个示例。
shouldInterceptRequest()
之前的Lollipop(5.0-SDK 21)除了URL,没有标题,没有正文之外,没有提供任何其他功能。这意味着您只能加载URL并希望webapp不需要其他任何东西(标题等),您实际上无法做更多的事情。
自棒棒糖以来,您就有另一种方法为您提供了一个WebResourceRequest
对象以及更多信息,因此您可以正确地重现请求。
无论Android版本是shouldInterceptRequest()
,将永远不会收到anything but GET requests; POST / PUT / DELETE不可拦截!
此外,如果您将以上方法与shouldInterceptRequest()
一起使用,则可能会引入major performance issues:before October 2015 Android Webview出现了一个错误,并始终在同一线程上执行该方法,从而导致阻塞队列,与javascript使用的方法相同,导致明显的"freeze" of the webview。从Lollipop开始,Webview随系统一起升级,但在此之前问题仍然存在。
您可以注入自定义Javascript接口来拦截Ajax调用,并使用OkHttp或用于HTTP请求和证书固定的任何库手动执行它们。
如果您确实想走这条路线have a look at this library,则它不支持表单,仅支持Ajax调用,但是可以通过类似的方式完成,如this other library所示。
但是!!!我完全不会推荐这个!!!!作者本人(我引用)说:
[..]到目前为止,我对自己的适应感到不满意。[...]
因为这是一个丑陋的黑客!
您可以考虑预先获取资源,以便在shouldInterceptRequest()
方法中请求资源时不必获取它们。这要求您已经知道所需的所有资源,而且我不必告诉您它可能最终会占用大量用户永远不会使用的带宽和磁盘空间。
shouldInterceptRequest()
的替代项如果您可以控制提供Webview数据的服务器,则可以尝试依靠HPKP,这基本上意味着您的服务器应在每次请求时返回标有证书SHA256摘要的标头:浏览器(在这种情况下为webview)应该被选中,并确保仅当您的证书相同时,下一个请求才会通过。
当然,这意味着您第一次没有保护,因此您可能需要结合使用shouldInterceptRequest()
和HPKP。
如果您在设置HPKP时犯了一些错误,也很容易使自己闭上脚(如我在链接中所述)。
您猜怎么着,由于设置困难和错误带来的风险,谷歌率先引入了HPKP deprecated it in 2017。这也意味着最近的Webview或将来可能不支持它。
如果您确实想安全地固定证书,请避免使用Web视图或将目标对象设置为minSDK 24(Android 7.0)。
也考虑一下...
代理:如果您进行证书固定,则忘记您的应用通过这些应用工作。通过代理建立HTTPS连接的唯一方法是在设备上安装代理证书,并使代理成为“中间人”。如果您固定不起作用的证书,无论如何。
重定向:如果您固定证书,并且具有更改域的HTTP重定向(301/302),则还需要固定另一个域。
动态内容:如果您的应用管理动态内容(由用户提供或不受您控制的网址输入),并且您不允许固定域列表之外的任何内容/证书无效,那么您将无法使用仍然可以将其固定(重定向也是如此)。
没有针对网络的固定,全世界的计算机浏览器都不使用证书固定。 Google本身已弃用HPKP(如上所述),到目前为止,没有人抱怨它。每个浏览器都使用受信任的证书颁发机构列表。
移动设备完成相同的操作。
浏览器和移动应用程序之间的主要区别在于,浏览器将清楚地显示证书信息,以及它是否安全,而应用程序可以执行任何所需的操作而不会向用户显示任何内容。您可以将PC应用程序与App进行比较,以获得更公平的比较。
您需要了解为应用程序的安全性真正固定证书的作用:不可能创建ManInTheMiddle攻击。即使是受感染的设备(用户安装的恶意证书颁发机构)也不允许建立通信,因此有人会嗅探您的应用程序正在发送和接收的内容。
可信任的证书颁发机构在历史上唯一一次遭到入侵的时间是2011年的DigiNotar。除了这种情况之外,打破信任链的唯一方法是使用受感染的设备(例如PC或PC)。手机)。
您需要保护用户免受自身伤害吗? (又名=在他的设备上安装伪造/恶意证书颁发机构),然后使用您的应用程序,并让MITM窃取他自己的数据和凭据。
您是否需要保护自己,防止用户窃取通过安全连接发送的数据? (api密钥,令牌等...)-请记住,有更好的策略来处理此问题,通常可以撤消api密钥和令牌。
可能您只需要保护应用程序使用的一些API。 OWASP suggest,当然总会固定住,但我的想法更多是,您应该对可能给您带来的威胁进行保护。 这完全是我的看法。
OWASP还说另一项重要的事情是不要手动做,您可能会创建比您要解决的威胁更大的安全威胁!(与上面讨论的所有变通办法背道而驰)。我100%同意这一点。
答案 1 :(得分:0)
我遇到了同样的问题,经过大量研究后我得出了以下解决方案。
1)证书应包含在SSLSocketFactory对象中。
2)检查连接的异步任务
private class SSLPinningTask extends AsyncTask<String, Void, Boolean> {
private String url;
protected Boolean doInBackground(String... urls) {
String result = null;
HttpURLConnection urlConnection = null;
url = urls[0];
try {
URL requestedUrl = new URL(url);
urlConnection = (HttpURLConnection) requestedUrl.openConnection();
if (urlConnection instanceof HttpsURLConnection) {
((HttpsURLConnection) urlConnection)
.setSSLSocketFactory(SSLInfo.getSSLSocketFactory());
}
//Depends on the request.
urlConnection.setRequestMethod("POST");
OutputStream os = urlConnection.getOutputStream();
} catch (Exception ex) {
//If the certificate is not listed then an exception
result = ex.toString();
return false;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return true;
}
protected void onPostExecute(Boolean certificateIncluded) {
if (certificateIncluded) {
//Code to handle if the certificate is included. For example load WebView
WebView.loadUrl(url);
} else
//Code to handle the case that the certificate is not included. Stop loading, redirect to an other page.
}
}
答案 2 :(得分:0)
在API级别24(Android 7)及更高版本上,您可以使用network security config在应用程序进程级别(包括WebViews)上固定证书。
上面链接中的示例。添加到AndroidManifest.xml
:
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
创建资源文件xml/network_security_config.xml
:
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>