diff --git a/README.md b/README.md index a684c5a..338bf0f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,77 @@ # 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 with +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.createHttpProxy(); + 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. + +**`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 @@ -32,4 +86,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. 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/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..41a0347 100644 --- a/lib/http_proxy_override.dart +++ b/lib/http_proxy_override.dart @@ -14,22 +14,69 @@ Future _getProxyPort() async { } class HttpProxyOverride extends HttpOverrides { - late final String? host; - late final String? port; + /// The host part of the proxy address. + final String? host; - HttpProxyOverride._(this.host, this.port); + /// The port part of the proxy address. + final String? port; - static Future createHttpProxy() async { - return HttpProxyOverride._(await _getProxyHost(), await _getProxyPort()); + /// 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; + + final SecurityContext securityContext; + + HttpProxyOverride._( + this.host, this.port, this.ignoreBadCertificates, this.securityContext); + + /// 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`. + /// + /// 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**. + static Future create( + {bool ignoreBadCertificates = false, + SecurityContext? securityContext}) async { + return HttpProxyOverride._( + await _getProxyHost(), + await _getProxyPort(), + ignoreBadCertificates, + securityContext ?? SecurityContext.defaultContext); } @override HttpClient createHttpClient(SecurityContext? context) { + if (context == null) { + context = this.securityContext; + } + 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; } @@ -47,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); 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: