From 1bd8b0b3993595f625ba6000aef63f4ef7e98add Mon Sep 17 00:00:00 2001 From: aruh Date: Sun, 10 Oct 2021 14:43:36 +0200 Subject: [PATCH 1/8] Don't allow badCertificates by default, add certificate configuration --- example/pubspec.lock | 14 ++++----- lib/http_proxy_override.dart | 57 ++++++++++++++++++++++++++++++------ pubspec.lock | 8 ++--- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 10f06e5..f990993 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -14,7 +14,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.8.1" boolean_selector: dependency: transitive description: @@ -35,7 +35,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.2" flutter: dependency: "direct main" description: flutter @@ -123,7 +123,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -144,7 +144,7 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.1" + version: "4.2.3" sky_engine: dependency: transitive description: flutter @@ -198,7 +198,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.2" typed_data: dependency: transitive description: @@ -219,7 +219,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "6.2.0" + version: "7.1.1" webdriver: dependency: transitive description: diff --git a/lib/http_proxy_override.dart b/lib/http_proxy_override.dart index 6a65ea0..c9a4edf 100644 --- a/lib/http_proxy_override.dart +++ b/lib/http_proxy_override.dart @@ -14,22 +14,44 @@ Future _getProxyPort() async { } class HttpProxyOverride extends HttpOverrides { - late final String? host; - late final String? port; + final String? host; + final String? port; + final bool ignoreBadCertificates; + final List? trustedCertificates; - HttpProxyOverride._(this.host, this.port); + HttpProxyOverride._(this.host, this.port, this.ignoreBadCertificates, + this.trustedCertificates); - static Future createHttpProxy() async { - return HttpProxyOverride._(await _getProxyHost(), await _getProxyPort()); + static Future createHttpProxy( + {bool ignoreBadCertificates = false, + List? trustedCertificates}) async { + return HttpProxyOverride._(await _getProxyHost(), await _getProxyPort(), + ignoreBadCertificates, trustedCertificates); } @override HttpClient createHttpClient(SecurityContext? context) { + if (trustedCertificates != null && + trustedCertificates?.isNotEmpty == true) { + if (context == null) { + context = SecurityContext.defaultContext; + } + + trustedCertificates?.forEach((Certificate cert) { + context?.setTrustedCertificatesBytes(cert.certificateBytes, + password: cert.password); + }); + } + var client = super.createHttpClient(context); - client.badCertificateCallback = - (X509Certificate cert, String host, int port) { - return true; - }; + + if (ignoreBadCertificates) { + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { + return true; + }; + } + return client; } @@ -54,3 +76,20 @@ class HttpProxyOverride extends HttpOverrides { return super.findProxyFromEnvironment(url, environment); } } + +class Certificate { + /// The bytes of a PEM or PKCS12 file containing X509 certificates. + /// + /// Example: + /// ``` + /// Certificate( + /// await new File('some-certificate.pem').readAsBytes() + /// ); + /// ``` + final List certificateBytes; + + /// An optional password required to read the contents of [certificateBytes]. + final String? password; + + Certificate(this.certificateBytes, {this.password}); +} diff --git a/pubspec.lock b/pubspec.lock index 4ed2c66..4935c14 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.8.1" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -73,7 +73,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -127,7 +127,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.2" typed_data: dependency: transitive description: From b9a2923e76341dae529b06c378ccefebdc1fac54 Mon Sep 17 00:00:00 2001 From: aruh Date: Sat, 16 Oct 2021 16:59:19 +0200 Subject: [PATCH 2/8] Extract Certificate class into separate file and improve docs --- .../http_proxy_override_test.dart | 4 +- example/lib/main.dart | 2 +- lib/certificate.dart | 21 ++++++++ lib/http_proxy_override.dart | 54 ++++++++++++------- 4 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 lib/certificate.dart diff --git a/example/integration_test/http_proxy_override_test.dart b/example/integration_test/http_proxy_override_test.dart index 5773f47..429addc 100644 --- a/example/integration_test/http_proxy_override_test.dart +++ b/example/integration_test/http_proxy_override_test.dart @@ -24,7 +24,7 @@ void main() { "Should get null proxy host, proxy port when http proxy not set", (WidgetTester tester) async { HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.createHttpProxy(); + await HttpProxyOverride.create(); HttpOverrides.global = httpProxyOverride; await tester.pumpWidget(app.MyApp( @@ -47,7 +47,7 @@ void main() { ); HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.createHttpProxy(); + await HttpProxyOverride.create(); HttpOverrides.global = httpProxyOverride; await tester.pumpWidget(app.MyApp( diff --git a/example/lib/main.dart b/example/lib/main.dart index 1402626..72f2b8c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,7 +6,7 @@ import 'package:http_proxy_override/http_proxy_override.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.createHttpProxy(); + await HttpProxyOverride.create(); HttpOverrides.global = httpProxyOverride; runApp(MyApp( httpProxyOverride.host, diff --git a/lib/certificate.dart b/lib/certificate.dart new file mode 100644 index 0000000..7fdc440 --- /dev/null +++ b/lib/certificate.dart @@ -0,0 +1,21 @@ +import 'dart:io'; + +/// A PEM or PKCS12 file containting X509 certificates that might be password +/// protected. +class Certificate { + /// The bytes of a PEM or PKCS12 file containing X509 certificates. + /// + /// Example: + /// ```dart + /// Certificate( + /// await new File('some-certificate.pem').readAsBytes() + /// ); + /// ``` + final List certificateBytes; + + /// An optional password required to read the contents of [certificateBytes]. + /// The password is passed to [SecurityContext.setTrustedCertificatesBytes]. + final String? password; + + Certificate(this.certificateBytes, {this.password}); +} diff --git a/lib/http_proxy_override.dart b/lib/http_proxy_override.dart index c9a4edf..3d046d1 100644 --- a/lib/http_proxy_override.dart +++ b/lib/http_proxy_override.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:flutter/services.dart'; +import 'certificate.dart'; + MethodChannel _channel = MethodChannel('com.littlegnal.http_proxy_override'); Future _getProxyHost() async { @@ -14,15 +16,48 @@ Future _getProxyPort() async { } class HttpProxyOverride extends HttpOverrides { + + /// The host part of the proxy address. final String? host; + /// The port part of the proxy address. final String? port; + + /// Configures whether a secure connection to a host should be allowed with + /// a server certificate that cannot be authenticated by any of the trusted + /// root certificates. final bool ignoreBadCertificates; + + /// Certificates added to the set of trusted X509 certificates used by + /// [SecureSocket] client connections. Servers configured with these + /// certificates will be trusted and HTTPS connections to the servers will be + /// allowed. final List? trustedCertificates; HttpProxyOverride._(this.host, this.port, this.ignoreBadCertificates, this.trustedCertificates); - static Future createHttpProxy( + /// Create an instance of [HttpProxyOverride]. + /// + /// Reads the configured proxy host and port from the underlying platform + /// and configures the [HttpClient] to use the proxy. The proxy settings are + /// read once at creation time. + /// + /// [ignoreBadCertificates] configures whether a secure connection to a host + /// should be allowed with a server certificate that cannot be authenticated + /// by any of our trusted root certificates. + /// For example, this can be useful when using debugging proxies like Charles + /// or mitmproxy during development. + /// **Do not enable this in production unless you are 100% sure.** Setting + /// this enables MITM attacks. + /// Default: `false`. + /// + /// [trustedCertificates] is an optional list of [Certificate]s that will be + /// added to the set of trusted X509 certificates used by [SecureSocket] + /// client connections. Servers configured with these certificates will be + /// trusted and HTTPS connections to the servers will be allowed. + /// + /// Supported platforms to read proxy settings from are iOS and Android. + static Future create( {bool ignoreBadCertificates = false, List? trustedCertificates}) async { return HttpProxyOverride._(await _getProxyHost(), await _getProxyPort(), @@ -76,20 +111,3 @@ class HttpProxyOverride extends HttpOverrides { return super.findProxyFromEnvironment(url, environment); } } - -class Certificate { - /// The bytes of a PEM or PKCS12 file containing X509 certificates. - /// - /// Example: - /// ``` - /// Certificate( - /// await new File('some-certificate.pem').readAsBytes() - /// ); - /// ``` - final List certificateBytes; - - /// An optional password required to read the contents of [certificateBytes]. - final String? password; - - Certificate(this.certificateBytes, {this.password}); -} From 12b9d03db87354eea848e3937541cb4697a17f35 Mon Sep 17 00:00:00 2001 From: aruh Date: Wed, 20 Oct 2021 08:33:28 +0200 Subject: [PATCH 3/8] Adjust README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a684c5a..b70ce3c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Build Status](https://api.cirrus-ci.com/github/littleGnAl/http_proxy_override.svg)](https://cirrus-ci.com/github/littleGnAl/http_proxy_override) [![pub package](https://img.shields.io/pub/v/http_proxy_override.svg)](https://pub.dev/packages/http_proxy_override) -**http_proxy_override** get the proxy settings from system, so you can set up proxy for [http](https://pub.dev/packages/http), that allow you to use Charles or other proxy tools in Flutter. +**http_proxy_override** get the proxy settings from system, so you can set up proxy for +[http](https://pub.dev/packages/http), that allow you to use Charles or other proxy tools in Flutter. ## Usage @@ -12,7 +13,7 @@ You should set up before the [http](https://pub.dev/packages/http) request, typi void main() async { WidgetsFlutterBinding.ensureInitialized(); HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.createHttpProxy(); + await HttpProxyOverride.create(); HttpOverrides.global = httpProxyOverride; runApp(MyApp()); } @@ -32,4 +33,4 @@ void main() async { distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. From db53e3c8bb5ad421df4647371786b0e0532adb0f Mon Sep 17 00:00:00 2001 From: aruh Date: Wed, 20 Oct 2021 09:29:12 +0200 Subject: [PATCH 4/8] Configure `SecurityContext` instead of `certificates` This avoids a crash that occurs when a certificate is added a second time to a SecurityContext. Instead the caller should add the certificates once to a SecurityContext and that single instance is used every time a HttpClient is created. --- lib/http_proxy_override.dart | 46 ++++++++++++++---------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/lib/http_proxy_override.dart b/lib/http_proxy_override.dart index 3d046d1..96fdae6 100644 --- a/lib/http_proxy_override.dart +++ b/lib/http_proxy_override.dart @@ -3,8 +3,6 @@ import 'dart:io'; import 'package:flutter/services.dart'; -import 'certificate.dart'; - MethodChannel _channel = MethodChannel('com.littlegnal.http_proxy_override'); Future _getProxyHost() async { @@ -16,9 +14,9 @@ Future _getProxyPort() async { } class HttpProxyOverride extends HttpOverrides { - /// The host part of the proxy address. final String? host; + /// The port part of the proxy address. final String? port; @@ -27,14 +25,10 @@ class HttpProxyOverride extends HttpOverrides { /// root certificates. final bool ignoreBadCertificates; - /// Certificates added to the set of trusted X509 certificates used by - /// [SecureSocket] client connections. Servers configured with these - /// certificates will be trusted and HTTPS connections to the servers will be - /// allowed. - final List? trustedCertificates; + final SecurityContext securityContext; - HttpProxyOverride._(this.host, this.port, this.ignoreBadCertificates, - this.trustedCertificates); + HttpProxyOverride._( + this.host, this.port, this.ignoreBadCertificates, this.securityContext); /// Create an instance of [HttpProxyOverride]. /// @@ -51,31 +45,27 @@ class HttpProxyOverride extends HttpOverrides { /// this enables MITM attacks. /// Default: `false`. /// - /// [trustedCertificates] is an optional list of [Certificate]s that will be - /// added to the set of trusted X509 certificates used by [SecureSocket] - /// client connections. Servers configured with these certificates will be - /// trusted and HTTPS connections to the servers will be allowed. + /// With [securityContext] a [SecurityContext] can be provided that is used to + /// construct the [HttpClient]. This can be useful to provide a + /// [SecurityContext] that is configured with certificates that a proxy + /// server requires. /// - /// Supported platforms to read proxy settings from are iOS and Android. + /// Supported platforms to read proxy settings from are **iOS** and + /// **Android**. static Future create( {bool ignoreBadCertificates = false, - List? trustedCertificates}) async { - return HttpProxyOverride._(await _getProxyHost(), await _getProxyPort(), - ignoreBadCertificates, trustedCertificates); + SecurityContext? securityContext}) async { + return HttpProxyOverride._( + await _getProxyHost(), + await _getProxyPort(), + ignoreBadCertificates, + securityContext ?? SecurityContext.defaultContext); } @override HttpClient createHttpClient(SecurityContext? context) { - if (trustedCertificates != null && - trustedCertificates?.isNotEmpty == true) { - if (context == null) { - context = SecurityContext.defaultContext; - } - - trustedCertificates?.forEach((Certificate cert) { - context?.setTrustedCertificatesBytes(cert.certificateBytes, - password: cert.password); - }); + if (context == null) { + context = this.securityContext; } var client = super.createHttpClient(context); From 25e934b55480e5296b68707bda03ff5b07094c5d Mon Sep 17 00:00:00 2001 From: aruh Date: Sun, 24 Oct 2021 13:26:28 +0200 Subject: [PATCH 5/8] Delegate setting default proxy port to dart.http The default proxy port of 8888 only seems to make sense for Charles - i don't know whether other libraries also use this port as a default. But, as this library allows setting general proxies - not constrained to Charles -, the default port of 8888 assumes that the user of this library is using Charles. This might not be the case at all. --- lib/http_proxy_override.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/http_proxy_override.dart b/lib/http_proxy_override.dart index 96fdae6..41a0347 100644 --- a/lib/http_proxy_override.dart +++ b/lib/http_proxy_override.dart @@ -94,8 +94,8 @@ class HttpProxyOverride extends HttpOverrides { environment['http_proxy'] = '$host:$port'; environment['https_proxy'] = '$host:$port'; } else { - environment['http_proxy'] = '$host:8888'; - environment['https_proxy'] = '$host:8888'; + environment['http_proxy'] = '$host'; + environment['https_proxy'] = '$host'; } return super.findProxyFromEnvironment(url, environment); From ea20d368586b6f320864e6f82bbb89ea46e2c284 Mon Sep 17 00:00:00 2001 From: aruh Date: Sun, 24 Oct 2021 14:15:56 +0200 Subject: [PATCH 6/8] Add `ignoreBadCertificates` to README --- README.md | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b70ce3c..ddd2e50 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,48 @@ # http_proxy_override -[![Build Status](https://api.cirrus-ci.com/github/littleGnAl/http_proxy_override.svg)](https://cirrus-ci.com/github/littleGnAl/http_proxy_override) + +[![Build Status](https://api.cirrus-ci.com/github/littleGnAl/http_proxy_override.svg)](https://cirrus-ci.com/github/littleGnAl/http_proxy_override) [![pub package](https://img.shields.io/pub/v/http_proxy_override.svg)](https://pub.dev/packages/http_proxy_override) -**http_proxy_override** get the proxy settings from system, so you can set up proxy for -[http](https://pub.dev/packages/http), that allow you to use Charles or other proxy tools in Flutter. +**http_proxy_override** gets the proxy settings from the platform, so you can set up the proxy for +[http](https://pub.dev/packages/http). E.g., this allows you to use Charles, mitm-proxy or other proxy tools in Flutter. ## Usage -You should set up before the [http](https://pub.dev/packages/http) request, typically before `runApp()`. +You should set up before performing [http](https://pub.dev/packages/http) requests, typically before `runApp()`. ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); - HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.create(); + HttpProxyOverride httpProxyOverride = await HttpProxyOverride.create(); HttpOverrides.global = httpProxyOverride; runApp(MyApp()); } ``` +## TLS Certificates + +In some scenarios, proxy servers provide unexpected certificates that are not included in the platforms trusted CA +certificates. E.g. when inspecting HTTPS requests with Charles, the received server certificate is a self-signed +certificate created by Charles. + +The library provides two options to handle such cases: + +**`ignoreBadCertificates`** + +```dart +void main() async { + HttpProxyOverride httpProxyOverride = + await HttpProxyOverride.create(ignoreBadCertificates: true); +} +``` + +This sets a [`badCertificateCallback`](https://api.flutter.dev/flutter/dart-io/HttpClient/badCertificateCallback.html) +that always returns `true` on created `HttpClient`s. This will bypass certificate checks and always allow the +connection. This can be useful for development, when certificates aren't final or to avoid the setup hassle of +certificates. +Be aware, that **this makes [Man-in-the-Middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) +possible** as non-matching certificates are ignored. This should very likely never be used in production. + ## License Copyright (C) 2021 littlegnal From f8323f1d6026665f006abf85457d054766f4b8d9 Mon Sep 17 00:00:00 2001 From: aruh Date: Wed, 27 Oct 2021 20:41:17 +0200 Subject: [PATCH 7/8] Documentation for SecurityContext parameter --- README.md | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ddd2e50..338bf0f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ [![pub package](https://img.shields.io/pub/v/http_proxy_override.svg)](https://pub.dev/packages/http_proxy_override) **http_proxy_override** gets the proxy settings from the platform, so you can set up the proxy for -[http](https://pub.dev/packages/http). E.g., this allows you to use Charles, mitm-proxy or other proxy tools in Flutter. +[http](https://pub.dev/packages/http). E.g. this allows you to use Charles, mitm-proxy or other proxy tools with +Flutter. ## Usage @@ -27,22 +28,50 @@ certificate created by Charles. The library provides two options to handle such cases: -**`ignoreBadCertificates`** +**`ignoreBadCertificates`:** ```dart void main() async { - HttpProxyOverride httpProxyOverride = - await HttpProxyOverride.create(ignoreBadCertificates: true); + HttpProxyOverride httpProxyOverride = + await HttpProxyOverride.create(ignoreBadCertificates: true); } ``` -This sets a [`badCertificateCallback`](https://api.flutter.dev/flutter/dart-io/HttpClient/badCertificateCallback.html) -that always returns `true` on created `HttpClient`s. This will bypass certificate checks and always allow the -connection. This can be useful for development, when certificates aren't final or to avoid the setup hassle of -certificates. -Be aware, that **this makes [Man-in-the-Middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) +This sets a [`badCertificateCallback`](https://api.flutter.dev/flutter/dart-io/HttpClient/badCertificateCallback.html) +that always returns `true` on created `HttpClient`s. This will bypass certificate checks and always allow the +connection. This can be useful for development, when certificates aren't final or to avoid the setup hassle of +certificates. Be aware, that **this +makes [Man-in-the-Middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) possible** as non-matching certificates are ignored. This should very likely never be used in production. +**`SecurityContext`:** + +`HttpProxyOverride.create` takes a `SecurityContext` as an argument. This allows trusting specific certificates by +adding them as trusted certificates to the `SecurityContext` that is passed in. + +Example: + +```dart +void main() async { + final certBytes = rootBundle + .load("assets/certs/cert.pem") + .then((certContents) => certContents.buffer.asUint8List()); + + final context = SecurityContext.defaultContext; + context.setTrustedCertificatesBytes(certBytes); + + HttpProxyOverride httpProxyOverride = await HttpProxyOverride.create(securityContext); +} +``` + +`HttpClient`s created within context of the `HttpProxyOverride` will be configured with this `SecurityContext` and allow +HTTP connections to servers with the trusted certificates. This avoids using a `badCertificateCallback` that always +returns `true` in production, or one that does complicated matching logic against the bad certificate. + +See [`SecurityContext.setTrustedCertificates`](https://api.flutter.dev/flutter/dart-io/SecurityContext/setTrustedCertificates.html) +and [`SecurityContext.setTrustedCertificatesBytes`](https://api.flutter.dev/flutter/dart-io/SecurityContext/setTrustedCertificatesBytes.html) +for more information on how to set certificates. + ## License Copyright (C) 2021 littlegnal From 3919bf4f3bc399ffb88adcbf3ab31bab8fc2e455 Mon Sep 17 00:00:00 2001 From: aruh Date: Thu, 28 Oct 2021 22:01:40 +0200 Subject: [PATCH 8/8] Remove unused Certificate class --- lib/certificate.dart | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 lib/certificate.dart diff --git a/lib/certificate.dart b/lib/certificate.dart deleted file mode 100644 index 7fdc440..0000000 --- a/lib/certificate.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:io'; - -/// A PEM or PKCS12 file containting X509 certificates that might be password -/// protected. -class Certificate { - /// The bytes of a PEM or PKCS12 file containing X509 certificates. - /// - /// Example: - /// ```dart - /// Certificate( - /// await new File('some-certificate.pem').readAsBytes() - /// ); - /// ``` - final List certificateBytes; - - /// An optional password required to read the contents of [certificateBytes]. - /// The password is passed to [SecurityContext.setTrustedCertificatesBytes]. - final String? password; - - Certificate(this.certificateBytes, {this.password}); -}