diff --git a/CMakeLists.txt b/CMakeLists.txt index 6372781..7907308 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,6 +287,9 @@ set(PROJECT_SOURCES include/ui/profile/edit_tuic.h src/ui/profile/edit_tuic.cpp include/ui/profile/edit_tuic.ui + include/ui/profile/edit_advanced.h + src/ui/profile/edit_advanced.cpp + include/ui/profile/edit_advanced.ui ) if (NOT APPLE AND Qt6_VERSION VERSION_GREATER_EQUAL 6.9.0) diff --git a/include/configs/common/TLS.h b/include/configs/common/TLS.h index f9eaf53..9a6536a 100644 --- a/include/configs/common/TLS.h +++ b/include/configs/common/TLS.h @@ -82,10 +82,10 @@ namespace Configs QString max_version; QStringList cipher_suites; QStringList curve_preferences; - QString certificate; + QStringList certificate; QString certificate_path; QStringList certificate_public_key_sha256; - QString client_certificate; + QStringList client_certificate; QString client_certificate_path; QStringList client_key; QString client_key_path; @@ -107,10 +107,10 @@ namespace Configs _add(new configItem("max_version", &max_version, string)); _add(new configItem("cipher_suites", &cipher_suites, stringList)); _add(new configItem("curve_preferences", &curve_preferences, stringList)); - _add(new configItem("certificate", &certificate, string)); + _add(new configItem("certificate", &certificate, stringList)); _add(new configItem("certificate_path", &certificate_path, string)); _add(new configItem("certificate_public_key_sha256", &certificate_public_key_sha256, stringList)); - _add(new configItem("client_certificate", &client_certificate, string)); + _add(new configItem("client_certificate", &client_certificate, stringList)); _add(new configItem("client_certificate_path", &client_certificate_path, string)); _add(new configItem("client_key", &client_key, stringList)); _add(new configItem("client_key_path", &client_key_path, string)); diff --git a/include/ui/profile/dialog_edit_profile.h b/include/ui/profile/dialog_edit_profile.h index 3819ff0..cf83347 100644 --- a/include/ui/profile/dialog_edit_profile.h +++ b/include/ui/profile/dialog_edit_profile.h @@ -42,7 +42,7 @@ private: QString network_title_base; struct { - QString certificate; + QStringList certificate; } CACHE; void typeSelected(const QString &newType); diff --git a/include/ui/profile/dialog_edit_profile.ui b/include/ui/profile/dialog_edit_profile.ui index 0738be6..d8a7507 100644 --- a/include/ui/profile/dialog_edit_profile.ui +++ b/include/ui/profile/dialog_edit_profile.ui @@ -294,6 +294,13 @@ + + + + Advanced Settings + + + diff --git a/include/ui/profile/edit_advanced.h b/include/ui/profile/edit_advanced.h new file mode 100644 index 0000000..585e763 --- /dev/null +++ b/include/ui/profile/edit_advanced.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include "ui_edit_advanced.h" +#include "include/dataStore/ProxyEntity.hpp" + +namespace Ui { +class EditAdvanced; +} + +class EditAdvanced : public QDialog +{ + Q_OBJECT + +public: + EditAdvanced(QWidget *parent, const std::shared_ptr &_ent); + + ~EditAdvanced() override; + +public slots: + void accept() override; + +private slots: + void on_ech_config_clicked(); + + void on_cert_sha256_clicked(); + + void on_client_cert_clicked(); + + void on_client_key_clicked(); + +private: + Ui::EditAdvanced *ui; + std::shared_ptr ent; + + struct { + QStringList echConfig; + QStringList certSha256; + QStringList clientCert; + QStringList clientKey; + } CACHE; +}; \ No newline at end of file diff --git a/include/ui/profile/edit_advanced.ui b/include/ui/profile/edit_advanced.ui new file mode 100644 index 0000000..81f7e2f --- /dev/null +++ b/include/ui/profile/edit_advanced.ui @@ -0,0 +1,210 @@ + + + EditAdvanced + + + + 0 + 0 + 400 + 534 + + + + Dialog + + + + + + Dial Fields + + + + + + Connect Timeout + + + + + + + + + + TCP Fast Open + + + + + + + TCP MultiPath + + + + + + + Reuse Address + + + + + + + UDP Fragment + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + TLS + + + + + + Enable ECH + + + + + + + ECH Config + + + + + + + Not Set + + + + + + + Certificate SHA256 + + + + + + + Not Set + + + + + + + Client Certificate + + + + + + + Not Set + + + + + + + Client Key + + + + + + + Not Set + + + + + + + Disable SNI + + + + + + + TLS Min Version + + + + + + + + + + TLS Max Version + + + + + + + + + + + + + + + buttonBox + accepted() + EditAdvanced + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditAdvanced + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/configs/common/TLS.cpp b/src/configs/common/TLS.cpp index 31b3e8a..07e0c4a 100644 --- a/src/configs/common/TLS.cpp +++ b/src/configs/common/TLS.cpp @@ -169,10 +169,10 @@ namespace Configs { if (query.hasQueryItem("tls_max_version")) max_version = query.queryItemValue("tls_max_version"); if (query.hasQueryItem("tls_cipher_suites")) cipher_suites = query.queryItemValue("tls_cipher_suites").split(","); if (query.hasQueryItem("tls_curve_preferences")) curve_preferences = query.queryItemValue("tls_curve_preferences").split(","); - if (query.hasQueryItem("tls_certificate")) certificate = query.queryItemValue("tls_certificate"); + if (query.hasQueryItem("tls_certificate")) certificate = query.queryItemValue("tls_certificate").split(","); if (query.hasQueryItem("tls_certificate_path")) certificate_path = query.queryItemValue("tls_certificate_path"); if (query.hasQueryItem("tls_certificate_public_key_sha256")) certificate_public_key_sha256 = query.queryItemValue("tls_certificate_public_key_sha256").split(","); - if (query.hasQueryItem("tls_client_certificate")) client_certificate = query.queryItemValue("tls_client_certificate"); + if (query.hasQueryItem("tls_client_certificate")) client_certificate = query.queryItemValue("tls_client_certificate").split(","); if (query.hasQueryItem("tls_client_certificate_path")) client_certificate_path = query.queryItemValue("tls_client_certificate_path"); if (query.hasQueryItem("tls_client_key")) client_key = query.queryItemValue("tls_client_key").split(","); if (query.hasQueryItem("tls_client_key_path")) client_key_path = query.queryItemValue("tls_client_key_path"); @@ -203,12 +203,20 @@ namespace Configs { if (object.contains("curve_preferences")) { curve_preferences = QJsonArray2QListString(object["curve_preferences"].toArray()); } - if (object.contains("certificate")) certificate = object["certificate"].toString(); + if (object.contains("certificate")) { + if (object["certificate"].isString()) { + certificate = object["certificate"].toString().split("\n", Qt::SkipEmptyParts); + } else { + certificate = QJsonArray2QListString(object["certificate"].toArray()); + } + } if (object.contains("certificate_path")) certificate_path = object["certificate_path"].toString(); if (object.contains("certificate_public_key_sha256")) { certificate_public_key_sha256 = QJsonArray2QListString(object["certificate_public_key_sha256"].toArray()); } - if (object.contains("client_certificate")) client_certificate = object["client_certificate"].toString(); + if (object.contains("client_certificate")) { + client_certificate = QJsonArray2QListString(object["client_certificate"].toArray()); + } if (object.contains("client_certificate_path")) client_certificate_path = object["client_certificate_path"].toString(); if (object.contains("client_key")) { client_key = QJsonArray2QListString(object["client_key"].toArray()); @@ -235,10 +243,10 @@ namespace Configs { if (!max_version.isEmpty()) query.addQueryItem("tls_max_version", max_version); if (!cipher_suites.isEmpty()) query.addQueryItem("tls_cipher_suites", cipher_suites.join(",")); if (!curve_preferences.isEmpty()) query.addQueryItem("tls_curve_preferences", curve_preferences.join(",")); - if (!certificate.isEmpty()) query.addQueryItem("tls_certificate", certificate); + if (!certificate.isEmpty()) query.addQueryItem("tls_certificate", certificate.join(",")); if (!certificate_path.isEmpty()) query.addQueryItem("tls_certificate_path", certificate_path); if (!certificate_public_key_sha256.isEmpty()) query.addQueryItem("tls_certificate_public_key_sha256", certificate_public_key_sha256.join(",")); - if (!client_certificate.isEmpty()) query.addQueryItem("tls_client_certificate", client_certificate); + if (!client_certificate.isEmpty()) query.addQueryItem("tls_client_certificate", client_certificate.join(",")); if (!client_certificate_path.isEmpty()) query.addQueryItem("tls_client_certificate_path", client_certificate_path); if (!client_key.isEmpty()) query.addQueryItem("tls_client_key", client_key.join(",")); if (!client_key_path.isEmpty()) query.addQueryItem("tls_client_key_path", client_key_path); @@ -269,12 +277,14 @@ namespace Configs { if (!curve_preferences.isEmpty()) { object["curve_preferences"] = QListStr2QJsonArray(curve_preferences); } - if (!certificate.isEmpty()) object["certificate"] = certificate; + if (!certificate.isEmpty()) { + object["certificate"] = QListStr2QJsonArray(certificate); + } if (!certificate_path.isEmpty()) object["certificate_path"] = certificate_path; if (!certificate_public_key_sha256.isEmpty()) { object["certificate_public_key_sha256"] = QListStr2QJsonArray(certificate_public_key_sha256); } - if (!client_certificate.isEmpty()) object["client_certificate"] = client_certificate; + if (!client_certificate.isEmpty()) object["client_certificate"] = QListStr2QJsonArray(client_certificate); if (!client_certificate_path.isEmpty()) object["client_certificate_path"] = client_certificate_path; if (!client_key.isEmpty()) { object["client_key"] = QListStr2QJsonArray(client_key); diff --git a/src/configs/common/transport.cpp b/src/configs/common/transport.cpp index be0dda7..43b0e91 100644 --- a/src/configs/common/transport.cpp +++ b/src/configs/common/transport.cpp @@ -103,6 +103,7 @@ namespace Configs { QString Transport::ExportToLink() { QUrlQuery query; + if (type.isEmpty() || type == "tcp") return ""; if (!type.isEmpty()) query.addQueryItem("type", type); if (!host.isEmpty()) query.addQueryItem("host", host); if (!path.isEmpty()) query.addQueryItem("path", path); @@ -118,6 +119,7 @@ namespace Configs { QJsonObject Transport::ExportToJson() { QJsonObject object; + if (type.isEmpty() || type == "tcp") return object; if (!type.isEmpty()) object["type"] = type; if (!host.isEmpty()) object["host"] = host; if (!path.isEmpty()) object["path"] = path; diff --git a/src/configs/sub/GroupUpdater.cpp b/src/configs/sub/GroupUpdater.cpp index 91dee8b..1218f4f 100644 --- a/src/configs/sub/GroupUpdater.cpp +++ b/src/configs/sub/GroupUpdater.cpp @@ -693,7 +693,7 @@ namespace Subscription { bean->tls->enabled = true; bean->tls->insecure = Node2Bool(proxy["skip-cert-verify"]); auto alpn = Node2QStringList(proxy["alpn"]); - bean->tls->certificate = Node2QString(proxy["ca-str"]); + bean->tls->certificate = Node2QString(proxy["ca-str"]).split("\n", Qt::SkipEmptyParts); if (!alpn.isEmpty()) bean->tls->alpn = {alpn[0]}; bean->tls->server_name = Node2QString(proxy["sni"]); @@ -755,7 +755,7 @@ namespace Subscription { bean->zero_rtt_handshake = Node2Bool(proxy["reduce-rtt"]); bean->tls->insecure = Node2Bool(proxy["skip-cert-verify"]); bean->tls->alpn = Node2QStringList(proxy["alpn"]); - bean->tls->certificate = Node2QString(proxy["ca-str"]); + bean->tls->certificate = Node2QString(proxy["ca-str"]).split("\n", Qt::SkipEmptyParts); bean->tls->server_name = Node2QString(proxy["sni"]); if (Node2Bool(proxy["udp-over-stream"])) bean->udp_over_stream = true; diff --git a/src/ui/profile/dialog_edit_profile.cpp b/src/ui/profile/dialog_edit_profile.cpp index 351a714..271b3f5 100644 --- a/src/ui/profile/dialog_edit_profile.cpp +++ b/src/ui/profile/dialog_edit_profile.cpp @@ -20,6 +20,7 @@ #include +#include "include/ui/profile/edit_advanced.h" #include "include/ui/profile/edit_hysteria.h" #include "include/ui/profile/edit_socks.h" #include "include/ui/profile/edit_trojan.h" @@ -125,6 +126,12 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, } }); + // Advanced options + connect(ui->advanced_button, &QPushButton::clicked, this, [=,this]() { + auto advancedWidget = new EditAdvanced(this, ent); + advancedWidget->show(); + }); + newEnt = _type != ""; if (newEnt) { this->groupId = profileOrGroupId; @@ -476,9 +483,9 @@ void DialogEditProfile::editor_cache_updated_impl() { void DialogEditProfile::on_certificate_edit_clicked() { bool ok; - auto txt = QInputDialog::getMultiLineText(this, tr("Certificate"), "", CACHE.certificate, &ok); + auto txt = QInputDialog::getMultiLineText(this, tr("Certificate"), "", CACHE.certificate.join("\n"), &ok); if (ok) { - CACHE.certificate = txt; + CACHE.certificate = txt.split("\n", Qt::SkipEmptyParts); editor_cache_updated_impl(); } } diff --git a/src/ui/profile/edit_advanced.cpp b/src/ui/profile/edit_advanced.cpp new file mode 100644 index 0000000..7125ff3 --- /dev/null +++ b/src/ui/profile/edit_advanced.cpp @@ -0,0 +1,130 @@ +#include "include/ui/profile/edit_advanced.h" + +#include + +#include "include/dataStore/ProxyEntity.hpp" + +EditAdvanced::EditAdvanced(QWidget *parent, const std::shared_ptr &_ent) + : QDialog(parent) + , ui(new Ui::EditAdvanced) +{ + ui->setupUi(this); + ent = _ent; + auto dialFieldsObj = ent->outbound->dialFields; + ui->reuse_addr->setChecked(dialFieldsObj->reuse_addr); + ui->tcp_fast_open->setChecked(dialFieldsObj->tcp_fast_open); + ui->udp_fragment->setChecked(dialFieldsObj->udp_fragment); + ui->tcp_multipath->setChecked(dialFieldsObj->tcp_multi_path); + ui->connect_timeout->setText(dialFieldsObj->connect_timeout); + + if (ent->outbound->HasTLS()) { + auto tlsObj = ent->outbound->GetTLS(); + ui->disable_sni->setChecked(tlsObj->disable_sni); + ui->min_version->setText(tlsObj->min_version); + ui->max_version->setText(tlsObj->max_version); + ui->enable_ech->setChecked(tlsObj->ech->enabled); + + // TODO enable when sing-box version is >= 1.13 + ui->cert_sha256->setEnabled(false); + ui->client_cert->setEnabled(false); + ui->client_key->setEnabled(false); + if (!tlsObj->ech->config.isEmpty()) { + ui->ech_config->setText("Already set"); + CACHE.echConfig = tlsObj->ech->config; + } + if (!tlsObj->certificate_public_key_sha256.isEmpty()) { + ui->cert_sha256->setText("Already set"); + CACHE.certSha256 = tlsObj->certificate_public_key_sha256; + } + if (!tlsObj->client_certificate.isEmpty()) { + ui->client_cert->setText("Already set"); + CACHE.clientCert = tlsObj->client_certificate; + } + if (!tlsObj->client_key.isEmpty()) { + ui->client_key->setText("Already set"); + CACHE.clientKey = tlsObj->client_key; + } + } else { + ui->tls_box->hide(); + adjustSize(); + } +} + +EditAdvanced::~EditAdvanced() +{ + delete ui; +} + +void EditAdvanced::accept() { + auto dialFieldsObj = ent->outbound->dialFields; + dialFieldsObj->reuse_addr = ui->reuse_addr->isChecked(); + dialFieldsObj->tcp_fast_open = ui->tcp_fast_open->isChecked(); + dialFieldsObj->udp_fragment = ui->udp_fragment->isChecked(); + dialFieldsObj->tcp_multi_path = ui->tcp_multipath->isChecked(); + dialFieldsObj->connect_timeout = ui->connect_timeout->text(); + + if (ent->outbound->HasTLS()) { + auto tlsObj = ent->outbound->GetTLS(); + tlsObj->disable_sni = ui->disable_sni->isChecked(); + tlsObj->min_version = ui->min_version->text(); + tlsObj->max_version = ui->max_version->text(); + tlsObj->ech->enabled = ui->enable_ech->isChecked(); + tlsObj->ech->config = CACHE.echConfig; + tlsObj->client_certificate = CACHE.clientCert; + tlsObj->client_key = CACHE.clientKey; + tlsObj->certificate_public_key_sha256 = CACHE.certSha256; + } + QDialog::accept(); +} + +void EditAdvanced::on_ech_config_clicked() { + bool ok; + auto txt = QInputDialog::getMultiLineText(this, tr("ECH Config"), "", CACHE.echConfig.join("\n"), &ok); + if (ok) { + CACHE.echConfig = txt.split("\n", Qt::SkipEmptyParts); + if (!CACHE.echConfig.isEmpty()) { + ui->ech_config->setText("Already set"); + } else { + ui->ech_config->setText("Not Set"); + } + } +} + +void EditAdvanced::on_client_cert_clicked() { + bool ok; + auto txt = QInputDialog::getMultiLineText(this, tr("Client Certificate"), "", CACHE.clientCert.join("\n"), &ok); + if (ok) { + CACHE.clientCert = txt.split("\n", Qt::SkipEmptyParts); + if (!CACHE.echConfig.isEmpty()) { + ui->client_cert->setText("Already set"); + } else { + ui->client_cert->setText("Not Set"); + } + } +} + +void EditAdvanced::on_client_key_clicked() { + bool ok; + auto txt = QInputDialog::getMultiLineText(this, tr("Client Key"), "", CACHE.clientKey.join("\n"), &ok); + if (ok) { + CACHE.clientKey = txt.split("\n", Qt::SkipEmptyParts); + if (!CACHE.echConfig.isEmpty()) { + ui->client_key->setText("Already set"); + } else { + ui->client_key->setText("Not Set"); + } + } +} + +void EditAdvanced::on_cert_sha256_clicked() { + bool ok; + auto txt = QInputDialog::getMultiLineText(this, tr("Certificate sha256"), "", CACHE.certSha256.join("\n"), &ok); + if (ok) { + CACHE.certSha256 = txt.split("\n", Qt::SkipEmptyParts); + if (!CACHE.echConfig.isEmpty()) { + ui->cert_sha256->setText("Already set"); + } else { + ui->cert_sha256->setText("Not Set"); + } + } +} \ No newline at end of file