Compare commits

...

2 Commits

Author SHA1 Message Date
Zhenyu FU
29e1852a68 fix: ci: macos: allow signed but not notarized dmg (#15530)
* fix: ci: macos: allow signed but not notarized dmg
Signed-off-by: Zhenyu FU <ysfcore@outlook.com>

* fix: ci: macos: add pre-check for macos identity
Signed-off-by: Zhenyu FU <ysfcore@outlook.com>

* merge notarize checking to existing steps
Signed-off-by: Zhenyu FU <ysfcore@outlook.com>
2026-07-09 15:23:43 +08:00
fufesou
8314335b31 fix: update download, force tls (#15529)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-09 15:05:49 +08:00
5 changed files with 169 additions and 14 deletions

View File

@@ -39,7 +39,7 @@ env:
# vcpkg version: 2025.08.27
# If we change the `VCPKG COMMIT_ID`, please remember:
# 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`.
# Or we may face build issue like
# Or we may face build issue like
# https://github.com/rustdesk/rustdesk/actions/runs/14414119794/job/40427970174
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
@@ -49,6 +49,7 @@ env:
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
MACOS_NOTARIZE_JSON: "${{ secrets.MACOS_NOTARIZE_JSON }}"
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}-2"
@@ -615,7 +616,7 @@ jobs:
run: |
rustup target add ${{ matrix.job.target }}
cargo build --locked --features flutter,hwcodec --release --target aarch64-apple-ios --lib
- name: Upload liblibrustdesk.a Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -693,12 +694,20 @@ jobs:
- name: Check sign and import sign key
if: env.MACOS_P12_BASE64 != null
env:
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
shell: bash
run: |
security default-keychain -s rustdesk.keychain
security find-identity -v
if ! [[ "$MACOS_CODESIGN_IDENTITY" =~ ^[A-Za-z0-9]+$ ]]; then
# Ensure no whitespaces or special characters
echo 'FATAL: Invalid `secrets.MACOS_CODESIGN_IDENTITY` given. If signing key is stored on your Mac, you can run `security find-identity -v -p codesigning` to find out hex format of your identity.' >&2
exit 128
fi
- name: Import notarize key
if: env.MACOS_P12_BASE64 != null
if: env.MACOS_P12_BASE64 != null && env.MACOS_NOTARIZE_JSON != null
uses: timheuer/base64-to-file@adaa40c0c581f276132199d4cf60afa07ce60eac # v1.2
with:
# https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_rcodesign.html#notarizing-and-stapling
@@ -846,8 +855,10 @@ jobs:
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict rustdesk-${{ env.VERSION }}.dmg -vvv
# notarize the rustdesk-${{ env.VERSION }}.dmg
rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple rustdesk-${{ env.VERSION }}.dmg
if [ "$MACOS_NOTARIZE_JSON" != "" ]; then
# notarize the rustdesk-${{ env.VERSION }}.dmg
rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple rustdesk-${{ env.VERSION }}.dmg
fi
- name: Rename rustdesk
if: env.UPLOAD_ARTIFACT == 'true'

View File

@@ -9,8 +9,8 @@ mod http_client;
pub mod record_upload;
pub mod sync;
pub use http_client::{
create_http_client_async, create_http_client_async_with_url, create_http_client_with_url,
get_url_for_tls,
create_http_client_async, create_http_client_async_with_url_strict,
create_http_client_with_url, create_http_client_with_url_strict, get_url_for_tls,
};
#[derive(Debug)]

View File

@@ -1,4 +1,4 @@
use super::create_http_client_async_with_url;
use super::create_http_client_async_with_url_strict;
use hbb_common::{
bail,
lazy_static::lazy_static,
@@ -167,7 +167,7 @@ async fn do_download(
auto_del_dur: Option<Duration>,
mut rx_cancel: UnboundedReceiver<()>,
) -> ResultType<bool> {
let client = create_http_client_async_with_url(&url).await;
let client = create_http_client_async_with_url_strict(&url).await?;
let mut is_all_downloaded = false;
tokio::select! {

View File

@@ -1,5 +1,6 @@
use hbb_common::{
async_recursion::async_recursion,
bail,
config::{Config, Socks5Server},
log::{self, info},
proxy::{Proxy, ProxyScheme},
@@ -7,6 +8,7 @@ use hbb_common::{
get_cached_tls_accept_invalid_cert, get_cached_tls_type, is_plain, upsert_tls_cache,
TlsType,
},
ResultType,
};
use reqwest::{blocking::Client as SyncClient, Client as AsyncClient};
@@ -137,6 +139,32 @@ pub fn create_http_client_with_url(url: &str) -> SyncClient {
)
}
pub fn create_http_client_with_url_strict(url: &str) -> ResultType<SyncClient> {
let parsed_url = url::Url::parse(url)?;
if parsed_url.scheme() != "https" {
bail!("Strict HTTP client requires HTTPS: {}", url);
}
let proxy_conf = Config::get_socks();
let tls_url = get_url_for_tls(url, &proxy_conf);
let cached_tls_type = get_cached_tls_type(tls_url);
let cached_danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
let can_reuse_cached_probe =
cached_tls_type.is_some() && cached_danger_accept_invalid_cert == Some(false);
let tls_type = if can_reuse_cached_probe {
cached_tls_type.unwrap_or(TlsType::Rustls)
} else {
TlsType::Rustls
};
Ok(create_http_client_with_url_(
url,
tls_url,
tls_type,
can_reuse_cached_probe,
Some(false),
Some(false),
))
}
fn create_http_client_with_url_(
url: &str,
tls_url: &str,
@@ -247,6 +275,33 @@ pub async fn create_http_client_async_with_url(url: &str) -> AsyncClient {
.await
}
pub async fn create_http_client_async_with_url_strict(url: &str) -> ResultType<AsyncClient> {
let parsed_url = url::Url::parse(url)?;
if parsed_url.scheme() != "https" {
bail!("Strict HTTP client requires HTTPS: {}", url);
}
let proxy_conf = Config::get_socks();
let tls_url = get_url_for_tls(url, &proxy_conf);
let cached_tls_type = get_cached_tls_type(tls_url);
let cached_danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
let can_reuse_cached_probe =
cached_tls_type.is_some() && cached_danger_accept_invalid_cert == Some(false);
let tls_type = if can_reuse_cached_probe {
cached_tls_type.unwrap_or(TlsType::Rustls)
} else {
TlsType::Rustls
};
Ok(create_http_client_async_with_url_(
url,
tls_url,
tls_type,
can_reuse_cached_probe,
Some(false),
Some(false),
)
.await)
}
#[async_recursion]
async fn create_http_client_async_with_url_(
url: &str,

View File

@@ -1,8 +1,8 @@
use crate::{common::do_check_software_update, hbbs_http::create_http_client_with_url};
use crate::{common::do_check_software_update, hbbs_http::create_http_client_with_url_strict};
use hbb_common::{bail, config, log, ResultType};
use std::{
io::Write,
path::PathBuf,
path::{Component, Path, PathBuf},
sync::{
atomic::{AtomicUsize, Ordering},
mpsc::{channel, Receiver, Sender},
@@ -153,7 +153,7 @@ fn check_update(manually: bool) -> ResultType<()> {
format!("{}/rustdesk-{}-x86-sciter.exe", download_url, version)
};
log::debug!("New version available: {}", &version);
let client = create_http_client_with_url(&download_url);
let client = create_http_client_with_url_strict(&download_url)?;
let Some(file_path) = get_download_file_from_url(&download_url) else {
bail!("Failed to get the file path from the URL: {}", download_url);
};
@@ -291,7 +291,96 @@ fn update_new_version(update_msi: bool, version: &str, file_path: &PathBuf) {
}
}
pub fn get_download_file_from_url(url: &str) -> Option<PathBuf> {
let filename = url.split('/').last()?;
pub fn get_update_download_file_from_url(url: &str) -> Option<PathBuf> {
let parsed = url::Url::parse(url).ok()?;
// Check the raw prefix before Url normalizes default ports.
if !url.starts_with("https://github.com/")
|| parsed.scheme() != "https"
|| parsed.host_str() != Some("github.com")
|| !parsed.username().is_empty()
|| parsed.password().is_some()
|| parsed.port().is_some()
|| parsed.query().is_some()
|| parsed.fragment().is_some()
{
return None;
}
let mut segments = parsed.path_segments()?;
let owner = segments.next()?;
let repo = segments.next()?;
let releases = segments.next()?;
let download = segments.next()?;
let tag = segments.next()?;
let filename = segments.next()?;
if owner != "rustdesk"
|| repo != "rustdesk"
|| releases != "releases"
|| download != "download"
|| tag.is_empty()
|| segments.next().is_some()
|| !is_plain_update_filename(filename)
{
return None;
}
Some(std::env::temp_dir().join(filename))
}
fn is_plain_update_filename(filename: &str) -> bool {
if filename.is_empty()
|| filename.contains('/')
|| filename.contains('\\')
|| filename.contains(':')
{
return false;
}
let mut components = Path::new(filename).components();
matches!(
components.next(),
Some(Component::Normal(name)) if name.to_str() == Some(filename)
) && components.next().is_none()
}
pub fn get_download_file_from_url(url: &str) -> Option<PathBuf> {
get_update_download_file_from_url(url)
}
#[cfg(test)]
mod tests {
use super::get_download_file_from_url;
#[test]
fn update_download_file_accepts_expected_github_asset_urls() {
let file = get_download_file_from_url(
"https://github.com/rustdesk/rustdesk/releases/download/1.4.0/rustdesk-1.4.0-x86_64.dmg",
)
.expect("valid GitHub release asset URL");
assert_eq!(
file.file_name().and_then(|name| name.to_str()),
Some("rustdesk-1.4.0-x86_64.dmg")
);
}
#[test]
fn update_download_file_rejects_untrusted_or_malformed_urls() {
for url in [
"http://github.com/rustdesk/rustdesk/releases/download/1/rustdesk.exe",
"https://example.com/rustdesk.exe",
"https://github.com/other/project/releases/download/1/rustdesk.exe",
"https://github.com/rustdesk/rustdesk/releases/download/1/",
"https://github.com/rustdesk/rustdesk/releases/download/1/nested/rustdesk.exe",
"https://github.com/rustdesk/rustdesk/releases/download/1/C:rustdesk.exe",
"https://user@github.com/rustdesk/rustdesk/releases/download/1/rustdesk.exe",
"https://github.com:443/rustdesk/rustdesk/releases/download/1/rustdesk.exe",
"https://github.com/rustdesk/rustdesk/releases/download/1/rustdesk.exe?download=1",
"https://github.com/rustdesk/rustdesk/releases/download/1/rustdesk.exe#download",
"not a url",
] {
assert!(get_download_file_from_url(url).is_none(), "{url}");
}
}
}