Android App 自带自签名证书CA
Android App 自带自签名证书CA
我们都知道自签名https证书默认是不被系统信任的。
所以访问使用自签名证书的网站,默认一般都是终止访问,提示错误。
面对自签名证书导致的错误提示,我们可以:
1、无视继续;
2、用户手动导入自签名证书CA(Certificate Authority)进系统并信任证书;
3、应用自带自签名证书;
本文主要说一下Android App自带自签名证书CA一些做法。
常规App
这里常规App是指使用Android SDK开发,或者基于Android SDK API的框架开发的App。
网络安全配置文件
在Manifest.xml的application添加android:networkSecurityConfig,如:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
...
>
...
</application>
</manifest>
添加文件res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="@raw/extra_cas"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
自签证书的CA,就写进文件res/raw/extra_cas(可以放多个):
-----BEGIN CERTIFICATE-----
Content Of CA1 .....
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Content Of CA2 .....
-----END CERTIFICATE-----
.....
适用范围
目前网络安全配置文件,仅适用Android 7.0+。
测试有效的API:WebView,URLConnection,okhttp
无效的API:Flutter HttpClient
Flutter
网络安全配置文件对Flutter开发的App是无效的。需要另想办法。
纯fluttr项目
在项目源码目录下,创建assets目录,然后CA保存到文件asset/extra_cas。
然后添加代码如下:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static Object ca = "";
@override
Widget build(BuildContext context) {
if (ca == "")ca = setupRootCA();
......
}
static Object setupRootCA (){
var ft = rootBundle.load('assets/extra_cas');
ft.then((ByteData value) {
SecurityContext.defaultContext.setTrustedCertificatesBytes(value.buffer.asUint8List());
return null;
});
return Object();
}
......
}
原先使用HttpClient的代码,无需任何修改就可以支持使用自签名证书的https链接了。
混合项目
原生代码和flutter混合的项目,如果不介意在apk放两个相同CAs文件,
其实很简单的(flutter和原生用不同文件)。
那么如果我们想项目就放一份CAs文件呢?假设就用res/raw/extra_cas这个文件,还是有办法的。
具体做方法:
原生侧
网络安全配置还是照样搞
在Flutter入口Activity,添加代码:
public class MainActivity extends FlutterActivity {
......
//读取CAs文件的内容
protected byte[] getTrustedCA(){
InputStream in = getResources().openRawResource(R.raw.extra_cas);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int n = 0;
while (true){
try {
if ((n = in.read(buf))<=0) break;
bao.write(buf,0,n);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return bao.toByteArray();
}
//对接Flutter的调用
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "flutter.app.share")
.setMethodCallHandler(
(call, result) -> {
if (call.method.contentEquals("getTrustedCA")) {
result.success(getTrustedCA());
}
}
);
}
......
}
Flutter侧
修改之前的方法setupRootCA:
static Object setupRootCA ()
{
Future ft = const MethodChannel('flutter.app.share').invokeMethod('getTrustedCA');
ft.then((value) {
Uint8List ca = value;
SecurityContext.defaultContext.setTrustedCertificatesBytes(ca);
return null;
});
return Object();
}
恶心的flutter
https的证书一般都给出自己有效的域名和IP。创建证书时一般域名和IP存不同字段名称的。
但是flutter似乎不验证IP字段的,所以使用IP访问会出现hostname不匹配的问题。
这事搞得我曾经怀疑人生,IP明明已经列进证书,Androd原生代码、PC浏览器、curl都没问题。
flutter就是不行,最后通过把IP列进域名字段就正常了。不按套路出牌,也不说明,真是累死人。
参考链接
Create your own Certificate Authority (CA) using OpenSSL