From adcfc5eddc8c2aacd1a6e754b88d36b7768250b9 Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Tue, 23 Jul 2024 00:48:46 +0800 Subject: [PATCH] Desktop Generate new root certificate (#185) --- lib/l10n/app_en.arb | 4 ++ lib/l10n/app_zh.arb | 25 ++++---- lib/network/http/codec.dart | 4 +- lib/network/proxy_helper.dart | 2 +- lib/network/util/crts.dart | 48 +++++++++++++++- lib/ui/component/utils.dart | 2 +- lib/ui/desktop/toolbar/ssl/ssl.dart | 88 ++++++++++++++++++----------- test/cert_test.dart | 41 ++++++++++++++ test/tests.dart | 6 +- 9 files changed, 168 insertions(+), 52 deletions(-) create mode 100644 test/cert_test.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b5c86b2..8f30b3c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -174,6 +174,10 @@ "installRootCa": "Install Certificate", "installCaLocal": "Install Certificate to Local-Machine", "downloadRootCa": "Download Certificate", + "generateCA": "Generate new root certificate", + "generateCADescribe": "Are you sure you want to generate a new root certificate? If confirmed,\nYou need to reinstall and trust the new certificate", + "resetDefaultCA": "Reset Default Root Certificate", + "resetDefaultCADescribe": "Are you sure you want to reset to the default root certificate?\nProxyPin default root certificate is the same for all users.", "trustCa": "Trust Certificate", "profileDownload": "Profile Download", "exportCA": "Export Root Certificate", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 00c9c43..95fa651 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -107,7 +107,7 @@ "sequence": "全部请求", "domainList": "域名列表", "domainWhitelist": "域名白名单", - "domainBlacklist" :"域名黑名单", + "domainBlacklist": "域名黑名单", "appWhitelist": "应用白名单", "appWhitelistDescribe": "只代理白名单中的应用, 白名单启用黑名单将会失效", "appBlacklist": "应用黑名单", @@ -133,7 +133,7 @@ "repeatInterval": "间隔(ms)", "repeatDelay": "延时(ms)", "fixed": "固定", - "random": "重放", + "random": "随机", "keepCustomSettings": "保持自定义设置", "editRequest": "编辑请求", "reSendRequest": "已重新发送请求", @@ -175,6 +175,10 @@ "installRootCa": "安装根证书", "installCaLocal": "安装根证书到本机", "downloadRootCa": "下载根证书", + "generateCA": "重新生成根证书", + "generateCADescribe": "您确定要生成新的根证书吗? 如果确认,\n则需要重新安装并信任新的证书", + "resetDefaultCA": "重置默认根证书", + "resetDefaultCADescribe": "确定要重置为默认根证书吗? ProxyPin默认\n根证书对所有用户都是相同的.", "trustCa": "信任证书", "exportCA": "导出根证书", "exportPrivateKey": "导出私钥", @@ -236,7 +240,7 @@ "externalProxyConnectFailure": "外部代理连接失败", "externalProxyFailureConfirm": "网络不通所有接口将会访问失败,是否继续设置外部代理。", - "mobileDisplayPacketCapture":"手机端是否展示抓包:", + "mobileDisplayPacketCapture": "手机端是否展示抓包:", "proxyPortRepeat": "启动失败,请检查端口号{port}是否被占用", "reset": "重置", "proxyIgnoreDomain": "代理忽略域名", @@ -252,14 +256,13 @@ "iosVpnBackgroundAudio": "开启抓包后,退出到后台。为了维护主UI线程的网络通信,将启用静音音频播放以保持主线程运行。否则,它将只在后台运行30秒。您同意在启用抓包后在后台播放音频吗?", "highlight": "高亮", - "blue" : "蓝色", - "green" : "绿色", - "yellow" : "黄色", - "red" : "红色", - "pink" : "粉色", - "gray" : "灰色", - "underline" : "下划线", + "blue": "蓝色", + "green": "绿色", + "yellow": "黄色", + "red": "红色", + "pink": "粉色", + "gray": "灰色", + "underline": "下划线", "requestBlock": "请求屏蔽" - } \ No newline at end of file diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index a3ad038..264396c 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -71,8 +71,8 @@ abstract interface class Encoder { /// 编解码器 abstract class Codec implements Decoder, Encoder { - static const int defaultMaxInitialLineLength = 409600; - static const int maxBodyLength = 4096000; + static const int defaultMaxInitialLineLength = 1024000; // 1M + static const int maxBodyLength = 4096000; // 4M } /// http编解码 diff --git a/lib/network/proxy_helper.dart b/lib/network/proxy_helper.dart index da3810b..abd17c7 100644 --- a/lib/network/proxy_helper.dart +++ b/lib/network/proxy_helper.dart @@ -89,7 +89,7 @@ class ProxyHelper { ..hostAndPort = hostAndPort; request.processInfo ??= channelContext.processInfo; - request.response ??= HttpResponse(status) + request.response = HttpResponse(status) ..headers.contentType = 'text/plain' ..headers.contentLength = message.codeUnits.length ..body = message.codeUnits; diff --git a/lib/network/util/crts.dart b/lib/network/util/crts.dart index 3b7afdb..ca11cad 100644 --- a/lib/network/util/crts.dart +++ b/lib/network/util/crts.dart @@ -86,10 +86,11 @@ class CertificateManager { Map x509Subject = { 'C': 'CN', 'ST': 'BJ', - 'L': 'BeiJing', + 'L': 'Beijing', 'O': 'Proxy', 'OU': 'ProxyPin', }; + x509Subject['CN'] = host; var csrPem = X509Generate.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 365, @@ -97,6 +98,51 @@ class CertificateManager { return csrPem; } + //重新生成根证书 + static Future generateNewRootCA() async { + if (!_initialized) { + await _initCAConfig(); + } + + var generateRSAKeyPair = CryptoUtils.generateRSAKeyPair(); + var serverPubKey = generateRSAKeyPair.publicKey as RSAPublicKey; + var serverPriKey = generateRSAKeyPair.privateKey as RSAPrivateKey; + + //根据CA证书subject来动态生成目标服务器证书的issuer和subject + Map x509Subject = { + 'C': 'CN', + 'ST': 'BJ', + 'L': 'Beijing', + 'O': 'Proxy', + 'OU': 'ProxyPin', + }; + x509Subject['CN'] = 'ProxyPin CA (${Platform.localHostname})'; + + var csrPem = generate(_caCert, serverPubKey, serverPriKey, 'ProxyPin CA (${Platform.localHostname})'); + + //重新写入根证书 + var caFile = await certificateFile(); + await caFile.writeAsString(csrPem); + + //私钥 + var serverPriKeyPem = CryptoUtils.encodeRSAPrivateKeyToPem(serverPriKey); + var keyFile = await privateKeyFile(); + await keyFile.writeAsString(serverPriKeyPem); + cleanCache(); + _initialized = false; + } + + ///重置默认根证书 + static Future resetDefaultRootCA() async { + var caFile = await certificateFile(); + await caFile.delete(); + + var keyFile = await privateKeyFile(); + await keyFile.delete(); + cleanCache(); + _initialized = false; + } + static Future _initCAConfig() async { if (_initialized) { return; diff --git a/lib/ui/component/utils.dart b/lib/ui/component/utils.dart index 055eb97..a6e966b 100644 --- a/lib/ui/component/utils.dart +++ b/lib/ui/component/utils.dart @@ -170,7 +170,7 @@ Future showConfirmDialog(BuildContext context, {String? title, String? co context: context, builder: (context) { return AlertDialog( - title: Text(title!), + title: Text(title!, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), content: Text(content!), actions: [ TextButton( diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 54e64fc..e7012fa 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -3,8 +3,10 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/util/crts.dart'; +import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/utils/ip.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -44,41 +46,63 @@ class _SslState extends State { item(localizations.installCaLocal, onPressed: pcCer), item("${localizations.installRootCa} iOS", onPressed: () async => iosCer(await localIp())), item("${localizations.installRootCa} Android", onPressed: () async => androidCer(await localIp())), - SubmenuButton( - menuStyle: MenuStyle( - surfaceTintColor: MaterialStatePropertyAll(surfaceTintColor), - ), - menuChildren: [ - MenuItemButton( - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Text(localizations.exportCA, style: const TextStyle(fontSize: 14))), - onPressed: () async { - String? path = (await getSaveLocation(suggestedName: "ProxyPinCA.crt"))?.path; - if (path == null) return; - - var caFile = await CertificateManager.certificateFile(); - await caFile.copy(path); - }), - const Divider(thickness: 0.3, height: 8), - MenuItemButton( - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Text(localizations.exportPrivateKey, style: const TextStyle(fontSize: 14))), - onPressed: () async { - String? path = (await getSaveLocation(suggestedName: "ProxyPinKey.pem"))?.path; - if (path == null) return; - - var keyFile = await CertificateManager.privateKeyFile(); - await keyFile.copy(path); - }), - ], - child: Padding( - padding: const EdgeInsets.only(left: 10), - child: Text(localizations.export, style: const TextStyle(fontSize: 14)))) + const Divider(thickness: 0.3, height: 3), + exportMenu(surfaceTintColor), + const Divider(thickness: 0.3, height: 3), + item(localizations.generateCA, onPressed: () async { + showConfirmDialog(context, title: localizations.generateCA, content: localizations.generateCADescribe, + onConfirm: () async { + await CertificateManager.generateNewRootCA(); + if (context.mounted) FlutterToastr.show(localizations.success, context); + }); + }), + const Divider(thickness: 0.3, height: 3), + item(localizations.resetDefaultCA, onPressed: () async { + showConfirmDialog(context, + title: localizations.resetDefaultCA, + content: localizations.resetDefaultCADescribe, onConfirm: () async { + await CertificateManager.resetDefaultRootCA(); + if (context.mounted) FlutterToastr.show(localizations.success, context); + }); + }), ]); } + Widget exportMenu(Color? surfaceTintColor) { + return SubmenuButton( + menuStyle: MenuStyle( + surfaceTintColor: MaterialStatePropertyAll(surfaceTintColor), + ), + menuChildren: [ + MenuItemButton( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Text(localizations.exportCA, style: const TextStyle(fontSize: 14))), + onPressed: () async { + String? path = (await getSaveLocation(suggestedName: "ProxyPinCA.crt"))?.path; + if (path == null) return; + + var caFile = await CertificateManager.certificateFile(); + await caFile.copy(path); + }), + const Divider(thickness: 0.3, height: 8), + MenuItemButton( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Text(localizations.exportPrivateKey, style: const TextStyle(fontSize: 14))), + onPressed: () async { + String? path = (await getSaveLocation(suggestedName: "ProxyPinKey.pem"))?.path; + if (path == null) return; + + var keyFile = await CertificateManager.privateKeyFile(); + await keyFile.copy(path); + }), + ], + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text(localizations.export, style: const TextStyle(fontSize: 14)))); + } + Widget item(String text, {VoidCallback? onPressed}) { return MenuItemButton( trailingIcon: const Icon(Icons.arrow_right), diff --git a/test/cert_test.dart b/test/cert_test.dart new file mode 100644 index 0000000..4382f87 --- /dev/null +++ b/test/cert_test.dart @@ -0,0 +1,41 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:basic_utils/basic_utils.dart'; +import 'package:network_proxy/network/util/file_read.dart'; +import 'package:network_proxy/network/util/x509.dart'; + +void main() async { + var caPem = await FileRead.readAsString('assets/certs/ca.crt'); + //生成 公钥和私钥 + var caRoot = X509Utils.x509CertificateFromPem(caPem); + var generateRSAKeyPair = CryptoUtils.generateRSAKeyPair(); + var serverPubKey = generateRSAKeyPair.publicKey as RSAPublicKey; + var serverPriKey = generateRSAKeyPair.privateKey as RSAPrivateKey; + + //保存私钥 + var serverPriKeyPem = CryptoUtils.encodeRSAPrivateKeyToPem(serverPriKey); + print(serverPriKeyPem); + await File('assets/certs/server.key').writeAsString(serverPriKeyPem); + var rsaPrivateKeyFromPem = CryptoUtils.rsaPrivateKeyFromPem(serverPriKeyPem); + print(rsaPrivateKeyFromPem); + var crt = generate(caRoot, serverPubKey, serverPriKey); + print(crt); + await File('assets/certs/server.crt').writeAsString(crt); +} + +/// 生成证书 +String generate(X509CertificateData caRoot, RSAPublicKey serverPubKey, RSAPrivateKey caPriKey) { +//根据CA证书subject来动态生成目标服务器证书的issuer和subject + Map x509Subject = { + 'C': 'CN', + 'ST': 'BJ', + 'L': 'Beijing', + 'O': 'Proxy', + 'OU': 'ProxyPin', + }; + x509Subject['CN'] = 'ProxyPin CA (${Platform.localHostname})'; + + var csrPem = X509Generate.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 1095, + serialNumber: Random().nextInt(1000000).toString(), subject: x509Subject); + return csrPem; +} diff --git a/test/tests.dart b/test/tests.dart index 4d3dd9a..f7ed163 100644 --- a/test/tests.dart +++ b/test/tests.dart @@ -1,5 +1,3 @@ - - import 'dart:io'; void main() async { @@ -14,8 +12,8 @@ void main() async { // return replaceAll; // })); // print(Platform.version); - print(Platform.localHostname); + print('localHostname: ${Platform.localHostname}'); print(Platform.operatingSystem); - // print(Platform.localeName); + print(Platform.localeName); print(Platform.script); }