Skip to content

Commit 9e9cdbb

Browse files
corregido pequeños bugs del .env vault
1 parent c7f8dfc commit 9e9cdbb

6 files changed

Lines changed: 30 additions & 53 deletions

File tree

Docs/Services/CLAVES_SISTEMA.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Ravage maneja varios tipos de claves criptográficas con propósitos distintos.
88

99
| Clave | Origen | Formato en memoria | Formato en reposo | Propósito |
1010
|---|---|---|---|---|
11-
| `secretKey` (usuario) | `randomBytes(32)` al registrar | `Buffer` | `BinData` en MongoDB | Cifra archivos locales del usuario (ajustes, historial, etc.) |
11+
| `secretKey` (usuario) | `randomBytes(32)` al registrar | `Buffer` | `EncryptedDataSchema` en MongoDB (cifrado con `INTERNAL_ENCRYPTION_KEY`) | Cifra archivos locales del usuario (ajustes, historial, etc.) |
1212
| `SECRET_KEY_COKKIE` | Variable de entorno (hex) | `Buffer` | `.env.secret` | Cifra archivos locales de sesión y configuración de la app |
1313
| `SECRET_KEY_PRIVATE` | Variable de entorno (hex) | `Buffer` | `.env.secret` | Cifra el archivo de identidad E2EE (clave privada RSA) en disco |
1414
| `INTERNAL_ENCRYPTION_KEY` | Variable de entorno (hex) | `Buffer` | `.env.secret` | Cifra datos sensibles almacenados en MongoDB (buzón, etc.) |
@@ -21,9 +21,11 @@ Ravage maneja varios tipos de claves criptográficas con propósitos distintos.
2121

2222
### Origen y formato
2323

24-
Generada al registrar el usuario con `randomBytes(32)` (32 bytes, 256 bits). Se almacena directamente como `Buffer` en MongoDB (campo `secretKey` del schema `User`, tipo Mongoose `Buffer` → MongoDB `BinData`).
24+
Generada al registrar el usuario con `randomBytes(32)` (32 bytes, 256 bits). Se almacena **cifrada** en MongoDB como `EncryptedDataSchema` usando `INTERNAL_ENCRYPTION_KEY` + AES-256-GCM. El hex del buffer es el plaintext cifrado.
2525

26-
Antes se almacenaba como cadena hexadecimal (64 chars). El cambio a `Buffer` elimina la conversión `Buffer.from(key, "hex")` en cada uso y reduce el tamaño en DB a la mitad.
26+
Al cargar en sesión, `desencriptarDatosSistema(dt.secretKey)` devuelve el hex → `Buffer.from(hex, 'hex')` → Buffer de 32 bytes que se pone en memoria con `setSecretKEY(buffer)`.
27+
28+
Esto significa que comprometer MongoDB sin tener también la `INTERNAL_ENCRYPTION_KEY` (almacenada en el vault del SO) no es suficiente para obtener las claves de cifrado de los usuarios.
2729

2830
### Ciclo de vida
2931

@@ -138,7 +140,7 @@ Para descifrar: receptor hace `ECDH(privKey, ephPub)` → misma wrapping key →
138140

139141
| Clave | Algoritmo de cifrado en reposo | Algoritmo de uso |
140142
|---|---|---|
141-
| `secretKey` usuario | (en MongoDB, protegido por acceso a la DB) | AES-256-GCM (cifra archivos locales) |
143+
| `secretKey` usuario | AES-256-GCM con `INTERNAL_ENCRYPTION_KEY` (en MongoDB) | AES-256-GCM (cifra archivos locales) |
142144
| `SECRET_KEY_COKKIE` | En `.env.secret` (protegido por vault del SO) | AES-256-GCM |
143145
| `SECRET_KEY_PRIVATE` | En `.env.secret` (protegido por vault del SO) | AES-256-GCM |
144146
| `INTERNAL_ENCRYPTION_KEY` | En `.env.secret` | AES-256-GCM (cifra datos en MongoDB) |

