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

使用Openssl生成自簽證書

Network security configuration

HttpClient

SecurityContext

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章