Skip to content

Commit

Permalink
Allow self-signed certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
bauerj committed Feb 13, 2022
1 parent 73615a6 commit 8e05a75
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 42 deletions.
28 changes: 28 additions & 0 deletions lib/api.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'
as SecureStorage;
Expand Down Expand Up @@ -204,6 +206,7 @@ class API {
String? authString;
String apiFlavour;
final Dio dio = new Dio();
static String? trustedCertificateSha512;

API(String baseURL,
{this.username = "", this.password = "", this.apiFlavour = "paperless"}) {
Expand Down Expand Up @@ -399,3 +402,28 @@ class API {
await updateResource("document", id, newDocument);
}
}

class SelfSignedCertHttpOverride extends HttpOverrides {
static X509Certificate? lastFailedCert;
static String toSha512(X509Certificate cert) {
return sha512.convert(cert.der).toString();
}

@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) {
if (!cert.endValidity.isAfter(DateTime.now())) {
// Never trust an expired certificate
lastFailedCert = cert;
return false;
}
if (API.trustedCertificateSha512 != toSha512(cert)) {
// Abort if this is not the known certificate
lastFailedCert = cert;
return false;
}
return true;
};
}
}
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get_it/get_it.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:paperless_app/api.dart';
import 'package:paperless_app/i18n.dart';
import 'package:paperless_app/routes/home_route.dart';

Expand Down Expand Up @@ -82,5 +85,6 @@ class _PaperlessAppState extends State<PaperlessApp> {
super.initState();
GetIt.I.registerSingleton<FlutterSecureStorage>(new FlutterSecureStorage());
loadAsync = MyI18n.loadTranslations();
HttpOverrides.global = SelfSignedCertHttpOverride();
}
}
29 changes: 4 additions & 25 deletions lib/routes/documents_route.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
Expand All @@ -26,6 +27,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';

import '../api.dart';
import '../util/handle_dio_error.dart';

class DocumentsRoute extends StatefulWidget {
static DateFormat dateFormat = DateFormat();
Expand Down Expand Up @@ -122,31 +124,8 @@ class _DocumentsRouteState extends State<DocumentsRoute> {
documents = _documents;
requesting = false;
});
} catch (e) {
showDialog(
context: _scaffoldKey.currentContext!,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error while connecting to server".i18n),
content: Text(e.toString()),
actions: <Widget>[
new TextButton(
onPressed: () {
Navigator.pop(context);
reloadDocuments();
},
child: Text("Retry".i18n)),
new TextButton(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ServerDetailsRoute()),
);
},
child: Text("Edit Server Details".i18n))
]);
});
} on DioError catch (e) {
handleDioError(e, context);
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/routes/home_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class _HomeRouteState extends State<HomeRoute> {

var username = await GetIt.I<FlutterSecureStorage>().read(key: "username");
var password = await GetIt.I<FlutterSecureStorage>().read(key: "password");
var trustedCertificateSha512 = await GetIt.I<FlutterSecureStorage>()
.read(key: "trustedCertificateSha512");
var apiFlavour =
await GetIt.I<FlutterSecureStorage>().read(key: "api_flavour");

Expand All @@ -66,6 +68,8 @@ class _HomeRouteState extends State<HomeRoute> {
apiFlavour = "paperless";
}

API.trustedCertificateSha512 = trustedCertificateSha512;

API(url, username: username, password: password, apiFlavour: apiFlavour);

Navigator.pushReplacement(
Expand Down
12 changes: 4 additions & 8 deletions lib/routes/login_route.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
Expand All @@ -9,6 +10,7 @@ import 'package:paperless_app/widgets/display_steps_widget.dart';
import 'package:paperless_app/widgets/textfield_widget.dart';

import '../api.dart';
import '../util/handle_dio_error.dart';

final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
Expand Down Expand Up @@ -48,14 +50,8 @@ class _LoginRouteState extends State<LoginRoute> {
MaterialPageRoute(builder: (context) => DocumentsRoute()),
);
}
} catch (e) {
showDialog(
context: _scaffoldKey.currentContext!,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text("Error while connecting to server".i18n),
content: Text(e.toString()));
});
} on DioError catch (e) {
handleDioError(e, context);
}
}
}
Expand Down
12 changes: 4 additions & 8 deletions lib/routes/server_details_route.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
Expand All @@ -7,6 +8,7 @@ import 'package:paperless_app/i18n.dart';
import 'package:paperless_app/widgets/button_widget.dart';
import 'package:paperless_app/widgets/textfield_widget.dart';

import '../util/handle_dio_error.dart';
import '../widgets/display_steps_widget.dart';
import 'login_route.dart';

Expand Down Expand Up @@ -46,14 +48,8 @@ class _ServerDetailsRouteState extends State<ServerDetailsRoute> {
MaterialPageRoute(builder: (context) => LoginRoute()),
);
}
} catch (e) {
showDialog(
context: _scaffoldKey.currentContext!,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text("Error while connecting to server".i18n),
content: Text(e.toString()));
});
} on DioError catch (e) {
handleDioError(e, _scaffoldKey.currentContext!);
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions lib/util/handle_dio_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'dart:io';

import 'package:convert/convert.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_it/get_it.dart';
import 'package:paperless_app/i18n.dart';

import '../api.dart';

void handleDioError(DioError e, BuildContext ctx) {
if (e.error.runtimeType == HandshakeException &&
SelfSignedCertHttpOverride.lastFailedCert != null) {
String fingerprint =
hex.encode(SelfSignedCertHttpOverride.lastFailedCert!.sha1);
showDialog(
context: ctx,
builder: (BuildContext ctx) {
return AlertDialog(
backgroundColor: Colors.red.shade900,
title: Text("This is not a secure connection".i18n),
content: Text(
"The certificate for %s is not valid. Do you still want to trust the certificate with fingerprint %s?"
.i18n
.fill([e.requestOptions.uri.host, fingerprint]) +
"\n" +
"WARNING: The connection is not secure if you do not compare the fingerprint."
.i18n),
actions: [
TextButton(
onPressed: () {
API.trustedCertificateSha512 =
SelfSignedCertHttpOverride.toSha512(
SelfSignedCertHttpOverride.lastFailedCert!);
GetIt.I<FlutterSecureStorage>().write(
key: "trustedCertificateSha512",
value: API.trustedCertificateSha512);
Navigator.of(ctx).pop();
},
child: Text('Yes'.i18n),
),
TextButton(
onPressed: () {
// Close the dialog
Navigator.of(ctx).pop();
},
child: Text('No'.i18n),
),
],
);
});
} else
showDialog(
context: ctx,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text("Error while connecting to server".i18n),
content: Text(e.toString()));
});
}
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ packages:
source: hosted
version: "3.0.1"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
url: "https://pub.dartlang.org"
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies:
wechat_assets_picker: ^5.4.0
share: ^2.0.1
url_launcher: ^6.0.20
crypto: ^3.0.1

dev_dependencies:
flutter_test:
Expand Down

0 comments on commit 8e05a75

Please sign in to comment.