backend/models/User.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ const UserSchema = new mongoose.Schema({
116116
index: true
117117
},
118118
secretKey: {
119-
type: Buffer
119+
type: EncryptedDataSchema,
120+
default: null
120121
},
121122
publicKey: {
122123
type: String,

backend/repositories/UserRepository.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export async function InsertarUsuario({ apodo = "Usuario", contrasena, correo, s
140140
correo: encriptarDatosSistema(correo),
141141
correo_hash: correoHash,
142142
contrasena: contrasena,
143-
secretKey: sKey,
143+
secretKey: encriptarDatosSistema(sKey.toString('hex')),
144144
idamigo: encriptarDatosSistema(idAmigo),
145145
idamigo_hash: idamigoHash,
146146
publicKey: publicKey
@@ -352,7 +352,7 @@ export async function ActualizarSecretKeyUsuario(actualizar = true) {
352352
if (!actualizar) return key;
353353
try {
354354
const correoHash = hashDatosSistema(getCorreoSesion());
355-
const r = await User.updateOne({ correo_hash: correoHash }, { $set: { secretKey: key } });
355+
const r = await User.updateOne({ correo_hash: correoHash }, { $set: { secretKey: encriptarDatosSistema(key.toString('hex')) } });
356356
if (r.matchedCount === 0) return false;
357357

358358
await _syncCache(correoHash);

backend/services/sesionUsuario.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
import { generarteToken, validateToken } from './CreadorTokens.js';
2929
import * as storage from '../STORAGE/Variables_sesion.js';
3030
import { hash, compare, createHash, machineIdSync, os, si } from '../utils/libs.js';
31-
import { generarLlavesX25519, hashDatosSistema, getIdentity } from './cryptoService.js';
31+
import { generarLlavesX25519, hashDatosSistema, getIdentity, desencriptarDatosSistema } from './cryptoService.js';
3232

3333
import { clearCacheUsuarios, setUsuarioEnCache } from '../repositories/UserRepository.js';
3434
import { clearCacheArchivosDescargados } from '../STORAGE/CACHE/_cache_archivos_descargados.js';
@@ -87,7 +87,7 @@ function ACTUALIZAR_DATOS_LOGIN({ data, limpiar = false, id_maquina = null }) {
8787
storage.setUsuariosSilence(lp ? dt.users_silence : []);
8888
storage.setUsuariosBloqueados(lp ? dt.users_bloq : []);
8989
storage.setIdDispositivo(lp ? (id_maquina ? id_maquina : machineIdSync()) : null)
90-
storage.setSecretKEY(lp ? dt.secretKey : null)
90+
storage.setSecretKEY(lp ? (dt.secretKey ? Buffer.from(desencriptarDatosSistema(dt.secretKey), 'hex') : null) : null)
9191
storage.setListaChats(lp ? dt.chats : []);
9292
storage.setListaContactos(lp ? dt.contactos : []);
9393
storage.setIDAmigo(lp ? dt.idamigo : false)

backend/utils/env_vault.js

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* 1. Al arrancar (después de app.whenReady): detecta .env en env/
66
* → los cifra con safeStorage y los borra del disco
77
* 2. Carga las variables desde el baúl cifrado a process.env
8-
* 3. Expone exportarEnvADescargas() para regenerar los .env en Descargas
98
*
109
* Almacenamiento: <userData>/env_vault/<nombre>.enc (Buffer cifrado por el SO)
1110
* Backend safeStorage: libsecret (Linux) · DPAPI (Windows) · Keychain (macOS)
@@ -64,30 +63,37 @@ function listarVault() {
6463
* @returns {boolean} true si migró al menos un archivo
6564
*/
6665
function migrarEnvAlBaul() {
67-
const archivos = listarEnvSrc();
66+
const archivos = listarEnvSrc().filter(({ ruta }) => {
67+
try { return fs.statSync(ruta).size > 0; } catch { return false; }
68+
});
6869
if (archivos.length === 0) return false;
6970

7071
if (!safeStorage.isEncryptionAvailable()) {
71-
console.warn('[EnvVault] safeStorage no disponible en este sistema, manteniendo .env en disco');
72+
console.warn('[EnvVault] safeStorage no disponible — los .env permanecen en disco sin cifrar. Instala gnome-keyring o kwallet para protegerlos.');
7273
return false;
7374
}
7475

7576
const vaultDir = getVaultDir();
7677
fs.mkdirSync(vaultDir, { recursive: true });
7778

79+
let migrados = 0;
7880
for (const { nombre, ruta } of archivos) {
7981
try {
8082
const contenido = fs.readFileSync(ruta, 'utf-8');
8183
const cifrado = safeStorage.encryptString(contenido);
82-
fs.writeFileSync(path.join(vaultDir, nombre + '.enc'), cifrado);
84+
const destino = path.join(vaultDir, nombre + '.enc');
85+
fs.writeFileSync(destino, cifrado);
86+
// Solo borra el original si el .enc se escribió correctamente
87+
fs.accessSync(destino, fs.constants.R_OK);
8388
fs.unlinkSync(ruta);
84-
console.log(`[EnvVault] Migrado al baúl y eliminado: ${nombre}`);
89+
migrados++;
90+
console.log(`[EnvVault] Migrado y eliminado del disco: ${nombre}`);
8591
} catch (err) {
86-
console.error(`[EnvVault] Error migrando ${nombre}:`, err.message);
92+
console.error(`[EnvVault] Fallo migrando ${nombre} — el archivo original se mantiene en disco:`, err.message);
8793
}
8894
}
8995

90-
return true;
96+
return migrados > 0;
9197
}
9298

9399
/**
@@ -100,10 +106,11 @@ function cargarDesdeVaul() {
100106
if (archivos.length === 0) return false;
101107

102108
if (!safeStorage.isEncryptionAvailable()) {
103-
console.warn('[EnvVault] safeStorage no disponible, no se pueden descifrar las variables');
109+
console.warn('[EnvVault] safeStorage no disponibleno se pueden leer los .enc del baúl.');
104110
return false;
105111
}
106112

113+
let cargados = 0;
107114
for (const { nombre, ruta } of archivos) {
108115
try {
109116
const cifrado = fs.readFileSync(ruta);
@@ -112,13 +119,14 @@ function cargarDesdeVaul() {
112119
for (const [key, value] of Object.entries(vars)) {
113120
process.env[key] = value;
114121
}
122+
cargados++;
115123
console.log(`[EnvVault] Cargado desde baúl: ${nombre}`);
116124
} catch (err) {
117125
console.error(`[EnvVault] Error descifrando ${nombre}:`, err.message);
118126
}
119127
}
120128

121-
return true;
129+
return cargados > 0;
122130
}
123131

124132
/**
@@ -129,34 +137,3 @@ export async function inicializarVault() {
129137
migrarEnvAlBaul();
130138
cargarDesdeVaul();
131139
}
132-
133-
/**
134-
* Descifra todos los archivos del baúl y los escribe en <Descargas>/Ravage-env/.
135-
* @returns {{ ok: boolean, ruta?: string, error?: string }}
136-
*/
137-
export function exportarEnvADescargas() {
138-
const archivos = listarVault();
139-
if (archivos.length === 0) return { ok: false, error: 'El baúl está vacío' };
140-
141-
if (!safeStorage.isEncryptionAvailable()) {
142-
return { ok: false, error: 'safeStorage no disponible en este sistema' };
143-
}
144-
145-
const destino = path.join(app.getPath('downloads'), 'Ravage-env');
146-
147-
try {
148-
fs.mkdirSync(destino, { recursive: true });
149-
150-
for (const { nombre, ruta } of archivos) {
151-
const cifrado = fs.readFileSync(ruta);
152-
const contenido = safeStorage.decryptString(cifrado);
153-
fs.writeFileSync(path.join(destino, nombre), contenido, { encoding: 'utf-8', mode: 0o600 });
154-
}
155-
156-
console.log(`[EnvVault] Archivos exportados a: ${destino}`);
157-
return { ok: true, ruta: destino };
158-
} catch (err) {
159-
console.error('[EnvVault] Error exportando:', err.message);
160-
return { ok: false, error: err.message };
161-
}
162-
}

main.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,9 @@ if (!gotTheLock) {
354354

355355
app.whenReady().then(async () => {
356356
// Migrar .env al baúl seguro del SO (si los hay) y cargar vars desde él
357-
const { inicializarVault, exportarEnvADescargas } = await import('./backend/utils/env_vault.js');
357+
const { inicializarVault } = await import('./backend/utils/env_vault.js');
358358
await inicializarVault();
359359

360-
// Handler IPC para exportar los .env desde el baúl a Descargas del usuario
361-
ipcMain.handle('vault:exportar-env', async () => exportarEnvADescargas());
362-
363360
const pServer = import('./backend/servidores/serverLocalHost.js');
364361
const pDb = import("./backend/db/mongo.js");
365362
const pSesion = import('./backend/services/sesionUsuario.js');

0 commit comments

Comments
 (0)