Custom-client: сервер виден+залочен + preset-пароль (Win+Android)
preconfigure.sh генерит src/custom_client.json (override-settings server/relay/key + verification-method + disable-change-permanent-password; preset-пароль salt+00b64(sha256(pw+salt)) в HARD_SETTINGS) и патчит common.rs встроенным неподписанным загрузчиком (обход ED25519-подписи RustDesk). Пароль из RUSTDESK_PRESET_PASSWORD, plaintext не хранится. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -92,3 +92,21 @@ Rust 1.75 (rustup override в клоне) · MSVC 14.44 из vcvars64.
|
||||
## Секреты в CI (gitea Actions)
|
||||
`RUSTDESK_SERVER`, `RUSTDESK_PUB_KEY`, (опц.) `RUSTDESK_API_SERVER`. Префикс имён — не `GITHUB_`/`GITEA_`
|
||||
(зарезервированы), при необходимости `GT_`.
|
||||
|
||||
## Custom-client: сервер виден+залочен + preset-пароль
|
||||
Требование: сервер показан в Настройках и НЕизменяем + вшитый постоянный пароль (+ временный).
|
||||
Механизм RustDesk (изучен по коду 1.4.9):
|
||||
- `src/common.rs::load_custom_client()` (зовётся из core_main) читает `custom.txt` рядом с exe —
|
||||
base64 + **ED25519-подпись** по вшитому pubkey `5Qbwsde3…` (приватник у генератора RustDesk Pro, у нас нет).
|
||||
- Он заполняет `OVERWRITE_SETTINGS` (override — залоченные опции), `DEFAULT_SETTINGS`, `BUILTIN_SETTINGS` (локи),
|
||||
`HARD_SETTINGS` (top-level, вкл. preset-пароль).
|
||||
- `custom-rendezvous-server`/`relay-server`/`key`/`api-server`/`verification-method` ∈ `KEYS_SETTINGS` →
|
||||
в `override-settings` показываются И залочены. `disable-change-permanent-password` ∈ BUILDIN.
|
||||
- Preset-пароль (`hbb_common/src/config/permanent_password.rs`): `HARD_SETTINGS.password="00"+base64(sha256(pw+salt))`,
|
||||
`HARD_SETTINGS.salt=<любая непустая соль>`. Формат «00»+b64(h1), h1=sha256(pw+salt)[..32].
|
||||
|
||||
Наша реализация (`preconfigure.sh`, вкл. по умолчанию; выкл. `RUSTDESK_NO_CUSTOM_CLIENT=1`):
|
||||
- Пишет `src/custom_client.json` (override-settings + password/salt из `RUSTDESK_PRESET_PASSWORD`).
|
||||
- Патчит `common.rs`: добавляет `read_custom_client_unsigned()` (парсит JSON БЕЗ подписи, тот же apply-код) +
|
||||
`include_str!("custom_client.json")` в начало `load_custom_client()`. Работает Windows+Android (без внешнего файла).
|
||||
- Пароль — только из env/secret `RUSTDESK_PRESET_PASSWORD`, солёный хэш пишется в gitignored-клон, plaintext нигде.
|
||||
|
||||
+103
-5
@@ -47,9 +47,107 @@ fi
|
||||
|
||||
echo "[preconfigure] RENDEZVOUS_SERVERS -> ${#SRV[@]} сервер(ов), RS_PUB_KEY вшит."
|
||||
|
||||
# Опциональный API-сервер (RustDesk Server Pro). По умолчанию у hbbs/hbbr его нет.
|
||||
if [ -n "${RUSTDESK_API_SERVER:-}" ]; then
|
||||
# В 1.4.9 отдельной const API_SERVER нет; дефолт задаётся через overrideConfig/filename.
|
||||
# Оставляем маркер для ручной доработки, если понадобится Pro-API.
|
||||
echo "[preconfigure] RUSTDESK_API_SERVER задан ($RUSTDESK_API_SERVER) — вшивание API требует доп. патча (см. docs/CODEMAP.md)."
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Custom-client: сервер/ключ ВИДНЫ и ЗАЛОЧЕНЫ в настройках (override-settings) +
|
||||
# опц. preset (постоянный) пароль в HARD_SETTINGS. Механизм RustDesk load_custom_client()
|
||||
# читает ПОДПИСАННЫЙ custom.txt (приватного ключа их генератора у нас нет) -> патчим
|
||||
# загрузчик под встроенный НЕподписанный конфиг (src/custom_client.json, include_str!),
|
||||
# что работает и на Windows, и на Android. Отключить: RUSTDESK_NO_CUSTOM_CLIENT=1.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
if [ "${RUSTDESK_NO_CUSTOM_CLIENT:-}" != "1" ]; then
|
||||
# Рабочий python (на Windows git-bash python3 = Store-заглушка -> берём python).
|
||||
PY=""; for p in python3 python; do "$p" -c 'import hashlib,base64,json,secrets' 2>/dev/null && { PY="$p"; break; }; done
|
||||
[ -n "$PY" ] || { echo "ОШИБКА: не найден рабочий python для custom-client" >&2; exit 1; }
|
||||
|
||||
FIRST_SRV="$(echo -n "${SRV[0]}" | tr -d '[:space:]')"
|
||||
LOCK_PW="${RUSTDESK_LOCK_PASSWORD:-Y}" # залочить смену preset-пароля (по умолчанию да)
|
||||
"$PY" - "$SRC/src/custom_client.json" "$FIRST_SRV" "$RUSTDESK_PUB_KEY" "${RUSTDESK_API_SERVER:-}" "${RUSTDESK_PRESET_PASSWORD:-}" "$LOCK_PW" <<'PYEOF'
|
||||
import sys, json, hashlib, base64, secrets
|
||||
out, server, key, api, preset_pw, lock_pw = sys.argv[1:7]
|
||||
override = {"custom-rendezvous-server": server, "relay-server": server, "key": key}
|
||||
if api:
|
||||
override["api-server"] = api
|
||||
cfg = {"override-settings": override}
|
||||
if preset_pw:
|
||||
override["verification-method"] = "use-both-passwords" # постоянный + временный пароль
|
||||
if lock_pw == "Y":
|
||||
override["disable-change-permanent-password"] = "Y"
|
||||
salt = secrets.token_hex(16)
|
||||
h1 = hashlib.sha256(preset_pw.encode() + salt.encode()).digest() # = compute_permanent_password_h1
|
||||
cfg["password"] = "00" + base64.b64encode(h1).decode() # HBBS preset storage: "00"+b64(h1)
|
||||
cfg["salt"] = salt
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
json.dump(cfg, f, ensure_ascii=False)
|
||||
print("[preconfigure] custom_client.json: сервер виден+залочен, preset-пароль=" + ("да" if preset_pw else "нет"))
|
||||
PYEOF
|
||||
|
||||
# Патч common.rs: встроенный неподписанный загрузчик (идемпотентно; клон сбрасывается перед каждой сборкой).
|
||||
CM="$SRC/src/common.rs"
|
||||
if ! grep -q "read_custom_client_unsigned" "$CM"; then
|
||||
sed -i 's|pub fn load_custom_client() {|pub fn load_custom_client() {\n read_custom_client_unsigned(include_str!("custom_client.json"));|' "$CM"
|
||||
grep -q 'read_custom_client_unsigned(include_str' "$CM" || { echo "ОШИБКА: не удалось внедрить вызов в load_custom_client" >&2; exit 1; }
|
||||
cat >> "$CM" <<'RUSTEOF'
|
||||
|
||||
// [rustdesk-custom] Встроенный НЕподписанный custom-client конфиг (src/custom_client.json).
|
||||
pub fn read_custom_client_unsigned(json: &str) {
|
||||
let json = json.trim();
|
||||
if json.is_empty() || json == "{}" {
|
||||
return;
|
||||
}
|
||||
let Ok(mut data) =
|
||||
serde_json::from_str::<std::collections::HashMap<String, serde_json::Value>>(json)
|
||||
else {
|
||||
log::error!("Failed to parse embedded custom client config");
|
||||
return;
|
||||
};
|
||||
if let Some(app_name) = data.remove("app-name") {
|
||||
if let Some(app_name) = app_name.as_str() {
|
||||
*config::APP_NAME.write().unwrap() = app_name.to_owned();
|
||||
}
|
||||
}
|
||||
let mut map_display_settings = HashMap::new();
|
||||
for s in keys::KEYS_DISPLAY_SETTINGS {
|
||||
map_display_settings.insert(s.replace("_", "-"), s);
|
||||
}
|
||||
let mut map_local_settings = HashMap::new();
|
||||
for s in keys::KEYS_LOCAL_SETTINGS {
|
||||
map_local_settings.insert(s.replace("_", "-"), s);
|
||||
}
|
||||
let mut map_settings = HashMap::new();
|
||||
for s in keys::KEYS_SETTINGS {
|
||||
map_settings.insert(s.replace("_", "-"), s);
|
||||
}
|
||||
let mut buildin_settings = HashMap::new();
|
||||
for s in keys::KEYS_BUILDIN_SETTINGS {
|
||||
buildin_settings.insert(s.replace("_", "-"), s);
|
||||
}
|
||||
if let Some(default_settings) = data.remove("default-settings") {
|
||||
read_custom_client_advanced_settings(
|
||||
default_settings,
|
||||
&map_display_settings,
|
||||
&map_local_settings,
|
||||
&map_settings,
|
||||
&buildin_settings,
|
||||
false,
|
||||
);
|
||||
}
|
||||
if let Some(overwrite_settings) = data.remove("override-settings") {
|
||||
read_custom_client_advanced_settings(
|
||||
overwrite_settings,
|
||||
&map_display_settings,
|
||||
&map_local_settings,
|
||||
&map_settings,
|
||||
&buildin_settings,
|
||||
true,
|
||||
);
|
||||
}
|
||||
for (k, v) in data {
|
||||
if let Some(v) = v.as_str() {
|
||||
config::HARD_SETTINGS.write().unwrap().insert(k, v.to_owned());
|
||||
};
|
||||
}
|
||||
}
|
||||
RUSTEOF
|
||||
echo "[preconfigure] common.rs пропатчен (встроенный custom-client загрузчик)."
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user