Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/dart-lang/sdk/issues/50435>.
await _flutterUserCertificatesAndroidPlugin.trustAndroidUserCertificates(SecurityContext.defaultContext);

runApp(const MyApp());
}

Expand All @@ -27,8 +42,6 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> {
Map<String, Uint8List> _certs = {};
String? error;
final _flutterUserCertificatesAndroidPlugin =
FlutterUserCertificatesAndroid();

@override
void initState() {
Expand Down Expand Up @@ -75,3 +88,38 @@ class _MyAppState extends State<MyApp> {
}
}
```

## 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
`<network-security-config>` that adds user certificates as a trust anchor.

`android/app/src/main/AndroidManifest.xml`:

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<application ... android:networkSecurityConfig="@xml/network_security_config">
...
</manifest>
```

`android/app/src/main/res/xml/network_security_config.xml`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>
```

See
<https://developer.android.com/privacy-and-security/security-config#network-security-config>
for more details.
13 changes: 10 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/dart-lang/sdk/issues/50435>.
await _flutterUserCertificatesAndroidPlugin.trustAndroidUserCertificates(SecurityContext.defaultContext);

runApp(const MyApp());
}

Expand All @@ -18,8 +27,6 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> {
Map<String, Uint8List> _certs = {};
String? error;
final _flutterUserCertificatesAndroidPlugin =
FlutterUserCertificatesAndroid();

@override
void initState() {
Expand Down
21 changes: 21 additions & 0 deletions lib/flutter_user_certificates_android.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'flutter_user_certificates_android_platform_interface.dart';
Expand All @@ -8,6 +9,26 @@ class FlutterUserCertificatesAndroid {
return FlutterUserCertificatesAndroidPlatform.instance
.getUserCertificates();
}

Future<void> trustAndroidUserCertificates(SecurityContext context) async {
// User certificates are an Android specific concept. On other platforms,
// there's nothing to do.
if(!Platform.isAndroid) {
return;
}
Comment on lines +14 to +18
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


final certs = await this.getUserCertificates();
if(certs == null) {
print("No user certificates found");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print statements should be avoided, especially in packages..

return;
}

for(var entry in certs.entries) {
final name = entry.key;
final keyData = entry.value;
context.setTrustedCertificatesBytes(utf8.encode(keyData.toPEM()));
}
}
}

typedef DERCertificate = Uint8List;
Expand Down