diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36ffe70..f525f37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,12 +58,6 @@ jobs: xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y xmake b -vy --root crossdesk - - name: Decode and save certificate - shell: bash - run: | - mkdir -p certs - echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt - - name: Package run: | chmod +x ./scripts/linux/pkg_amd64.sh @@ -123,12 +117,6 @@ jobs: xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y xmake b -vy --root crossdesk - - name: Decode and save certificate - shell: bash - run: | - mkdir -p certs - echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt - - name: Package run: | chmod +x ${{ matrix.package_script }} @@ -192,12 +180,6 @@ jobs: xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y xmake b -vy crossdesk - - name: Decode and save certificate - shell: bash - run: | - mkdir -p certs - echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt - - name: Package CrossDesk app run: | chmod +x ${{ matrix.package_script }} @@ -301,12 +283,6 @@ jobs: xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true -y xmake b -vy crossdesk - - name: Decode and save certificate - shell: powershell - run: | - New-Item -ItemType Directory -Force -Path certs - [System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}')) - - name: Package shell: pwsh run: | diff --git a/.gitignore b/.gitignore index 379cb48..b7c01b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Xmake cache .xmake/ build/ -certs/ # MacOS Cache .DS_Store diff --git a/README.md b/README.md index 44fdb3f..59401ef 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ sudo docker run -d \ **注意**: - **服务器需开放端口:COTURN_PORT/udp,COTURN_PORT/tcp,MIN_PORT-MAX_PORT/udp,CROSSDESK_SERVER_PORT/tcp。** - 如果不挂载 volume,容器删除后数据会丢失 -- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下 +- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下。由于默认使用的是自签证书,无法保障安全性,建议在云服务商申请正式证书放到该目录下并重启服务。 - 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下 - 日志文件会自动创建并持久化到宿主机的 `/var/log/crossdesk/` 路径下 @@ -232,16 +232,30 @@ sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk ### 客户端 1. 点击右上角设置进入设置页面。

-image

+image
-2. 点击点击`自托管服务器配置`按钮。

-image

+2. 点击`自托管服务器配置`按钮。

+image
-3. 输入`服务器地址`(**EXTERNAL_IP**)、`信令服务端口`(**CROSSDESK_SERVER_PORT**)、`中继服务端口`(**COTURN_PORT**)。

-image

+3. 输入`服务器地址`(**EXTERNAL_IP**)、`信令服务端口`(**CROSSDESK_SERVER_PORT**)、`中继服务端口`(**COTURN_PORT**),点击确认按钮。 + +4. 勾选`自托管服务器配置`选项,点击确认按钮保存设置。如果服务端使用的是正式证书,则到此步骤为止,客户端即可显示已连接服务器。 -4. 后续如果自托管服务器被重置或因其他原因导致证书更换,可以点击`重置证书指纹`按钮重置客户端保存的证书指纹。

-image

+5. 如果使用默认证书(正式证书忽略此步骤),则需要将服务端`/var/lib/crossdesk/certs/`目录下的`api.crossdesk.cn_root.crt`自签根证书下载到运行客户端的机器,并执行下述命令安装证书: + +Windows 平台使用**管理员权限**打开 PowerShell 执行 +``` +certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt" +``` +Linux +``` +sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt +sudo update-ca-certificates +``` +macOS +``` +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt +``` ### Web 客户端 详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。 diff --git a/README_EN.md b/README_EN.md index 4d823b8..62ebc82 100644 --- a/README_EN.md +++ b/README_EN.md @@ -222,7 +222,7 @@ sudo docker run -d \ **Notes** - **The server must open the following ports: COTURN_PORT/udp, COTURN_PORT/tcp, MIN_PORT–MAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.** - If you don’t mount volumes, all data will be lost when the container is removed. -- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`. +- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.As the default certificates are self-signed and cannot guarantee security, it is strongly recommended to apply for a trusted certificate from a cloud provider, deploy it to this directory, and restart the service. - The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`. - Log files will be created and stored at `/var/log/crossdesk/`. @@ -243,16 +243,31 @@ Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/y ### Client Side 1. Click the settings icon in the top-right corner to enter the settings page.

-image

+image
2. Click `Self-Hosted Server Configuration` button.

-image

+image
-3. Enter the `Server Address` (**EXTERNAL_IP**), `Signaling Service Port` (**CROSSDESK_SERVER_PORT**), and `Relay Service Port` (**COTURN_PORT**).

-image

+3. Enter the `Server Address` (**EXTERNAL_IP**), `Signaling Service Port` (**CROSSDESK_SERVER_PORT**), and `Relay Service Port` (**COTURN_PORT**) and click OK button. + +4. Check the `Self-hosted server configuration` option and click the OK button to save the settings. If the server is using a valid (official) certificate, the process ends here and the client will show that it is connected to the server. + +5. If the default certificate is used (skip this step if an official certificate is used), download the self-signed root certificate `api.crossdesk.cn_root.crt` from the server directory /var/lib/crossdesk/certs/ to the machine running the client, and install the certificate by executing the following command: + +On Windows, open PowerShell with **administrator privileges** and execute: +``` +certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt" +``` +Linux +``` +sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt +sudo update-ca-certificates +``` +macOS +``` +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt +``` -4. If the self-hosted server is later reset or the certificate is replaced for any reason, you can click the `Reset Certificate Fingerprint` button to clear the certificate fingerprint saved on the client.

