v0.4.18: protección anti-secuestro de ID en el relay de internet
relay.rs envía al relay una huella SHA-256 del client_token al registrar el ID de 8 dígitos, en vez del token en texto plano. Permite que el relay (gradio-relay, cambio complementario ya desplegado en relay.gradio.net) rechace un re-registro de un ID activo cuando la huella no coincide, cerrando la ventana para que un tercero que conozca el ID intercepte el token de un cliente remoto suplantando a la estación real.
This commit is contained in:
@@ -4,6 +4,14 @@ Todas las versiones publicadas de `gradio-player` (paquete .deb).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [0.4.18] — 2026-08-23
|
||||||
|
|
||||||
|
### Seguridad
|
||||||
|
|
||||||
|
- **Protección contra secuestro de ID en el relay de internet**: `relay.rs` ahora envía al relay una huella SHA-256 del `client_token` configurado (nunca el token en sí) al registrarse con el ID de 8 dígitos. El servidor de relay (`gradio-relay`, desplegado en `relay.gradio.net`) rechaza un nuevo registro para un ID ya activo si la huella no coincide, evitando que un tercero que conozca/adivine el ID de una estación le robe el slot e intercepte el token de los clientes remotos que se conecten después. Compatible con estaciones que aún no actualizaron: si el slot activo nunca tuvo huella (versión anterior), el comportamiento es el mismo que antes de este fix — sin regresión, pero sin protección hasta actualizar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.4.17] — 2026-08-22
|
## [0.4.17] — 2026-08-22
|
||||||
|
|
||||||
### Nuevo
|
### Nuevo
|
||||||
|
|||||||
Generated
+13
-1
@@ -1591,7 +1591,7 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radio-player"
|
name = "radio-player"
|
||||||
version = "0.4.17"
|
version = "0.4.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
@@ -1614,6 +1614,7 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
@@ -1834,6 +1835,17 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "radio-player"
|
name = "radio-player"
|
||||||
version = "0.4.17"
|
version = "0.4.18"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "G Radio Player — reproductor y herramientas de programación"
|
description = "G Radio Player — reproductor y herramientas de programación"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
@@ -92,6 +92,7 @@ zip = "2"
|
|||||||
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
|
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
|
sha2 = "0.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6619,7 +6619,7 @@ fn main() {
|
|||||||
if gr_cfg_srv.relay_habilitado && gr_cfg_srv.relay_id.len() == 8
|
if gr_cfg_srv.relay_habilitado && gr_cfg_srv.relay_id.len() == 8
|
||||||
&& gr_cfg_srv.relay_id.chars().all(|c| c.is_ascii_digit())
|
&& gr_cfg_srv.relay_id.chars().all(|c| c.is_ascii_digit())
|
||||||
{
|
{
|
||||||
relay::iniciar(gr_cfg_srv.relay_id, gr_cfg_srv.client_port, cliente_conectado.clone());
|
relay::iniciar(gr_cfg_srv.relay_id, gr_cfg_srv.client_port, cliente_conectado.clone(), gr_cfg_srv.client_token.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = Application::builder()
|
let app = Application::builder()
|
||||||
|
|||||||
+28
-7
@@ -45,9 +45,27 @@ fn relay_url() -> String {
|
|||||||
std::env::var("GRADIO_RELAY_URL").unwrap_or_else(|_| DEFAULT_RELAY_URL.to_string())
|
std::env::var("GRADIO_RELAY_URL").unwrap_or_else(|_| DEFAULT_RELAY_URL.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calcula la huella (SHA-256 hex) del client_token para probar continuidad
|
||||||
|
/// de dueño ante el relay sin enviarle el token real. `None` si el token
|
||||||
|
/// está vacío (el relay trata eso como "sin protección", igual que antes
|
||||||
|
/// de tener este mecanismo).
|
||||||
|
fn secreto_de_token(client_token: &str) -> Option<String> {
|
||||||
|
if client_token.trim().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(client_token.trim().as_bytes());
|
||||||
|
Some(format!("{:x}", hasher.finalize()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Lanza el hilo de relay. No bloquea — retorna inmediatamente.
|
/// Lanza el hilo de relay. No bloquea — retorna inmediatamente.
|
||||||
/// `id` — 8 dígitos numéricos, `puerto_local` — puerto del servidor TCP local.
|
/// `id` — 8 dígitos numéricos, `puerto_local` — puerto del servidor TCP local,
|
||||||
pub fn iniciar(id: String, puerto_local: u16, cliente_conectado: ClienteConectado) {
|
/// `client_token` — token de autenticación configurado (se envía al relay
|
||||||
|
/// como huella SHA-256, nunca en texto plano) para evitar que un tercero
|
||||||
|
/// que conozca el ID le robe el slot a esta estación.
|
||||||
|
pub fn iniciar(id: String, puerto_local: u16, cliente_conectado: ClienteConectado, client_token: String) {
|
||||||
|
let secreto = secreto_de_token(&client_token);
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("gradio-relay".to_string())
|
.name("gradio-relay".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
@@ -55,17 +73,17 @@ pub fn iniciar(id: String, puerto_local: u16, cliente_conectado: ClienteConectad
|
|||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("relay: no se pudo crear runtime tokio");
|
.expect("relay: no se pudo crear runtime tokio");
|
||||||
rt.block_on(bucle_relay(id, puerto_local, cliente_conectado));
|
rt.block_on(bucle_relay(id, puerto_local, cliente_conectado, secreto));
|
||||||
})
|
})
|
||||||
.expect("relay: no se pudo crear hilo");
|
.expect("relay: no se pudo crear hilo");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Bucle de reconexión ──────────────────────────────────────────────────────
|
// ─── Bucle de reconexión ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
async fn bucle_relay(id: String, puerto_local: u16, cliente_conectado: ClienteConectado) {
|
async fn bucle_relay(id: String, puerto_local: u16, cliente_conectado: ClienteConectado, secreto: Option<String>) {
|
||||||
loop {
|
loop {
|
||||||
log::info!("relay: conectando con ID={}", id);
|
log::info!("relay: conectando con ID={}", id);
|
||||||
match conectar_y_operar(&id, puerto_local, cliente_conectado.clone()).await {
|
match conectar_y_operar(&id, puerto_local, cliente_conectado.clone(), secreto.clone()).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!("relay: sesión cerrada, reconectando en 1s...");
|
log::info!("relay: sesión cerrada, reconectando en 1s...");
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
@@ -80,7 +98,7 @@ async fn bucle_relay(id: String, puerto_local: u16, cliente_conectado: ClienteCo
|
|||||||
|
|
||||||
// ─── Conexión principal ───────────────────────────────────────────────────────
|
// ─── Conexión principal ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
async fn conectar_y_operar(id: &str, puerto_local: u16, cliente_conectado: ClienteConectado) -> Result<()> {
|
async fn conectar_y_operar(id: &str, puerto_local: u16, cliente_conectado: ClienteConectado, secreto: Option<String>) -> Result<()> {
|
||||||
// connect_async maneja TLS automáticamente para wss:// (feature native-tls)
|
// connect_async maneja TLS automáticamente para wss:// (feature native-tls)
|
||||||
let url = relay_url();
|
let url = relay_url();
|
||||||
log::info!("relay: usando URL {}", url);
|
log::info!("relay: usando URL {}", url);
|
||||||
@@ -115,7 +133,10 @@ async fn conectar_y_operar(id: &str, puerto_local: u16, cliente_conectado: Clien
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Handshake de registro ─────────────────────────────────────────────────
|
// ── Handshake de registro ─────────────────────────────────────────────────
|
||||||
let msg = format!(r#"{{"tipo":"register","id":"{}"}}"#, id);
|
let msg = match &secreto {
|
||||||
|
Some(s) => format!(r#"{{"tipo":"register","id":"{}","secreto":"{}"}}"#, id, s),
|
||||||
|
None => format!(r#"{{"tipo":"register","id":"{}"}}"#, id),
|
||||||
|
};
|
||||||
out_tx.send(Message::Text(msg.into()))
|
out_tx.send(Message::Text(msg.into()))
|
||||||
.map_err(|_| anyhow::anyhow!("relay: canal de escritura cerrado antes de registrar"))?;
|
.map_err(|_| anyhow::anyhow!("relay: canal de escritura cerrado antes de registrar"))?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user