Skip to content
Open
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
66 changes: 60 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
limitations under the License.
4 changes: 2 additions & 2 deletions example/integration_test/http_proxy_override_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -47,7 +47,7 @@ void main() {
);

HttpProxyOverride httpProxyOverride =
await HttpProxyOverride.createHttpProxy();
await HttpProxyOverride.create();
HttpOverrides.global = httpProxyOverride;

await tester.pumpWidget(app.MyApp(
Expand Down
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 7 additions & 7 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
69 changes: 58 additions & 11 deletions lib/http_proxy_override.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,69 @@ Future<String?> _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<HttpProxyOverride> createHttpProxy() async {
Copy link
Owner

Choose a reason for hiding this comment

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

I think you should mark this function as @deprecated instead remove it directly, to avoid break change at this time

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<HttpProxyOverride> create(
Copy link
Owner

@littleGnAl littleGnAl Dec 12, 2021

Choose a reason for hiding this comment

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

I think since this library aims to use for debugging purposes

  1. the default value of ignoreBadCertificates should be true, if the user actually need to use it in production, they should set it to false
  2. it's better to make the return type to nullable and return null if ignoreBadCertificates is not true, so we do not "override" anything if ignoreBadCertificates is false

e.g.

static Future<HttpProxyOverride?> create(
      {bool ignoreBadCertificates = true,
      SecurityContext? securityContext}) async {
    if (!ignoreBadCertificates) return null;
    return HttpProxyOverride._(
        await _getProxyHost(),
        await _getProxyPort(),
        ignoreBadCertificates,
        securityContext ?? SecurityContext.defaultContext);
  }

{bool ignoreBadCertificates = false,
SecurityContext? securityContext}) async {
return HttpProxyOverride._(
await _getProxyHost(),
await _getProxyPort(),
ignoreBadCertificates,
securityContext ?? SecurityContext.defaultContext);
}

@override
HttpClient createHttpClient(SecurityContext? context) {
if (context == null) {
Copy link
Owner

Choose a reason for hiding this comment

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

The SecurityContext should be handled when the HttpClient is created, see https://api.dart.dev/stable/2.14.4/dart-io/HttpClient/HttpClient.html, but not set by this library, say that the user should

  final context = SecurityContext.defaultContext;
  context.setTrustedCertificatesBytes(...);
  HttpClient httpClient = HttpClient(context: context);

  HttpProxyOverride httpProxyOverride = await HttpProxyOverride.create();

but not

  HttpClient httpClient = HttpClient();

  final context = SecurityContext.defaultContext;
  context.setTrustedCertificatesBytes(...);
  HttpProxyOverride httpProxyOverride = await HttpProxyOverride.create(securityContext: context);

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;
}

Expand All @@ -47,8 +94,8 @@ class HttpProxyOverride extends HttpOverrides {
environment['http_proxy'] = '$host:$port';
environment['https_proxy'] = '$host:$port';
} else {
environment['http_proxy'] = '$host:8888';
Copy link
Owner

Choose a reason for hiding this comment

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

Good catch, but I'm not sure if it can be set to null or not, or just do nothing

environment['https_proxy'] = '$host:8888';
environment['http_proxy'] = '$host';
environment['https_proxy'] = '$host';
}

return super.findProxyFromEnvironment(url, environment);
Expand Down
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down