mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-09 00:34:17 +08:00
Windows Automatic Install Certificate
This commit is contained in:
@@ -336,5 +336,8 @@
|
||||
"appUpdateIgnoreBtnTxt": "Ignore",
|
||||
|
||||
"requestMap": "Request Map",
|
||||
"requestMapDescribe": "Do not request remote services, use local configuration or script for response"
|
||||
"requestMapDescribe": "Do not request remote services, use local configuration or script for response",
|
||||
|
||||
"automatic": "Automatic",
|
||||
"manual": "Manual"
|
||||
}
|
||||
@@ -1979,6 +1979,18 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Do not request remote services, use local configuration or script for response'**
|
||||
String get requestMapDescribe;
|
||||
|
||||
/// No description provided for @automatic.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Automatic'**
|
||||
String get automatic;
|
||||
|
||||
/// No description provided for @manual.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manual'**
|
||||
String get manual;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -978,4 +978,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestMapDescribe => 'Do not request remote services, use local configuration or script for response';
|
||||
|
||||
@override
|
||||
String get automatic => 'Automatic';
|
||||
|
||||
@override
|
||||
String get manual => 'Manual';
|
||||
}
|
||||
|
||||
@@ -966,6 +966,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestMapDescribe => '不请求远程服务,使用本地配置或脚本进行响应';
|
||||
|
||||
@override
|
||||
String get automatic => '自动安装';
|
||||
|
||||
@override
|
||||
String get manual => '手动安装';
|
||||
}
|
||||
|
||||
/// The translations for Chinese, using the Han script (`zh_Hant`).
|
||||
|
||||
@@ -335,5 +335,8 @@
|
||||
"appUpdateIgnoreBtnTxt": "忽略",
|
||||
|
||||
"requestMap": "请求映射",
|
||||
"requestMapDescribe": "不请求远程服务,使用本地配置或脚本进行响应"
|
||||
"requestMapDescribe": "不请求远程服务,使用本地配置或脚本进行响应",
|
||||
|
||||
"automatic": "自动安装",
|
||||
"manual": "手动安装"
|
||||
}
|
||||
@@ -21,7 +21,6 @@ class X509CertificateData {
|
||||
Map<String, String?> issuer;
|
||||
|
||||
/// The validity of the certificate
|
||||
@Deprecated('Use tbsCertificate.validity instead')
|
||||
X509CertificateValidity validity;
|
||||
|
||||
/// The sha1 thumbprint for the certificate
|
||||
|
||||
@@ -19,11 +19,8 @@ import 'dart:core';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:pointycastle/api.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart';
|
||||
import 'package:proxypin/network/util/cert/basic_constraints.dart';
|
||||
import 'package:pointycastle/export.dart';
|
||||
import 'package:proxypin/network/util/cert/pkcs12.dart';
|
||||
import 'package:proxypin/network/util/cert/x509.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
@@ -31,6 +28,7 @@ import 'package:proxypin/network/util/random.dart';
|
||||
import 'package:proxypin/utils/lang.dart';
|
||||
|
||||
import 'cache.dart';
|
||||
import 'cert/basic_constraints.dart';
|
||||
import 'cert/cert_data.dart';
|
||||
import 'cert/extension.dart';
|
||||
import 'cert/key_usage.dart';
|
||||
@@ -39,7 +37,6 @@ import 'file_read.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await CertificateManager.getCertificateContext('www.jianshu.com');
|
||||
CertificateManager.caCert.tbsCertificateSeqAsString;
|
||||
}
|
||||
|
||||
enum StartState { uninitialized, initializing, initialized }
|
||||
@@ -53,7 +50,7 @@ class CertificateManager {
|
||||
static AsymmetricKeyPair _serverKeyPair = CryptoUtils.generateRSAKeyPair();
|
||||
|
||||
/// ca证书
|
||||
static late X509CertificateData _caCert;
|
||||
static X509CertificateData? _caCert;
|
||||
|
||||
/// ca私钥
|
||||
static late RSAPrivateKey _caPriKey;
|
||||
@@ -66,7 +63,7 @@ class CertificateManager {
|
||||
return _certificateMap[host];
|
||||
}
|
||||
|
||||
static X509CertificateData get caCert => _caCert;
|
||||
static X509CertificateData? get caCert => _caCert;
|
||||
|
||||
/// 清除缓存
|
||||
static void cleanCache() {
|
||||
@@ -84,7 +81,7 @@ class CertificateManager {
|
||||
await initCAConfig();
|
||||
}
|
||||
|
||||
String cer = generate(_caCert, _serverKeyPair.publicKey as RSAPublicKey, _caPriKey, host);
|
||||
String cer = generate(_caCert!, _serverKeyPair.publicKey as RSAPublicKey, _caPriKey, host);
|
||||
|
||||
var rsaPrivateKey = _serverKeyPair.privateKey as RSAPrivateKey;
|
||||
|
||||
@@ -122,7 +119,7 @@ class CertificateManager {
|
||||
await initCAConfig();
|
||||
}
|
||||
|
||||
var subject = caCert.subject;
|
||||
var subject = caCert!.subject;
|
||||
return '${X509Utils.getSubjectHashName(subject)}.0';
|
||||
}
|
||||
|
||||
@@ -147,7 +144,7 @@ class CertificateManager {
|
||||
x509Subject['CN'] = 'ProxyPin CA (${DateTime.now().dateFormat()},${RandomUtil.randomString(6).toUpperCase()})';
|
||||
|
||||
var csrPem = X509Utils.generateSelfSignedCertificate(
|
||||
_caCert,
|
||||
_caCert!,
|
||||
serverPubKey,
|
||||
serverPriKey,
|
||||
825,
|
||||
@@ -230,6 +227,12 @@ class CertificateManager {
|
||||
return caFile;
|
||||
}
|
||||
|
||||
///证书pem格式内容
|
||||
static Future<String> certificatePem() async {
|
||||
var caFile = await certificateFile();
|
||||
return caFile.readAsString();
|
||||
}
|
||||
|
||||
/// 私钥文件
|
||||
static Future<File> privateKeyFile() async {
|
||||
final String appPath = await getApplicationSupportDirectory().then((value) => value.path);
|
||||
@@ -265,4 +268,12 @@ class CertificateManager {
|
||||
cleanCache();
|
||||
_state = StartState.uninitialized;
|
||||
}
|
||||
|
||||
/// 获取证书详细信息
|
||||
static Future<X509CertificateData> getCertificateDetails() async {
|
||||
if (_state != StartState.initialized) {
|
||||
await initCAConfig();
|
||||
}
|
||||
return caCert!;
|
||||
}
|
||||
}
|
||||
|
||||
62
lib/ui/desktop/ssl/cert_installer.dart
Normal file
62
lib/ui/desktop/ssl/cert_installer.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:proxypin/network/util/cert/cert_data.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
import 'package:x509_cert_store/x509_cert_store.dart';
|
||||
|
||||
class CertInstaller {
|
||||
static Future<bool> installCertificate(File certFile) async {
|
||||
try {
|
||||
// Read the certificate file and encode it to Base64
|
||||
final certBytes = await certFile.readAsBytes();
|
||||
final certificateBase64 = base64.encode(certBytes);
|
||||
|
||||
// Initialize the X509CertStore plugin
|
||||
final x509CertStorePlugin = X509CertStore();
|
||||
|
||||
// Add the certificate to the trusted root store
|
||||
final result = await x509CertStorePlugin.addCertificate(
|
||||
storeName: X509StoreName.root, // Add to the trusted root store
|
||||
certificateBase64: certificateBase64, // Base64-encoded certificate
|
||||
addType: X509AddType.addNewer, // Replace if it already exists
|
||||
setTrusted: Platform.isMacOS, // Mark the certificate as trusted
|
||||
);
|
||||
|
||||
logger.d('Certificate successfully installed to the trusted root store. Result: ${result.code} $result');
|
||||
return result.isOk || result.code == X509ErrorCode.alreadyExist.getString();
|
||||
} catch (e) {
|
||||
logger.e('Failed to install certificate: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查证书是否已安装
|
||||
static Future<bool> isCertInstalled(X509CertificateData caCert) async {
|
||||
String commonName = caCert.subject['2.5.4.3'] ?? 'ProxyPin CA';
|
||||
String? sha1 = caCert.sha1Thumbprint;
|
||||
try {
|
||||
if (Platform.isWindows) {
|
||||
List<String> args = ['-user', '-store', 'root'];
|
||||
if (sha1 != null) {
|
||||
args.add(sha1);
|
||||
}
|
||||
var res = await Process.run('certutil', args);
|
||||
return res.stdout.toString().toLowerCase().contains(commonName.toLowerCase());
|
||||
} else if (Platform.isMacOS) {
|
||||
var res = await Process.run('security', ['find-certificate', '-c', commonName, '-a']);
|
||||
return (res.stdout as String).isNotEmpty;
|
||||
} else if (Platform.isLinux) {
|
||||
// check common locations
|
||||
var paths = [
|
||||
'/usr/local/share/ca-certificates/$commonName.crt',
|
||||
'/etc/ssl/certs/$commonName.crt',
|
||||
];
|
||||
for (var p in paths) if (await File(p).exists()) return true;
|
||||
// fallback: search /etc/ssl/certs for subject text
|
||||
var res = await Process.run('grep', ['-i', commonName, '-R', '/etc/ssl/certs']);
|
||||
return (res.stdout as String).isNotEmpty;
|
||||
}
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
274
lib/ui/desktop/ssl/pc_cert.dart
Normal file
274
lib/ui/desktop/ssl/pc_cert.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:proxypin/l10n/app_localizations.dart';
|
||||
import 'package:proxypin/network/util/cert/cert_data.dart';
|
||||
import 'package:proxypin/network/util/crts.dart';
|
||||
import 'package:proxypin/ui/component/app_dialog.dart';
|
||||
import 'package:proxypin/ui/desktop/ssl/cert_installer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PCCert extends StatefulWidget {
|
||||
const PCCert({super.key});
|
||||
|
||||
@override
|
||||
State<PCCert> createState() => _PCCertState();
|
||||
}
|
||||
|
||||
class _PCCertState extends State<PCCert> with TickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
final RxnBool isCertInstalled = RxnBool(true);
|
||||
X509CertificateData? certDetails;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
certDetails = CertificateManager.caCert;
|
||||
_checkCertStatus();
|
||||
|
||||
if (certDetails == null) {
|
||||
CertificateManager.getCertificateDetails().then((value) => setState(() {
|
||||
certDetails = value;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void _checkCertStatus() async {
|
||||
final details = certDetails ?? await CertificateManager.getCertificateDetails();
|
||||
isCertInstalled.value = await CertInstaller.isCertInstalled(details);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
return SimpleDialog(
|
||||
titlePadding: const EdgeInsets.symmetric(),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 15),
|
||||
title: Row(children: [
|
||||
const Expanded(child: SizedBox()),
|
||||
Text(isCN ? "安装证书" : "Install Certificate", style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
const Expanded(child: SizedBox()),
|
||||
Align(alignment: Alignment.topRight, child: CloseButton())
|
||||
]),
|
||||
children: [
|
||||
TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: localizations.automatic),
|
||||
Tab(text: localizations.manual),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: 700,
|
||||
height: 470,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildAutomaticTab(context),
|
||||
_buildManualTab(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutomaticTab(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 16.0),
|
||||
child: Obx(() => Column(children: buildAutomaticChildren())),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildAutomaticChildren() {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
final subtitleStyle = Theme.of(context).textTheme.bodyMedium;
|
||||
final infoLabelStyle = Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]);
|
||||
final infoValueStyle = Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500);
|
||||
List<Widget> children = [
|
||||
const SizedBox(height: 8),
|
||||
Text(isCN ? "通过安装并信任 ProxyPin CA" : "Install and Trust ProxyPin CA Certificate",
|
||||
style: subtitleStyle, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
isCN
|
||||
? "ProxyPin 可以动态解密 HTTPS 流量以展示原始请求/响应。"
|
||||
: "ProxyPin can decrypt encrypted traffic on the fly and enable to see raw HTTPS requests and responses.",
|
||||
style: subtitleStyle,
|
||||
textAlign: TextAlign.center),
|
||||
const SizedBox(height: 45),
|
||||
];
|
||||
|
||||
if (isCertInstalled.value == false) {
|
||||
children.add(const SizedBox(height: 20));
|
||||
children.add(Icon(Icons.error_outline, color: Colors.red, size: 56));
|
||||
children.add(const SizedBox(height: 12));
|
||||
children.add(Text(isCN ? '证书未安装' : 'Certificate Not Installed',
|
||||
textAlign: TextAlign.center, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)));
|
||||
children.add(const SizedBox(height: 20));
|
||||
children.add(
|
||||
FilledButton(
|
||||
onPressed: _installCert,
|
||||
style: FilledButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 19)),
|
||||
child: Text(localizations.install)),
|
||||
);
|
||||
} else if (isCertInstalled.value == true) {
|
||||
children.add(Card(
|
||||
elevation: 2,
|
||||
color: Theme.brightnessOf(context) == Brightness.light ? Colors.grey[50] : Colors.grey[800],
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 18.0),
|
||||
child: Column(children: [
|
||||
Icon(Icons.verified_rounded, color: Colors.green, size: 56),
|
||||
const SizedBox(height: 12),
|
||||
Text(isCN ? "证书已安装" : "Certificate Installed", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 8),
|
||||
if (certDetails != null) ...[
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
// certificate details
|
||||
Row(children: [
|
||||
Text('Name', style: infoLabelStyle),
|
||||
Expanded(
|
||||
child: SelectableText(certDetails!.subject['2.5.4.3'] ?? 'ProxyPin CA',
|
||||
style: infoValueStyle, textAlign: TextAlign.right)),
|
||||
]),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [
|
||||
Text('Expires', style: infoLabelStyle),
|
||||
Expanded(
|
||||
child: SelectableText(certDetails!.validity.notAfter.toLocal().toString().split(' ').first,
|
||||
style: infoValueStyle, textAlign: TextAlign.right)),
|
||||
]),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [
|
||||
Text('Fingerprint', style: infoLabelStyle),
|
||||
Expanded(
|
||||
child: SelectableText(certDetails!.sha1Thumbprint ?? '-',
|
||||
style: infoValueStyle, textAlign: TextAlign.right),
|
||||
),
|
||||
])
|
||||
]
|
||||
]),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
Widget _buildManualTab(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildChildren(context),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildChildren(BuildContext context) {
|
||||
if (Platform.isMacOS || Platform.isWindows) {
|
||||
return _buildWindowsAndMacContent(context);
|
||||
}
|
||||
return _buildLinuxContent(context);
|
||||
}
|
||||
|
||||
List<Widget> _buildWindowsAndMacContent(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
return [
|
||||
isCN
|
||||
? Text(" 安装证书到本系统,${Platform.isMacOS ? "安装完双击选择“始终信任此证书”。 如安装打开失败,请导出证书拖拽到系统证书里" : "选择“受信任的根证书颁发机构”"}")
|
||||
: Text(
|
||||
" Install certificate to this system,${Platform.isMacOS ? "After installation, double-click to select “Always Trust”。\n If installation and opening fail,Please export the certificate and drag it to the system certificate" : "choice“Trusted Root Certificate Authority”"}"),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: FilledButton(
|
||||
onPressed: () => _manualInstallCert(),
|
||||
style: FilledButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
|
||||
child: Text(localizations.installRootCa))),
|
||||
const SizedBox(height: 10),
|
||||
Platform.isMacOS
|
||||
? Image.network("https://foruda.gitee.com/images/1689323260158189316/c2d881a4_1073801.png",
|
||||
width: 800, height: 500)
|
||||
: Row(children: [
|
||||
Image.network("https://foruda.gitee.com/images/1689335589122168223/c904a543_1073801.png",
|
||||
width: 370, height: 380),
|
||||
const SizedBox(width: 10),
|
||||
Image.network("https://foruda.gitee.com/images/1689335334688878324/f6aa3a3a_1073801.png",
|
||||
width: 370, height: 380)
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildLinuxContent(BuildContext context) {
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
return [
|
||||
Text(isCN
|
||||
? "安装证书到本系统,以Ubuntu为例 下载证书:\n"
|
||||
"先把证书复制到 /usr/local/share/ca-certificates/,然后执行 update-ca-certificates 即可。\n"
|
||||
"其他系统请网上搜索安装根证书"
|
||||
: "Install the certificate to this system), take Ubuntu as an example to download the certificate:\n"
|
||||
"First copy the certificate to /usr/local/share/ca-certificates/, and then execute update-ca-certificates.\n"
|
||||
"For other systems, please search online for installing root certificates."),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
isCN
|
||||
? "提示:FireFox有自己的信任证书库,所以要手动在设置中导入需要导入的证书。"
|
||||
: "Note: FireFox has its own trusted certificate library, so you need to manually import the required certificates in the settings.",
|
||||
style: TextStyle(fontSize: 12)),
|
||||
const SizedBox(height: 10),
|
||||
const SelectableText.rich(
|
||||
textAlign: TextAlign.justify,
|
||||
TextSpan(style: TextStyle(color: Color(0xff6a8759)), children: [
|
||||
TextSpan(text: " sudo cp ProxyPinCA.crt /usr/local/share/ca-certificates/ \n"),
|
||||
TextSpan(text: " sudo update-ca-certificates")
|
||||
])),
|
||||
const SizedBox(height: 10)
|
||||
];
|
||||
}
|
||||
|
||||
void _installCert() async {
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
var caFile = await CertificateManager.certificateFile();
|
||||
bool success = await CertInstaller.installCertificate(caFile);
|
||||
CertificateManager.cleanCache();
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
isCertInstalled.value = true;
|
||||
CustomToast.success(isCN ? "证书安装成功" : "Certificate installed successfully").show(context);
|
||||
} else {
|
||||
isCertInstalled.value = false;
|
||||
final isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
CustomToast.error(isCN ? "证书安装失败,请尝试手动安装" : "Certificate installation failed, please try manual installation")
|
||||
.show(context);
|
||||
}
|
||||
}
|
||||
|
||||
void _manualInstallCert() async {
|
||||
var caFile = await CertificateManager.certificateFile();
|
||||
launchUrl(Uri.file(caFile.path)).then((_) {
|
||||
CertificateManager.cleanCache();
|
||||
isCertInstalled.value = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:proxypin/network/bin/server.dart';
|
||||
import 'package:proxypin/network/util/crts.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
import 'package:proxypin/ui/component/utils.dart';
|
||||
import 'package:proxypin/ui/desktop/ssl/pc_cert.dart';
|
||||
import 'package:proxypin/utils/ip.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -42,7 +43,8 @@ class _SslState extends State<SslWidget> {
|
||||
},
|
||||
menuChildren: [
|
||||
_Switch(proxyServer: widget.proxyServer, onEnableChange: (val) => setState(() {})),
|
||||
item(localizations.installCaLocal, onPressed: pcCer),
|
||||
item(localizations.installCaLocal,
|
||||
onPressed: () => showDialog(context: context, builder: (context) => PCCert())),
|
||||
item("${localizations.installRootCa} iOS", onPressed: () async => iosCer(await localIp())),
|
||||
item("${localizations.installRootCa} Android", onPressed: () async => androidCer(await localIp())),
|
||||
const Divider(thickness: 0.3, height: 3),
|
||||
@@ -202,64 +204,6 @@ class _SslState extends State<SslWidget> {
|
||||
child: Text(text, style: const TextStyle(fontSize: 14))));
|
||||
}
|
||||
|
||||
void pcCer() async {
|
||||
bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
List<Widget> list = [];
|
||||
if (Platform.isMacOS || Platform.isWindows) {
|
||||
list = [
|
||||
isCN
|
||||
? Text(" 安装证书到本系统,${Platform.isMacOS ? "安装完双击选择“始终信任此证书”。 如安装打开失败,请导出证书拖拽到系统证书里" : "选择“受信任的根证书颁发机构”"}")
|
||||
: Text(" Install certificate to this system,${Platform.isMacOS ? "After installation, double-click to select “Always Trust”。\n"
|
||||
" If installation and opening fail,Please export the certificate and drag it to the system certificate" : "choice“Trusted Root Certificate Authority”"}"),
|
||||
const SizedBox(height: 10),
|
||||
FilledButton(onPressed: _installCert, child: Text(localizations.installRootCa)),
|
||||
const SizedBox(height: 10),
|
||||
Platform.isMacOS
|
||||
? Image.network("https://foruda.gitee.com/images/1689323260158189316/c2d881a4_1073801.png",
|
||||
width: 800, height: 500)
|
||||
: Row(children: [
|
||||
Image.network("https://foruda.gitee.com/images/1689335589122168223/c904a543_1073801.png",
|
||||
width: 400, height: 400),
|
||||
const SizedBox(width: 10),
|
||||
Image.network("https://foruda.gitee.com/images/1689335334688878324/f6aa3a3a_1073801.png",
|
||||
width: 400, height: 400)
|
||||
])
|
||||
];
|
||||
} else {
|
||||
list.add(const Text("安装证书到本系统,以Ubuntu为例 下载证书:\n"
|
||||
"先把证书复制到 /usr/local/share/ca-certificates/,然后执行 update-ca-certificates 即可。\n"
|
||||
"其他系统请网上搜索安装根证书"));
|
||||
list.add(const SizedBox(height: 5));
|
||||
list.add(const Text("提示:FireFox有自己的信任证书库,所以要手动在设置中导入需要导入的证书。", style: TextStyle(fontSize: 12)));
|
||||
list.add(const SizedBox(height: 10));
|
||||
list.add(const SelectableText.rich(
|
||||
textAlign: TextAlign.justify,
|
||||
TextSpan(style: TextStyle(color: Color(0xff6a8759)), children: [
|
||||
TextSpan(text: " sudo cp ProxyPinCA.crt /usr/local/share/ca-certificates/ \n"),
|
||||
TextSpan(text: " sudo update-ca-certificates")
|
||||
])));
|
||||
list.add(const SizedBox(height: 10));
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return SimpleDialog(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
title: Row(children: [
|
||||
const Expanded(child: SizedBox()),
|
||||
Text(isCN ? "电脑HTTPS抓包配置" : "Computer HTTPS Packet Capture Configuration",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
|
||||
const Expanded(child: SizedBox()),
|
||||
Align(alignment: Alignment.topRight, child: CloseButton())
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
children: list);
|
||||
});
|
||||
}
|
||||
|
||||
void iosCer(String host) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -402,11 +346,6 @@ class _SslState extends State<SslWidget> {
|
||||
))));
|
||||
});
|
||||
}
|
||||
|
||||
void _installCert() async {
|
||||
var caFile = await CertificateManager.certificateFile();
|
||||
launchUrl(Uri.file(caFile.path)).then((value) => CertificateManager.cleanCache());
|
||||
}
|
||||
}
|
||||
|
||||
class _Switch extends StatefulWidget {
|
||||
|
||||
@@ -18,6 +18,7 @@ import share_plus
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
import window_manager
|
||||
import x509_cert_store
|
||||
import zstandard_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
@@ -34,5 +35,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
X509CertStorePlugin.register(with: registry.registrar(forPlugin: "X509CertStorePlugin"))
|
||||
ZstandardMacosPlugin.register(with: registry.registrar(forPlugin: "ZstandardMacosPlugin"))
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ dependencies:
|
||||
macos_window_utils: ^1.9.0
|
||||
win32audio: ^1.3.1
|
||||
vclibs: ^0.1.3
|
||||
x509_cert_store: ^1.2.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <vclibs/vclibs_plugin_c_api.h>
|
||||
#include <win32audio/win32audio_plugin_c_api.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
#include <x509_cert_store/x509_cert_store_plugin_c_api.h>
|
||||
#include <zstandard_windows/zstandard_windows_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
@@ -42,6 +43,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("Win32audioPluginCApi"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||
X509CertStorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("X509CertStorePluginCApi"));
|
||||
ZstandardWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ZstandardWindowsPluginCApi"));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
vclibs
|
||||
win32audio
|
||||
window_manager
|
||||
x509_cert_store
|
||||
zstandard_windows
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user