使用 Android Native、Android Studio 和改造在本地网络中信任我自己的自签名证书
我正在创建一个简单的 Android 应用程序,将在封闭的本地网络中使用。在此本地网络中,正在运行 Flask 服务器,该服务器配置为通过 nginx 代理使用自签名证书。服务器的后端应用程序使用自签名证书可以正常工作,我已经使用浏览器和邮递员验证了这一点。 (显然,我必须明确要求浏览器信任我的证书)。
几天来,我一直试图在网上找到一些关于如何让我的 Android 应用程序接受我的证书的明确答案,但我尝试过的所有事情都让我走进了死胡同。有时这些解决方案已被弃用,有时又对于这样一件微不足道的事情来说太复杂了。
http请求是使用Retrofit发送的;据我了解,我必须以某种方式配置改造实例的 http 客户端以接受我的证书。
我已经设法使用接受任何证书的客户端,但这不是我想要的。理想情况下,我的证书将添加到官方 CA 默认信任的证书“集”中,以便应用程序也可以向外部资源发送请求。
那么,假设后端应用程序运行在例如 192.168.1.10:443 上,我该如何处理呢?
注意:我已阅读此处给出的说明 https://developer.android。 com/training/articles/security-config.html#TrustingAdditionalCas 并已将
android:networkSecurityConfig="@xml/network_security_config"
添加到我的清单文件中,但出现以下错误:
主机名 192.168.1.10 未验证:证书 sha256/.... ./.....
并继续列出证书的信息,如通用名称等。
I am creating a simple android app that will be used in a closed local network. In this local network, a flask server is running which is configured to use a self-signed certificate via nginx proxying. The backend application of the server works fine using the self-signed certificate, I have verified this both using my browser and postman. (Obviously, I had to explicitly ask the browser to trust my certificate).
For days, I have been trying to find some definitive answer online on how to make my android app accept my certificate, but all the things I have tried have led me to a dead end. Sometimes the solutions where deprecated, and other times just too complicated for such a trivial thing.
The http requests are sent using Retrofit; as I understand, I must somehow configure my retrofit instance's http client to accept my certificate.
I have managed to use a client that accepts any certificate, but this is not what I want. Ideally, my certificate would be added to the "set" of certificates that are trusted by default by official CAs, so that the app can possibly send requests to outside resources as well.
So, given that the backend application is running on e.g. 192.168.1.10:443, how would I go about this?
Note: I have read the instructions given here https://developer.android.com/training/articles/security-config.html#TrustingAdditionalCas
and have added
android:networkSecurityConfig="@xml/network_security_config"
to my manifest file, but I am getting the following error:
Hostname 192.168.1.10 not verified: certificate sha256/...../.....
and continues to list the information of the certificate like common name etc.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Shlomi Katriel 的答案是我已经尝试过的,但它间接引导我找到了解决方案。请记住,我能够解决这个问题的唯一原因是因为我拥有服务器的 root 访问权限,并且可以随心所欲地使用它。
这个
答案基本上就是解决整个问题的关键。我将发布所有步骤,以防其他人需要。
第 1 步
创建您的自签名证书。就我而言,我使用了 openssl 实用程序。包含 -addext 标志非常重要。这是直接取自我上面链接的答案的示例:
将 1.2.3.4 替换为服务器的本地 ip。这将在证书的 subjectAltName 属性中包含服务器的本地 IP。如果没有这个,您将不断收到错误“主机名...未验证...”
在正在运行的 nginx 实例中使用证书是一个不同的主题,可以轻松完成,并且在线有大量资源。
第 2 步 打开 android studio 并在
res
下创建一个名为raw
的新 Android 资源目录(如果您还没有)。在此目录中,复制上述命令生成的 .crt 文件的内容。第3步 在
res
下创建一个名为xml
的android资源目录。在那里,创建一个新的 xml 文件,在我的例子中,我将其命名为network_security_config.xml
。我在其中插入了以下内容:使用您在步骤 2 中复制的证书文件的文件名,而不是“证书”。
步骤 4 添加
android:networkSecurityConfig="@xml/network_security_config"
到 AndroidManifest.xml 中的应用程序元素Shlomi Katriel's answer was something I had already tried, but it led me to the solution indirectly. Please keep in mind that the only reason I was able to solve this was because I have root access to the server and can do with it as I please.
This
answer was basically the key to solving the whole thing. I will post all the steps in case someone else needs this.
Step 1
Create your self-signed certificate. In my case, I used the openssl utility. It is very important to include the -addext flag. Here is an example taken directly from the answer I linked above:
Replace 1.2.3.4 with the local ip of your server. This will include the server's local ip in the subjectAltName property of the certificate. Without this, you will keep getting the error "Hostname ... not verified ..."
Using the certificate in a running nginx instance is a different subject which can be done easily and there are plenty of resources online.
Step 2 Open android studio and create a new android resource directory (if you don't already have it) under
res
namedraw
. Inside this directory, copy the contents of the .crt file that the command above produced.Step 3 Create an android resource directory under
res
namedxml
. In there, create a new xml file which in my case I callednetwork_security_config.xml
. In there, I inserted the following:Instead of "certificate", use the filename of the certificate file you copied in step 2.
Step 4 Add
android:networkSecurityConfig="@xml/network_security_config"
to the application element in your AndroidManifest.xml最佳解决方案是
拉动字符串以使服务器所有者使用由自定义根 CA 签名的证书,然后使用
networkSecurityConfig
固定该证书。在我看来,这应该是一个阻止功能发布的要求。
另一种方法
是创建 2 个 http 客户端:
chechServerTrusted(...)
的空 imolementation) - 仅适用于预期 SSL 握手失败的特定本地网络请求。通过这样做,您将暴露此中间人攻击请求,以防攻击者进入本地网络 - 攻击者和真实服务器证书都不可信。
他们还可以模仿证书元数据,这样您就无法分辨出差异。
只有您知道您的系统是否能够承受风险(以及风险的可能性有多大)。
更多见解:
networkSecurityConfig
适用于众所周知的证书,因此您可以对公钥进行硬编码,因此我不相信它可以解决您的问题。更新
根据 Android 开发人员,您需要以下配置:
他们声称
res/raw/my_custom_cas
接受所有常见格式:Best solution
pull the strings to make the owner of the server use a certificate, which is signed by a custom root CA, then pin this certificate using
networkSecurityConfig
.In my opinion it should be a requirement that should prevent a feature from being released.
Alternative
I would create 2 http clients:
chechServerTrusted(...)
) - only for that specific local network request that expected to fail the SSL handshake.By doing so, you're exposing this request for man-in-a-middle attack in case an attacker got into the local network - both attacker and real server certificates are not trusted.
They can also mimic the certificate metadata so you won't tell the difference.
Only you know if your system can afford the risk (and how probable is the risk anyway).
More insights:
networkSecurityConfig
is meant for well known certificates so you can hard code public keys, so I don't believe it can solve your case.Update
According to Android Developers, you need the following configuration:
They claim
res/raw/my_custom_cas
accepts all common formats: