1.6.2: manifiesto firmado, tiempo estimado en tareas, pruebas automáticas

- firma.py: RSA-2048 PKCS#1 v1.5 SHA-256 sin dependencias; empaquetar.py firma
  el manifiesto y actualizador.py exige la firma (CLAVE_PUBLICA incrustada).
- Barra de tareas: tiempo restante estimado a partir del avance.
- Corregido: APLICANDO nunca llegaba al total y «Moviendo» quedaba fijo,
  bloqueando desconectar la carpeta.
- probar.py: reglas, firma y recorrido completo sobre una copia temporal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-09-15 14:48:47 -05:00
padre 8c5153e8ed
commit 8b482af652
Se han modificado 9 ficheros con 414 adiciones y 14 borrados

Ver fichero

@@ -13,6 +13,7 @@ Lo que se exige del manifiesto para instalar (si no se cumple, no se instala y s
- nombre de archivo simple (sin rutas) terminado en .zip
- sha256 de 64 hex, obligatorio
- URL de descarga con el mismo esquema y host que el manifiesto (nada de saltar a otro servidor)
- firma RSA (firma.py) de versión+archivo+sha256+tamaño que cuadre con CLAVE_PUBLICA; sin ella no se instala
`O` es el módulo organizador (lo fija él al arrancar).
"""
@@ -28,6 +29,7 @@ import tempfile
import threading
import subprocess
import urllib.error
import firma
import urllib.request
from urllib.parse import urljoin, urlparse
@@ -55,6 +57,10 @@ def _tupla(v):
return tuple(out + [0] * (3 - len(out)))
# clave pública de firma.py (la privada está en ~/.labstream/organizador-firma.json, solo la usa empaquetar.py)
CLAVE_PUBLICA = {"e": 65537, "n": 24937087096150856258549531047458881410156319770314378325709810834727102787144078753904837107336360100277476122470417827694103702029600632424402930901393600571590411504459673905624547394933513534401412673838815719634257887193733377640612491708305947101493071063533297310409777772800911393696346540036769491297235656643827279260544972034028016485933509783117840560074417501599224256143123720254264555615803987537224119534643367425486525306755766385726396897033934795259582227391903282650010470084464660818450797096588538639818043343189296266160938215570507865913766129394056803324311835125960516203968300455854004216307}
def _validar(m, url_manifiesto):
"""Devuelve (dict disponible, "") o (None, motivo). Solo acepta manifiestos bien formados."""
app = m.get("app") or {}
@@ -73,6 +79,9 @@ def _validar(m, url_manifiesto):
return None, f"la descarga apunta a otro servidor ({b.netloc}); solo se acepta {a.netloc}"
if a.scheme != "https" and not a.netloc.split(":")[0] in ("127.0.0.1", "localhost"):
return None, "el manifiesto debe servirse por https"
f = m.get("firma") or {}
if not isinstance(f, dict) or not firma.verificar(firma.texto_manifiesto(m), str(f.get("valor") or ""), CLAVE_PUBLICA):
return None, "el manifiesto no está firmado con la clave de Labstream (o se modificó); no se instala nada"
return {"version": version, "notes": str(m.get("notes", ""))[:2000], "archivo": archivo,
"size": int(app.get("size") or 0), "sha256": sha.lower(), "url": url}, ""