-image

### Web Client See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。 diff --git a/scripts/linux/pkg_amd64.sh b/scripts/linux/pkg_amd64.sh index 2c3e370..bee1871 100644 --- a/scripts/linux/pkg_amd64.sh +++ b/scripts/linux/pkg_amd64.sh @@ -15,21 +15,18 @@ DEB_VERSION="${APP_VERSION#v}" DEB_DIR="${PKG_NAME}-${DEB_VERSION}" DEBIAN_DIR="$DEB_DIR/DEBIAN" BIN_DIR="$DEB_DIR/usr/bin" -CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor" DESKTOP_DIR="$DEB_DIR/usr/share/applications" rm -rf "$DEB_DIR" -mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR" +mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$DESKTOP_DIR" cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME" chmod +x "$BIN_DIR/$PKG_NAME" ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME" -cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt" - for size in 16 24 32 48 64 96 128 256; do mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps" cp "icons/linux/crossdesk_${size}x${size}.png" \ @@ -71,7 +68,6 @@ if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then rm -f /usr/bin/$PKG_NAME || true rm -f /usr/bin/$APP_NAME || true rm -f /usr/share/applications/$PKG_NAME.desktop || true - rm -rf /opt/$PKG_NAME/certs || true for size in 16 24 32 48 64 96 128 256; do rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true done @@ -85,32 +81,9 @@ cat > "$DEBIAN_DIR/postinst" << 'EOF' #!/bin/bash set -e -CERT_SRC="/opt/crossdesk/certs" -CERT_FILE="crossdesk.cn_root.crt" - -for user_home in /home/*; do - [ -d "$user_home" ] || continue - username=$(basename "$user_home") - config_dir="$user_home/.config/CrossDesk/certs" - target="$config_dir/$CERT_FILE" - - if [ ! -f "$target" ]; then - mkdir -p "$config_dir" || true - cp "$CERT_SRC/$CERT_FILE" "$target" || true - chown -R "$username:$username" "$user_home/.config/CrossDesk" || true - echo "✔ Installed cert for $username at $target" - fi -done - -if [ -d "/root" ]; then - config_dir="/root/.config/CrossDesk/certs" - mkdir -p "$config_dir" || true - cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true - chown -R root:root /root/.config/CrossDesk || true -fi - exit 0 EOF + chmod +x "$DEBIAN_DIR/postinst" dpkg-deb --build "$DEB_DIR" diff --git a/scripts/linux/pkg_arm64.sh b/scripts/linux/pkg_arm64.sh index baf6af5..3e14cff 100644 --- a/scripts/linux/pkg_arm64.sh +++ b/scripts/linux/pkg_arm64.sh @@ -15,21 +15,18 @@ DEB_VERSION="${APP_VERSION#v}" DEB_DIR="${PKG_NAME}-${DEB_VERSION}" DEBIAN_DIR="$DEB_DIR/DEBIAN" BIN_DIR="$DEB_DIR/usr/bin" -CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor" DESKTOP_DIR="$DEB_DIR/usr/share/applications" rm -rf "$DEB_DIR" -mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR" +mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$DESKTOP_DIR" cp build/linux/arm64/release/crossdesk "$BIN_DIR" chmod +x "$BIN_DIR/$PKG_NAME" ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME" -cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt" - for size in 16 24 32 48 64 96 128 256; do mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps" cp "icons/linux/crossdesk_${size}x${size}.png" \ @@ -70,7 +67,6 @@ if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then rm -f /usr/bin/$PKG_NAME || true rm -f /usr/bin/$APP_NAME || true rm -f /usr/share/applications/$PKG_NAME.desktop || true - rm -rf /opt/$PKG_NAME/certs || true for size in 16 24 32 48 64 96 128 256; do rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true done @@ -84,30 +80,6 @@ cat > "$DEBIAN_DIR/postinst" << 'EOF' #!/bin/bash set -e -CERT_SRC="/opt/crossdesk/certs" -CERT_FILE="crossdesk.cn_root.crt" - -for user_home in /home/*; do - [ -d "$user_home" ] || continue - username=$(basename "$user_home") - config_dir="$user_home/.config/CrossDesk/certs" - target="$config_dir/$CERT_FILE" - - if [ ! -f "$target" ]; then - mkdir -p "$config_dir" || true - cp "$CERT_SRC/$CERT_FILE" "$target" || true - chown -R "$username:$username" "$user_home/.config/CrossDesk" || true - echo "✔ Installed cert for $username at $target" - fi -done - -if [ -d "/root" ]; then - config_dir="/root/.config/CrossDesk/certs" - mkdir -p "$config_dir" || true - cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true - chown -R root:root /root/.config/CrossDesk || true -fi - exit 0 EOF diff --git a/scripts/macosx/pkg_arm64.sh b/scripts/macosx/pkg_arm64.sh index 6c4b193..37321ff 100755 --- a/scripts/macosx/pkg_arm64.sh +++ b/scripts/macosx/pkg_arm64.sh @@ -11,9 +11,6 @@ IDENTIFIER="cn.crossdesk.app" ICON_PATH="icons/macos/crossdesk.icns" MACOS_MIN_VERSION="10.12" -CERTS_SOURCE="certs" -CERT_NAME="crossdesk.cn_root.crt" - APP_BUNDLE="${APP_NAME_UPPER}.app" CONTENTS_DIR="${APP_BUNDLE}/Contents" MACOS_DIR="${CONTENTS_DIR}/MacOS" @@ -98,11 +95,6 @@ IDENTIFIER="cn.crossdesk.app" USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) -# 复制证书文件 -DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" -mkdir -p "$DEST" -cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" - # 清除应用的权限授权,以便重新授权 # 使用 tccutil 重置录屏权限和辅助功能权限 if command -v tccutil >/dev/null 2>&1; then @@ -140,17 +132,8 @@ EOF chmod +x build_pkg_scripts/postinstall -pkgbuild \ - --root "${CERTS_SOURCE}" \ - --identifier "${IDENTIFIER}.certs" \ - --version "${APP_VERSION}" \ - --install-location "/Library/Application Support/CrossDesk/certs" \ - --scripts build_pkg_scripts \ - build_pkg_temp/${APP_NAME}-certs.pkg - productbuild \ --package build_pkg_temp/${APP_NAME}-component.pkg \ - --package build_pkg_temp/${APP_NAME}-certs.pkg \ "${PKG_NAME}" echo "PKG package created: ${PKG_NAME}" diff --git a/scripts/macosx/pkg_x64.sh b/scripts/macosx/pkg_x64.sh index 35c5e5e..f6717bb 100755 --- a/scripts/macosx/pkg_x64.sh +++ b/scripts/macosx/pkg_x64.sh @@ -11,9 +11,6 @@ IDENTIFIER="cn.crossdesk.app" ICON_PATH="icons/macos/crossdesk.icns" MACOS_MIN_VERSION="10.12" -CERTS_SOURCE="certs" -CERT_NAME="crossdesk.cn_root.crt" - APP_BUNDLE="${APP_NAME_UPPER}.app" CONTENTS_DIR="${APP_BUNDLE}/Contents" MACOS_DIR="${CONTENTS_DIR}/MacOS" @@ -98,11 +95,6 @@ IDENTIFIER="cn.crossdesk.app" USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) -# 复制证书文件 -DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" -mkdir -p "$DEST" -cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" - # 清除应用的权限授权,以便重新授权 # 使用 tccutil 重置录屏权限和辅助功能权限 if command -v tccutil >/dev/null 2>&1; then @@ -140,17 +132,8 @@ EOF chmod +x build_pkg_scripts/postinstall -pkgbuild \ - --root "${CERTS_SOURCE}" \ - --identifier "${IDENTIFIER}.certs" \ - --version "${APP_VERSION}" \ - --install-location "/Library/Application Support/CrossDesk/certs" \ - --scripts build_pkg_scripts \ - build_pkg_temp/${APP_NAME}-certs.pkg - productbuild \ --package build_pkg_temp/${APP_NAME}-component.pkg \ - --package build_pkg_temp/${APP_NAME}-certs.pkg \ "${PKG_NAME}" echo "PKG package created: ${PKG_NAME}" diff --git a/scripts/windows/nsis_script.nsi b/scripts/windows/nsis_script.nsi index e98706f..52eadec 100644 --- a/scripts/windows/nsis_script.nsi +++ b/scripts/windows/nsis_script.nsi @@ -12,9 +12,6 @@ ; Installer icon path !define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico" -; Certificate path -!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt" - ; Compression settings SetCompressor /FINAL lzma @@ -99,11 +96,6 @@ Section -Post ExecWait '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\mt.exe" -manifest "$INSTDIR\crossdesk.manifest" -outputresource:"$INSTDIR\crossdesk.exe";1' SectionEnd -Section "Cert" - SetOutPath "$APPDATA\CrossDesk\certs" - File /r "${CERT_FILE}" -SectionEnd - Section -AdditionalIcons ; Desktop shortcut CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico" diff --git a/src/app/main.cpp b/src/app/main.cpp index 4e3a35a..dd8d5dd 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -35,11 +35,8 @@ int main(int argc, char* argv[]) { bool enable_daemon = false; auto path_manager = std::make_unique("CrossDesk"); if (path_manager) { - std::string cert_path = - (path_manager->GetCertPath() / "crossdesk.cn_root.crt").string(); std::string cache_path = path_manager->GetCachePath().string(); - crossdesk::ConfigCenter config_center(cache_path + "/config.ini", - cert_path); + crossdesk::ConfigCenter config_center(cache_path + "/config.ini"); enable_daemon = config_center.IsEnableDaemon(); } diff --git a/src/config_center/config_center.cpp b/src/config_center/config_center.cpp index 3709b84..f99fa29 100644 --- a/src/config_center/config_center.cpp +++ b/src/config_center/config_center.cpp @@ -5,11 +5,8 @@ namespace crossdesk { -ConfigCenter::ConfigCenter(const std::string& config_path, - const std::string& cert_file_path) - : config_path_(config_path), - cert_file_path_(cert_file_path), - cert_file_path_default_(cert_file_path) { +ConfigCenter::ConfigCenter(const std::string& config_path) + : config_path_(config_path) { ini_.SetUnicode(true); Load(); } @@ -70,71 +67,6 @@ int ConfigCenter::Load() { } else { coturn_server_port_ = 0; } - const char* cert_file_path_value = - ini_.GetValue(section_, "cert_file_path", nullptr); - if (cert_file_path_value != nullptr && strlen(cert_file_path_value) > 0) { - cert_file_path_ = cert_file_path_value; - } else { - cert_file_path_ = ""; - } - const char* cert_fingerprint_value = - ini_.GetValue(section_, "cert_fingerprint", nullptr); - if (cert_fingerprint_value != nullptr && strlen(cert_fingerprint_value) > 0) { - cert_fingerprint_ = cert_fingerprint_value; - } else { - cert_fingerprint_ = ""; - } - const char* cert_fingerprint_server_host_value = - ini_.GetValue(section_, "cert_fingerprint_server_host", nullptr); - if (cert_fingerprint_server_host_value != nullptr && - strlen(cert_fingerprint_server_host_value) > 0) { - cert_fingerprint_server_host_ = cert_fingerprint_server_host_value; - } else { - cert_fingerprint_server_host_ = ""; - } - - const char* default_cert_fingerprint_value = - ini_.GetValue(section_, "default_cert_fingerprint", nullptr); - if (default_cert_fingerprint_value != nullptr && - strlen(default_cert_fingerprint_value) > 0) { - default_cert_fingerprint_ = default_cert_fingerprint_value; - } else { - default_cert_fingerprint_ = ""; - } - const char* default_cert_fingerprint_server_host_value = - ini_.GetValue(section_, "default_cert_fingerprint_server_host", nullptr); - if (default_cert_fingerprint_server_host_value != nullptr && - strlen(default_cert_fingerprint_server_host_value) > 0) { - default_cert_fingerprint_server_host_ = - default_cert_fingerprint_server_host_value; - } else { - default_cert_fingerprint_server_host_ = ""; - } - - if (enable_self_hosted_ && !cert_fingerprint_.empty() && - !cert_fingerprint_server_host_.empty() && - signal_server_host_ != cert_fingerprint_server_host_) { - LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint", - cert_fingerprint_server_host_, signal_server_host_); - cert_fingerprint_.clear(); - cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "cert_fingerprint", false); - ini_.Delete(section_, "cert_fingerprint_server_host", false); - ini_.SaveFile(config_path_.c_str()); - } - - if (!enable_self_hosted_ && !default_cert_fingerprint_.empty() && - !default_cert_fingerprint_server_host_.empty() && - signal_server_host_default_ != default_cert_fingerprint_server_host_) { - LOG_INFO( - "Default server IP changed from {} to {}, clearing old fingerprint", - default_cert_fingerprint_server_host_, signal_server_host_default_); - default_cert_fingerprint_.clear(); - default_cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "default_cert_fingerprint", false); - ini_.Delete(section_, "default_cert_fingerprint_server_host", false); - ini_.SaveFile(config_path_.c_str()); - } enable_autostart_ = ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_); @@ -165,19 +97,6 @@ int ConfigCenter::Save() { static_cast(signal_server_port_)); ini_.SetLongValue(section_, "coturn_server_port", static_cast(coturn_server_port_)); - ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); - if (!cert_fingerprint_.empty()) { - ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str()); - ini_.SetValue(section_, "cert_fingerprint_server_host", - cert_fingerprint_server_host_.c_str()); - } - } - - if (!default_cert_fingerprint_.empty()) { - ini_.SetValue(section_, "default_cert_fingerprint", - default_cert_fingerprint_.c_str()); - ini_.SetValue(section_, "default_cert_fingerprint_server_host", - default_cert_fingerprint_server_host_.c_str()); } ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_); @@ -270,15 +189,6 @@ int ConfigCenter::SetSrtp(bool enable_srtp) { } int ConfigCenter::SetServerHost(const std::string& signal_server_host) { - if (enable_self_hosted_ && !cert_fingerprint_.empty() && - signal_server_host != signal_server_host_) { - LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint", - signal_server_host_, signal_server_host); - cert_fingerprint_.clear(); - cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "cert_fingerprint", false); - ini_.Delete(section_, "cert_fingerprint_server_host", false); - } signal_server_host_ = signal_server_host; ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str()); SI_Error rc = ini_.SaveFile(config_path_.c_str()); @@ -310,67 +220,6 @@ int ConfigCenter::SetCoturnServerPort(int coturn_server_port) { return 0; } -int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) { - cert_file_path_ = cert_file_path; - ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - -int ConfigCenter::SetCertFingerprint(const std::string& fingerprint) { - cert_fingerprint_ = fingerprint; - cert_fingerprint_server_host_ = signal_server_host_; - ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str()); - ini_.SetValue(section_, "cert_fingerprint_server_host", - cert_fingerprint_server_host_.c_str()); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - -int ConfigCenter::SetDefaultCertFingerprint(const std::string& fingerprint) { - default_cert_fingerprint_ = fingerprint; - default_cert_fingerprint_server_host_ = signal_server_host_default_; - ini_.SetValue(section_, "default_cert_fingerprint", - default_cert_fingerprint_.c_str()); - ini_.SetValue(section_, "default_cert_fingerprint_server_host", - default_cert_fingerprint_server_host_.c_str()); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - -int ConfigCenter::ClearCertFingerprint() { - cert_fingerprint_.clear(); - cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "cert_fingerprint", false); - ini_.Delete(section_, "cert_fingerprint_server_host", false); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - -int ConfigCenter::ClearDefaultCertFingerprint() { - default_cert_fingerprint_.clear(); - default_cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "default_cert_fingerprint", false); - ini_.Delete(section_, "default_cert_fingerprint_server_host", false); - SI_Error rc = ini_.SaveFile(config_path_.c_str()); - if (rc < 0) { - return -1; - } - return 0; -} - int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { enable_self_hosted_ = enable_self_hosted; ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_); @@ -397,45 +246,12 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { coturn_server_port_ = static_cast( ini_.GetLongValue(section_, "coturn_server_port", 0)); } - const char* cert_file_path_value = - ini_.GetValue(section_, "cert_file_path", nullptr); - if (cert_file_path_value != nullptr && strlen(cert_file_path_value) > 0) { - cert_file_path_ = cert_file_path_value; - } - const char* cert_fingerprint_value = - ini_.GetValue(section_, "cert_fingerprint", nullptr); - if (cert_fingerprint_value != nullptr && - strlen(cert_fingerprint_value) > 0) { - cert_fingerprint_ = cert_fingerprint_value; - } - const char* cert_fingerprint_server_host_value = - ini_.GetValue(section_, "cert_fingerprint_server_host", nullptr); - if (cert_fingerprint_server_host_value != nullptr && - strlen(cert_fingerprint_server_host_value) > 0) { - cert_fingerprint_server_host_ = cert_fingerprint_server_host_value; - } - - if (!cert_fingerprint_.empty() && !cert_fingerprint_server_host_.empty() && - signal_server_host_ != cert_fingerprint_server_host_) { - LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint", - cert_fingerprint_server_host_, signal_server_host_); - cert_fingerprint_.clear(); - cert_fingerprint_server_host_.clear(); - ini_.Delete(section_, "cert_fingerprint", false); - ini_.Delete(section_, "cert_fingerprint_server_host", false); - } ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str()); ini_.SetLongValue(section_, "signal_server_port", static_cast(signal_server_port_)); ini_.SetLongValue(section_, "coturn_server_port", static_cast(coturn_server_port_)); - ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); - if (!cert_fingerprint_.empty()) { - ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str()); - ini_.SetValue(section_, "cert_fingerprint_server_host", - cert_fingerprint_server_host_.c_str()); - } } SI_Error rc = ini_.SaveFile(config_path_.c_str()); @@ -523,16 +339,6 @@ int ConfigCenter::GetSignalServerPort() const { return signal_server_port_; } int ConfigCenter::GetCoturnServerPort() const { return coturn_server_port_; } -std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; } - -std::string ConfigCenter::GetCertFingerprint() const { - return cert_fingerprint_; -} - -std::string ConfigCenter::GetDefaultCertFingerprint() const { - return default_cert_fingerprint_; -} - std::string ConfigCenter::GetDefaultServerHost() const { return signal_server_host_default_; } @@ -545,10 +351,6 @@ int ConfigCenter::GetDefaultCoturnServerPort() const { return coturn_server_port_default_; } -std::string ConfigCenter::GetDefaultCertFilePath() const { - return cert_file_path_default_; -} - bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; } bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; } diff --git a/src/config_center/config_center.h b/src/config_center/config_center.h index 4e556a6..fe077da 100644 --- a/src/config_center/config_center.h +++ b/src/config_center/config_center.h @@ -21,9 +21,7 @@ class ConfigCenter { enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 }; public: - explicit ConfigCenter( - const std::string& config_path = "config.ini", - const std::string& cert_file_path = "crossdesk.cn_root.crt"); + explicit ConfigCenter(const std::string& config_path = "config.ini"); ~ConfigCenter(); // write config @@ -37,11 +35,6 @@ class ConfigCenter { int SetServerHost(const std::string& signal_server_host); int SetServerPort(int signal_server_port); int SetCoturnServerPort(int coturn_server_port); - int SetCertFilePath(const std::string& cert_file_path); - int SetCertFingerprint(const std::string& fingerprint); - int SetDefaultCertFingerprint(const std::string& fingerprint); - int ClearCertFingerprint(); - int ClearDefaultCertFingerprint(); int SetSelfHosted(bool enable_self_hosted); int SetMinimizeToTray(bool enable_minimize_to_tray); int SetAutostart(bool enable_autostart); @@ -59,13 +52,9 @@ class ConfigCenter { std::string GetSignalServerHost() const; int GetSignalServerPort() const; int GetCoturnServerPort() const; - std::string GetCertFilePath() const; - std::string GetCertFingerprint() const; - std::string GetDefaultCertFingerprint() const; std::string GetDefaultServerHost() const; int GetDefaultSignalServerPort() const; int GetDefaultCoturnServerPort() const; - std::string GetDefaultCertFilePath() const; bool IsSelfHosted() const; bool IsMinimizeToTray() const; bool IsEnableAutostart() const; @@ -92,12 +81,6 @@ class ConfigCenter { int server_port_default_ = 9099; int coturn_server_port_ = 0; int coturn_server_port_default_ = 3478; - std::string cert_file_path_ = ""; - std::string cert_file_path_default_ = ""; - std::string cert_fingerprint_ = ""; - std::string cert_fingerprint_server_host_ = ""; - std::string default_cert_fingerprint_ = ""; - std::string default_cert_fingerprint_server_host_ = ""; bool enable_self_hosted_ = false; bool enable_minimize_to_tray_ = false; bool enable_autostart_ = false; diff --git a/src/gui/assets/localization/localization.h b/src/gui/assets/localization/localization.h index 21b9ae3..f517d50 100644 --- a/src/gui/assets/localization/localization.h +++ b/src/gui/assets/localization/localization.h @@ -112,13 +112,8 @@ static std::vector self_hosted_server_port = { reinterpret_cast(u8"信令服务端口:"), "Signal Service Port:"}; static std::vector self_hosted_server_coturn_server_port = { reinterpret_cast(u8"中继服务端口:"), "Relay Service Port:"}; -static std::vector self_hosted_server_certificate_path = { - reinterpret_cast(u8"证书文件路径:"), "Certificate File Path:"}; static std::vector select_a_file = { reinterpret_cast(u8"请选择文件"), "Please select a file"}; -static std::vector reset_cert_fingerprint = { - reinterpret_cast(u8"重置证书指纹"), - "Reset Certificate Fingerprint"}; static std::vector ok = {reinterpret_cast(u8"确认"), "OK"}; static std::vector cancel = { diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 18dbaff..2adf315 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -672,13 +672,11 @@ int Render::CreateConnectionPeer() { std::string signal_server_ip; int signal_server_port; int coturn_server_port; - std::string tls_cert_fingerprint; if (config_center_->IsSelfHosted()) { signal_server_ip = config_center_->GetSignalServerHost(); signal_server_port = config_center_->GetSignalServerPort(); coturn_server_port = config_center_->GetCoturnServerPort(); - tls_cert_fingerprint = config_center_->GetCertFingerprint(); std::string current_self_hosted_ip = config_center_->GetSignalServerHost(); bool use_cached_id = false; @@ -739,7 +737,6 @@ int Render::CreateConnectionPeer() { signal_server_ip = config_center_->GetDefaultServerHost(); signal_server_port = config_center_->GetDefaultSignalServerPort(); coturn_server_port = config_center_->GetDefaultCoturnServerPort(); - tls_cert_fingerprint = config_center_->GetDefaultCertFingerprint(); params_.user_id = client_id_with_password_; } @@ -763,7 +760,6 @@ int Render::CreateConnectionPeer() { } else { coturn_server_port_self_[0] = '\0'; } - tls_cert_path_self_ = config_center_->GetCertFilePath(); // peer config strncpy((char*)params_.signal_server_ip, signal_server_ip.c_str(), @@ -784,30 +780,6 @@ int Render::CreateConnectionPeer() { strncpy((char*)params_.turn_server_password, "crossdeskpw", sizeof(params_.turn_server_password) - 1); params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0'; - strncpy(params_.tls_cert_fingerprint, tls_cert_fingerprint.c_str(), - sizeof(params_.tls_cert_fingerprint) - 1); - params_.tls_cert_fingerprint[sizeof(params_.tls_cert_fingerprint) - 1] = '\0'; - - if (config_center_->IsSelfHosted()) { - params_.on_cert_fingerprint = [](const char* fingerprint, void* user_data) { - Render* render = static_cast(user_data); - if (render && render->config_center_) { - render->config_center_->SetCertFingerprint(fingerprint); - LOG_INFO("Saved self-hosted certificate fingerprint: {}", fingerprint); - } - }; - params_.fingerprint_user_data = this; - } else { - params_.on_cert_fingerprint = [](const char* fingerprint, void* user_data) { - Render* render = static_cast(user_data); - if (render && render->config_center_) { - render->config_center_->SetDefaultCertFingerprint(fingerprint); - LOG_INFO("Saved default server certificate fingerprint: {}", - fingerprint); - } - }; - params_.fingerprint_user_data = this; - } strncpy(params_.log_path, dll_log_path_.c_str(), sizeof(params_.log_path) - 1); @@ -1458,13 +1430,11 @@ int Render::Run() { path_manager_ = std::make_unique("CrossDesk"); if (path_manager_) { - cert_path_ = - (path_manager_->GetCertPath() / "crossdesk.cn_root.crt").string(); exec_log_path_ = path_manager_->GetLogPath().string(); dll_log_path_ = path_manager_->GetLogPath().string(); cache_path_ = path_manager_->GetCachePath().string(); config_center_ = - std::make_unique(cache_path_ + "/config.ini", cert_path_); + std::make_unique(cache_path_ + "/config.ini"); strncpy(signal_server_ip_self_, config_center_->GetSignalServerHost().c_str(), sizeof(signal_server_ip_self_) - 1); @@ -1478,8 +1448,6 @@ int Render::Run() { } else { signal_server_port_self_[0] = '\0'; } - strncpy(cert_file_path_, cert_path_.c_str(), sizeof(cert_file_path_) - 1); - cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; } else { std::cerr << "Failed to create PathManager" << std::endl; return -1; diff --git a/src/gui/render.h b/src/gui/render.h index c138eea..646c3a6 100644 --- a/src/gui/render.h +++ b/src/gui/render.h @@ -226,7 +226,6 @@ class Render { int RecentConnectionsWindow(); int SettingWindow(); int SelfHostedServerWindow(); - int ShowSimpleFileBrowser(); int ControlWindow(std::shared_ptr& props); int ControlBar(std::shared_ptr& props); int AboutWindow(); @@ -381,7 +380,6 @@ class Render { ConfigCenter::LANGUAGE localization_language_ = ConfigCenter::LANGUAGE::CHINESE; std::unique_ptr path_manager_; - std::string cert_path_; std::string exec_log_path_; std::string dll_log_path_; std::string cache_path_; @@ -633,7 +631,6 @@ class Render { char signal_server_ip_[256] = "api.crossdesk.cn"; char signal_server_port_[6] = "9099"; char coturn_server_port_[6] = "3478"; - char cert_file_path_[256] = ""; bool enable_self_hosted_ = false; int language_button_value_last_ = 0; int video_quality_button_value_last_ = 0; @@ -652,7 +649,6 @@ class Render { char signal_server_ip_self_[256] = ""; char signal_server_port_self_[6] = ""; char coturn_server_port_self_[6] = ""; - std::string tls_cert_path_self_ = ""; bool settings_window_pos_reset_ = true; bool self_hosted_server_config_window_pos_reset_ = true; std::string selected_current_file_path_ = ""; diff --git a/src/gui/render_callback.cpp b/src/gui/render_callback.cpp index f710d67..6ffa511 100644 --- a/src/gui/render_callback.cpp +++ b/src/gui/render_callback.cpp @@ -613,10 +613,6 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id, render->signal_connected_ = false; } else if (SignalStatus::SignalServerClosed == status) { render->signal_connected_ = false; - } else if (SignalStatus::SignalFingerprintMismatch == status) { - render->signal_connected_ = false; - LOG_ERROR("[{}] signal server fingerprint mismatch", client_id); - render->config_center_->ClearDefaultCertFingerprint(); } } else { if (client_id.rfind("C-", 0) != 0) { @@ -644,9 +640,6 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id, props->signal_connected_ = false; } else if (SignalStatus::SignalServerClosed == status) { props->signal_connected_ = false; - } else if (SignalStatus::SignalFingerprintMismatch == status) { - props->signal_connected_ = false; - LOG_ERROR("[{}] signal server fingerprint mismatch", remote_id); } } } diff --git a/src/gui/windows/server_settings_window.cpp b/src/gui/windows/server_settings_window.cpp index a5caaf7..b3bd8c5 100644 --- a/src/gui/windows/server_settings_window.cpp +++ b/src/gui/windows/server_settings_window.cpp @@ -28,98 +28,6 @@ std::vector GetRootEntries() { return roots; } -int Render::ShowSimpleFileBrowser() { - std::string display_text; - - if (selected_current_file_path_.empty()) { - selected_current_file_path_ = std::filesystem::current_path().string(); - } - - if (!tls_cert_path_self_.empty()) { - display_text = - std::filesystem::path(tls_cert_path_self_).filename().string(); - } else if (selected_current_file_path_ != "Root") { - display_text = - std::filesystem::path(selected_current_file_path_).filename().string(); - if (display_text.empty()) { - display_text = selected_current_file_path_; - } - } - - if (display_text.empty()) { - display_text = - localization::select_a_file[localization_language_index_].c_str(); - } - - if (show_file_browser_) { - ImGui::PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); - - float fixed_width = title_bar_button_width_ * 3.8f; - ImGui::SetNextItemWidth(fixed_width); - ImGui::SetNextWindowSizeConstraints(ImVec2(fixed_width, 0), - ImVec2(fixed_width, 100.0f)); - - if (ImGui::BeginCombo("##select_a_file", display_text.c_str(), 0)) { - ImGui::SetWindowFontScale(0.5f); - bool file_selected = false; - - auto roots = GetRootEntries(); - if (selected_current_file_path_ == "Root" || - !std::filesystem::exists(selected_current_file_path_) || - !std::filesystem::is_directory(selected_current_file_path_)) { - for (const auto& root : roots) { - if (ImGui::Selectable(root.c_str())) { - selected_current_file_path_ = root; - tls_cert_path_self_.clear(); - } - } - } else { - std::filesystem::path p(selected_current_file_path_); - - if (ImGui::Selectable("..")) { - if (std::find(roots.begin(), roots.end(), - selected_current_file_path_) != roots.end()) { - selected_current_file_path_ = "Root"; - } else if (p.has_parent_path()) { - selected_current_file_path_ = p.parent_path().string(); - } else { - selected_current_file_path_ = "Root"; - } - tls_cert_path_self_.clear(); - } - - try { - for (const auto& entry : std::filesystem::directory_iterator( - selected_current_file_path_)) { - std::string name = entry.path().filename().string(); - if (entry.is_directory()) { - if (ImGui::Selectable(name.c_str())) { - selected_current_file_path_ = entry.path().string(); - tls_cert_path_self_.clear(); - } - } else { - if (ImGui::Selectable(name.c_str())) { - tls_cert_path_self_ = entry.path().string(); - file_selected = true; - show_file_browser_ = false; - } - } - } - } catch (const std::exception& e) { - ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what()); - } - } - - ImGui::EndCombo(); - } - ImGui::PopItemFlag(); - } else { - show_file_browser_ = true; - } - - return 0; -} - int Render::SelfHostedServerWindow() { ImGuiIO& io = ImGui::GetIO(); if (show_self_hosted_server_config_window_) { @@ -128,12 +36,12 @@ int Render::SelfHostedServerWindow() { ImGui::SetNextWindowPos( ImVec2(io.DisplaySize.x * 0.298f, io.DisplaySize.y * 0.25f)); ImGui::SetNextWindowSize( - ImVec2(io.DisplaySize.x * 0.407f, io.DisplaySize.y * 0.41f)); + ImVec2(io.DisplaySize.x * 0.407f, io.DisplaySize.y * 0.35f)); } else { ImGui::SetNextWindowPos( ImVec2(io.DisplaySize.x * 0.27f, io.DisplaySize.y * 0.3f)); ImGui::SetNextWindowSize( - ImVec2(io.DisplaySize.x * 0.465f, io.DisplaySize.y * 0.41f)); + ImVec2(io.DisplaySize.x * 0.465f, io.DisplaySize.y * 0.35f)); } self_hosted_server_config_window_pos_reset_ = false; @@ -212,35 +120,6 @@ int Render::SelfHostedServerWindow() { IM_ARRAYSIZE(coturn_server_port_self_)); } - ImGui::Separator(); - - // { - // ImGui::AlignTextToFramePadding(); - // ImGui::Text( - // "%s", - // localization::reset_cert_fingerprint[localization_language_index_] - // .c_str()); - // ImGui::SameLine(); - // if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { - // ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f); - // } else { - // ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f); - // } - // ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f); - - // ShowSimpleFileBrowser(); - // } - - { - ImGui::AlignTextToFramePadding(); - if (ImGui::Button(localization::reset_cert_fingerprint - [localization_language_index_] - .c_str())) { - config_center_->ClearCertFingerprint(); - LOG_INFO("Certificate fingerprint cleared by user"); - } - } - if (stream_window_inited_) { ImGui::EndDisabled(); } @@ -263,7 +142,6 @@ int Render::SelfHostedServerWindow() { config_center_->SetServerHost(signal_server_ip_self_); config_center_->SetServerPort(atoi(signal_server_port_self_)); config_center_->SetCoturnServerPort(atoi(coturn_server_port_self_)); - config_center_->SetCertFilePath(tls_cert_path_self_); strncpy(signal_server_ip_, signal_server_ip_self_, sizeof(signal_server_ip_) - 1); signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0'; @@ -273,9 +151,6 @@ int Render::SelfHostedServerWindow() { strncpy(coturn_server_port_, coturn_server_port_self_, sizeof(coturn_server_port_) - 1); coturn_server_port_[sizeof(coturn_server_port_) - 1] = '\0'; - strncpy(cert_file_path_, tls_cert_path_self_.c_str(), - sizeof(cert_file_path_) - 1); - cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; self_hosted_server_config_window_pos_reset_ = true; } @@ -306,7 +181,6 @@ int Render::SelfHostedServerWindow() { } else { coturn_server_port_self_[0] = '\0'; } - tls_cert_path_self_ = config_center_->GetCertFilePath(); } ImGui::SetWindowFontScale(1.0f); diff --git a/src/path_manager/path_manager.cpp b/src/path_manager/path_manager.cpp index 7e36f89..1d629e9 100644 --- a/src/path_manager/path_manager.cpp +++ b/src/path_manager/path_manager.cpp @@ -40,20 +40,6 @@ std::filesystem::path PathManager::GetLogPath() { #endif } -std::filesystem::path PathManager::GetCertPath() { -#ifdef _WIN32 - // %APPDATA%\AppName\Certs - return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs"; -#elif __APPLE__ - // $HOME/Library/Application Support/AppName/certs - return GetHome() + "/Library/Application Support/" + app_name_ + "/certs"; -#else - // $XDG_CONFIG_HOME/AppName/certs - return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / - app_name_ / "certs"; -#endif -} - bool PathManager::CreateDirectories(const std::filesystem::path& p) { std::error_code ec; bool created = std::filesystem::create_directories(p, ec); diff --git a/src/path_manager/path_manager.h b/src/path_manager/path_manager.h index 21f9b0c..1adb308 100644 --- a/src/path_manager/path_manager.h +++ b/src/path_manager/path_manager.h @@ -26,8 +26,6 @@ class PathManager { std::filesystem::path GetLogPath(); - std::filesystem::path GetCertPath(); - bool CreateDirectories(const std::filesystem::path& p); private: diff --git a/submodules/minirtc b/submodules/minirtc index 27f7210..28ca08b 160000 --- a/submodules/minirtc +++ b/submodules/minirtc @@ -1 +1 @@ -Subproject commit 27f721015b8ca9628f0337b8258cf3cae758aba1 +Subproject commit 28ca08bce9c67712a296044a85d147c4a0d1b9bd