integraciones para cloud recovery Reviewed-on: #9 Co-authored-by: hreyes <hreyes@aduanasoft.com.mx> Co-committed-by: hreyes <hreyes@aduanasoft.com.mx>
88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
/**
|
|
* Pruebas del cifrado AES-256-GCM puro (crypto-core).
|
|
* Cubre: round-trip, detección de manipulación (GCM), formato inválido,
|
|
* unicidad del IV y validación de clave/longitud.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { randomBytes } from 'node:crypto';
|
|
import {
|
|
encryptWithKey,
|
|
decryptWithKey,
|
|
isEncrypted,
|
|
decodeKey,
|
|
KEY_LENGTH,
|
|
VERSION_PREFIX
|
|
} from './crypto-core';
|
|
|
|
const KEY = randomBytes(KEY_LENGTH);
|
|
|
|
describe('crypto-core AES-256-GCM', () => {
|
|
it('round-trip: descifrar devuelve el texto original', () => {
|
|
const plaintext = 'Soluciones01!';
|
|
const envelope = encryptWithKey(plaintext, KEY);
|
|
expect(decryptWithKey(envelope, KEY)).toBe(plaintext);
|
|
});
|
|
|
|
it('produce sobres con prefijo de versión gcm:', () => {
|
|
const envelope = encryptWithKey('secreto', KEY);
|
|
expect(envelope.startsWith(`${VERSION_PREFIX}:`)).toBe(true);
|
|
expect(isEncrypted(envelope)).toBe(true);
|
|
expect(isEncrypted('texto-plano')).toBe(false);
|
|
expect(isEncrypted(null)).toBe(false);
|
|
});
|
|
|
|
it('usa IV aleatorio: dos cifrados del mismo texto difieren', () => {
|
|
const a = encryptWithKey('mismo', KEY);
|
|
const b = encryptWithKey('mismo', KEY);
|
|
expect(a).not.toBe(b);
|
|
// pero ambos descifran al mismo valor
|
|
expect(decryptWithKey(a, KEY)).toBe(decryptWithKey(b, KEY));
|
|
});
|
|
|
|
it('detecta manipulación del ciphertext (auth tag GCM)', () => {
|
|
const envelope = encryptWithKey('integridad', KEY);
|
|
const parts = envelope.split(':');
|
|
// Alterar un byte del ciphertext
|
|
const tampered = Buffer.from(parts[3], 'base64');
|
|
tampered[0] = tampered[0] ^ 0xff;
|
|
parts[3] = tampered.toString('base64');
|
|
expect(() => decryptWithKey(parts.join(':'), KEY)).toThrow();
|
|
});
|
|
|
|
it('falla al descifrar con clave incorrecta', () => {
|
|
const envelope = encryptWithKey('secreto', KEY);
|
|
const otherKey = randomBytes(KEY_LENGTH);
|
|
expect(() => decryptWithKey(envelope, otherKey)).toThrow();
|
|
});
|
|
|
|
it('rechaza formato de sobre inválido', () => {
|
|
expect(() => decryptWithKey('no-es-un-sobre', KEY)).toThrow(/Formato/);
|
|
expect(() => decryptWithKey('aes:1:2:3', KEY)).toThrow(/Formato/);
|
|
});
|
|
|
|
it('rechaza claves con longitud incorrecta', () => {
|
|
expect(() => encryptWithKey('x', randomBytes(16))).toThrow(/32 bytes/);
|
|
});
|
|
|
|
describe('decodeKey', () => {
|
|
it('decodifica clave base64 de 32 bytes', () => {
|
|
const b64 = randomBytes(KEY_LENGTH).toString('base64');
|
|
expect(decodeKey(b64).length).toBe(KEY_LENGTH);
|
|
});
|
|
|
|
it('decodifica clave hex de 32 bytes', () => {
|
|
const hex = randomBytes(KEY_LENGTH).toString('hex');
|
|
expect(decodeKey(hex).length).toBe(KEY_LENGTH);
|
|
});
|
|
|
|
it('lanza si la clave está ausente', () => {
|
|
expect(() => decodeKey(undefined)).toThrow(/ENCRYPTION_KEY/);
|
|
expect(() => decodeKey(' ')).toThrow(/ENCRYPTION_KEY/);
|
|
});
|
|
|
|
it('lanza si la clave no decodifica a 32 bytes', () => {
|
|
expect(() => decodeKey('demasiado-corta')).toThrow(/32 bytes/);
|
|
});
|
|
});
|
|
});
|