diff --git a/README.md b/README.md index 9183754..416ed72 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,33 @@ A Flutter plugin for getting user installed certificates on Android. +This is useful for working around the limitation that Dart's HttpClient doesn't +honor user installed certificates on Android. See: + +- [dart-lang/sdk#50435](https://github.com/dart-lang/sdk/issues/50435) +- [flutter/flutter#56607](https://github.com/flutter/flutter/issues/56607) + ## Getting Started -Here is an example that lists all the user installed certificates on the device. +Here is an example that trusts and lists all the user installed certificates on the device. ```dart import 'package:flutter/material.dart'; import 'dart:async'; +import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter_user_certificates_android/flutter_user_certificates_android.dart'; -void main() { +final _flutterUserCertificatesAndroidPlugin = FlutterUserCertificatesAndroid(); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Extend the default security context to trust Android user certificates. + // This is a workaround for . + await _flutterUserCertificatesAndroidPlugin.trustAndroidUserCertificates(SecurityContext.defaultContext); + runApp(const MyApp()); } @@ -27,8 +42,6 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { Map _certs = {}; String? error; - final _flutterUserCertificatesAndroidPlugin = - FlutterUserCertificatesAndroid(); @override void initState() { @@ -75,3 +88,38 @@ class _MyAppState extends State { } } ``` + +## Note about native Android HTTP + +The above example only affects HTTP calls with Dart's +[http](https://pub.dev/packages/http) library. If you (or some library you +depend on) are using `javax.net.ssl.HttpsURLConnection` or +[Cronet](https://pub.dev/packages/cronet_http), then you will also need a +`` that adds user certificates as a trust anchor. + +`android/app/src/main/AndroidManifest.xml`: + +```xml + + + ... + +``` + +`android/app/src/main/res/xml/network_security_config.xml`: + +```xml + + + + + + + + + +``` + +See + +for more details. diff --git a/example/lib/main.dart b/example/lib/main.dart index 686321e..91a7a46 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,19 @@ import 'package:flutter/material.dart'; import 'dart:async'; +import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter_user_certificates_android/flutter_user_certificates_android.dart'; -void main() { +final _flutterUserCertificatesAndroidPlugin = FlutterUserCertificatesAndroid(); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Extend the default security context to trust Android user certificates. + // This is a workaround for . + await _flutterUserCertificatesAndroidPlugin.trustAndroidUserCertificates(SecurityContext.defaultContext); + runApp(const MyApp()); } @@ -18,8 +27,6 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { Map _certs = {}; String? error; - final _flutterUserCertificatesAndroidPlugin = - FlutterUserCertificatesAndroid(); @override void initState() { diff --git a/lib/flutter_user_certificates_android.dart b/lib/flutter_user_certificates_android.dart index f4b3c39..d8ae6bf 100644 --- a/lib/flutter_user_certificates_android.dart +++ b/lib/flutter_user_certificates_android.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; import 'flutter_user_certificates_android_platform_interface.dart'; @@ -8,6 +9,26 @@ class FlutterUserCertificatesAndroid { return FlutterUserCertificatesAndroidPlatform.instance .getUserCertificates(); } + + Future trustAndroidUserCertificates(SecurityContext context) async { + // User certificates are an Android specific concept. On other platforms, + // there's nothing to do. + if(!Platform.isAndroid) { + return; + } + + final certs = await this.getUserCertificates(); + if(certs == null) { + print("No user certificates found"); + return; + } + + for(var entry in certs.entries) { + final name = entry.key; + final keyData = entry.value; + context.setTrustedCertificatesBytes(utf8.encode(keyData.toPEM())); + } + } } typedef DERCertificate = Uint8List;