分类 默认分类 下的文章

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

Gtk 实现图片圆角和圆化的方法

代码:

#!/usr/bin/env python3
# _*_ coding: utf-8 _*_
#
#
# @File : test_round_image.py
# @Time : 2021-12-10 22:02 
# Copyright (C) 2021 WeiKeting<weikting@gmail.com>. All rights reserved.
# @Description :
#
#
import math

import gi

gi.require_version('GdkPixbuf', '2.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GdkPixbuf, Gtk, Gdk


def rounded_image(fn, size=None, corner_radius=0):
    import cairo
    if size is not None and size[0] > 0 and size[1] > 0:
        piu = GdkPixbuf.Pixbuf.new_from_file_at_scale(fn, size[0], size[1], True)
    else:
        piu = GdkPixbuf.Pixbuf.new_from_file(fn)
    w = piu.get_width()
    h = piu.get_height()
    if corner_radius > 0:
        # 圆角
        r = min(corner_radius, w / 2., h / 2)
        surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
        ctx = cairo.Context(surface)

        Gdk.cairo_set_source_pixbuf(ctx, piu, 0, 0)

        # top left 圆角
        ctx.arc(r, r, r, -math.pi, -math.pi / 2.)
        # top line
        ctx.line_to(w - r, 0)

        # top right 圆角
        ctx.arc(w - r, r, r, -math.pi / 2., 0)
        # right line
        ctx.line_to(w, -r)

        # bottom right 圆角
        ctx.arc(w - r, h - r, r, 0, math.pi / 2.)
        # bottom line
        ctx.line_to(r, h)

        # bottom left 圆角
        ctx.arc(r, h - r, r, math.pi / 2., math.pi)
        # 连接当前path的起点和重点,即left line
        ctx.close_path()

        # 创建clip(可绘制区域)
        ctx.clip()

        ctx.paint()
        piu = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)
    else:
        # 圆边
        sw, sh = (min(w, h),)*2
        surface = cairo.ImageSurface(cairo.Format.ARGB32, sw, sh)
        ctx = cairo.Context(surface)
        # 把原图的pixbuf,居中显示
        Gdk.cairo_set_source_pixbuf(ctx, piu, -(w - sh) / 2.0, -(h - sh) / 2.0)
        # 圆形path
        ctx.arc(sw / 2.0, sh / 2., sh / 2.0, 0, 2 * math.pi)
        # 以当前path(圆形),创建clip(可绘制区域)
        ctx.clip()
        # 把source(pixbuf)画到clip中
        ctx.paint()
        piu = Gdk.pixbuf_get_from_surface(surface, 0, 0, sw, sh)
    return piu


def main():
    import sys
    f = sys.argv[1]
    pb = rounded_image(f, size=None, corner_radius=0)
    win = Gtk.Window()
    image = Gtk.Image()
    image.set_from_pixbuf(pb)
    win.add(image)
    win.set_default_size(300, 400)
    win.show_all()
    win.present()
    win.connect("delete-event", Gtk.main_quit)
    Gtk.main()


if __name__ == '__main__':
    main()

代码虽然是python写的,但是看一眼应该就可以写出C语言的版本。

测试环境: Ubuntu 21.10
不过我相信支持python3和Gtk3的环境应该都能跑起来