diff options
Diffstat (limited to 'CryptoGzipModule-draft.js')
| -rw-r--r-- | CryptoGzipModule-draft.js | 315 |
1 files changed, 0 insertions, 315 deletions
diff --git a/CryptoGzipModule-draft.js b/CryptoGzipModule-draft.js deleted file mode 100644 index 2c1e45f..0000000 --- a/CryptoGzipModule-draft.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * CryptoGzipModule — Draft for Dev Agent - * - * Commands: - * gzip <path> → creates <path>.gz - * gunzip <path.gz> → restores <path> - * encrypt <path> → prompts passphrase → creates <path>.enc - * decrypt <path.enc> → prompts passphrase → restores <path> - * - * Wires into WebX11 module system. Copy into modules/ext/ or modules/tools/. - */ - -class CryptoGzipModule { - constructor() { - this.commands = {}; - this.terminal = null; - this._passphraseCache = new Map(); // session-only, never persisted - } - - requirements() { - const hasCrypto = !!(window.crypto && crypto.subtle); - const hasCompression = !!(window.CompressionStream); - const missing = []; - if (!hasCrypto) missing.push('Web Crypto API not available (requires secure context)'); - if (!hasCompression) missing.push('CompressionStream not available (requires Chromium-based browser or recent Firefox)'); - return { met: missing.length === 0, missing }; - } - - getHelpInfo() { - return { - 'gzip': 'gzip <path> — Compress a VFS file to .gz', - 'gunzip': 'gunzip <path.gz> — Decompress a .gz file in VFS', - 'encrypt':'encrypt <path> — AES-256-GCM encrypt a VFS file (interactive passphrase)', - 'decrypt':'decrypt <path.enc> — Decrypt a .enc file (interactive passphrase)', - }; - } - - init(terminal) { - this.terminal = terminal; - this.commands = { - 'gzip': (args) => this._cmdGzip(args), - 'gunzip': (args) => this._cmdGunzip(args), - 'encrypt':(args) => this._cmdEncrypt(args), - 'decrypt':(args) => this._cmdDecrypt(args), - }; - } - - // ============================================================ - // VFS WIRE POINTS — Dev Agent: replace with real VFS calls - // ============================================================ - - async _vfsRead(path) { - // WIRE: return await this.terminal.vfs.readFile(path, 'string'); - // OR: return window.webx11_vfs.read(path); - throw new Error('_vfsRead not wired'); - } - - async _vfsWrite(path, content) { - // WIRE: await this.terminal.vfs.writeFile(path, content); - // content is a string (base64 for binary-ish data) - throw new Error('_vfsWrite not wired'); - } - - async _vfsExists(path) { - // WIRE: return this.terminal.vfs.exists(path); - throw new Error('_vfsExists not wired'); - } - - async _vfsDelete(path) { - // WIRE: await this.terminal.vfs.delete(path); - throw new Error('_vfsDelete not wired'); - } - - // ============================================================ - // Prompt Utility - // ============================================================ - - _promptPassphrase(message = 'Enter passphrase: ') { - return new Promise((resolve) => { - // WIRE: adapt to WebX11 terminal prompt - // Option A: if terminal has hidden prompt: - // this.terminal.prompt(message, { hidden: true }).then(resolve); - // Option B: temporary override: - const savedHandler = this.terminal.onCommand; - this.terminal.print(message); - this.terminal.onCommand = (line) => { - this.terminal.onCommand = savedHandler; - resolve(line); - }; - }); - } - - _confirmPrompt(message) { - return new Promise((resolve) => { - this.terminal.print(message + ' (yes/no): '); - const savedHandler = this.terminal.onCommand; - this.terminal.onCommand = (line) => { - this.terminal.onCommand = savedHandler; - resolve(line.trim().toLowerCase() === 'yes'); - }; - }); - } - - // ============================================================ - // GZIP - // ============================================================ - - async _cmdGzip(args) { - const path = args[0]; - if (!path) throw new Error('Usage: gzip <path>'); - - const exists = await this._vfsExists(path); - if (!exists) throw new Error(`File not found: ${path}`); - - const outPath = path + '.gz'; - if (await this._vfsExists(outPath)) { - const ok = await this._confirmPrompt(`Overwrite ${outPath}?`); - if (!ok) return 'Cancelled.'; - } - - const content = await this._vfsRead(path); - const compressed = await this._gzipString(content); - await this._vfsWrite(outPath, compressed); - await this._vfsDelete(path); - - const ratio = (compressed.length / Math.max(content.length, 1)).toFixed(2); - return `gzipped: ${path} → ${outPath} (${content.length} → ${compressed.length} chars, ratio ${ratio})`; - } - - async _cmdGunzip(args) { - const path = args[0]; - if (!path || !path.endsWith('.gz')) throw new Error('Usage: gunzip <path.gz>'); - - const exists = await this._vfsExists(path); - if (!exists) throw new Error(`File not found: ${path}`); - - const outPath = path.replace(/\.gz$/, ''); - if (await this._vfsExists(outPath)) { - const ok = await this._confirmPrompt(`Overwrite ${outPath}?`); - if (!ok) return 'Cancelled.'; - } - - const compressed = await this._vfsRead(path); - const content = await this._gunzipString(compressed); - await this._vfsWrite(outPath, content); - await this._vfsDelete(path); - - return `gunzipped: ${path} → ${outPath} (${compressed.length} chars)`; - } - - async _gzipString(str) { - const encoder = new TextEncoder(); - const bytes = encoder.encode(str); - const cs = new CompressionStream('gzip'); - const writer = cs.writable.getWriter(); - writer.write(bytes); - writer.close(); - const compressed = await new Response(cs.readable).arrayBuffer(); - return this._bytesToBase64(new Uint8Array(compressed)); - } - - async _gunzipString(b64) { - const compressed = this._base64ToBytes(b64); - const ds = new DecompressionStream('gzip'); - const writer = ds.writable.getWriter(); - writer.write(compressed); - writer.close(); - const decompressed = await new Response(ds.readable).arrayBuffer(); - return new TextDecoder().decode(decompressed); - } - - // ============================================================ - // ENCRYPT / DECRYPT (AES-256-GCM) - // ============================================================ - - async _cmdEncrypt(args) { - const path = args[0]; - if (!path) throw new Error('Usage: encrypt <path>'); - - const exists = await this._vfsExists(path); - if (!exists) throw new Error(`File not found: ${path}`); - - const outPath = path + '.enc'; - if (await this._vfsExists(outPath)) { - const ok = await this._confirmPrompt(`Overwrite ${outPath}?`); - if (!ok) return 'Cancelled.'; - } - - const passphrase = await this._promptPassphrase('Enter passphrase: '); - if (!passphrase || passphrase.length < 4) throw new Error('Passphrase too short (min 4 chars)'); - - const plaintext = await this._vfsRead(path); - const envelope = await this._encryptString(plaintext, passphrase); - await this._vfsWrite(outPath, JSON.stringify(envelope, null, 0)); - await this._vfsDelete(path); - - return `encrypted: ${path} → ${outPath}`; - } - - async _cmdDecrypt(args) { - const path = args[0]; - if (!path || !path.endsWith('.enc')) throw new Error('Usage: decrypt <path.enc>'); - - const exists = await this._vfsExists(path); - if (!exists) throw new Error(`File not found: ${path}`); - - const outPath = path.replace(/\.enc$/, ''); - if (await this._vfsExists(outPath)) { - const ok = await this._confirmPrompt(`Overwrite ${outPath}?`); - if (!ok) return 'Cancelled.'; - } - - const passphrase = await this._promptPassphrase('Enter passphrase: '); - if (!passphrase) throw new Error('Passphrase required'); - - const envelopeJson = await this._vfsRead(path); - let envelope; - try { - envelope = JSON.parse(envelopeJson); - } catch (e) { - throw new Error('Invalid .enc file format (expected JSON envelope)'); - } - - const plaintext = await this._decryptEnvelope(envelope, passphrase); - await this._vfsWrite(outPath, plaintext); - await this._vfsDelete(path); - - return `decrypted: ${path} → ${outPath}`; - } - - async _encryptString(plaintext, passphrase) { - const salt = crypto.getRandomValues(new Uint8Array(16)); - const iv = crypto.getRandomValues(new Uint8Array(12)); - const key = await this._deriveKey(passphrase, salt); - - const encoder = new TextEncoder(); - const ciphertext = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv }, - key, - encoder.encode(plaintext) - ); - - return { - v: 1, // format version - alg: 'AES-256-GCM+PBKDF2', - salt: this._bytesToBase64(salt), - iv: this._bytesToBase64(iv), - ct: this._bytesToBase64(new Uint8Array(ciphertext)), - }; - } - - async _decryptEnvelope(envelope, passphrase) { - if (!envelope || envelope.v !== 1) throw new Error('Unsupported envelope version'); - - const salt = this._base64ToBytes(envelope.salt); - const iv = this._base64ToBytes(envelope.iv); - const ct = this._base64ToBytes(envelope.ct); - const key = await this._deriveKey(passphrase, salt); - - try { - const decrypted = await crypto.subtle.decrypt( - { name: 'AES-GCM', iv }, - key, - ct - ); - return new TextDecoder().decode(decrypted); - } catch (e) { - throw new Error('Decryption failed — wrong passphrase or corrupted file'); - } - } - - async _deriveKey(passphrase, salt) { - const encoder = new TextEncoder(); - const baseKey = await crypto.subtle.importKey( - 'raw', - encoder.encode(passphrase), - { name: 'PBKDF2' }, - false, - ['deriveKey'] - ); - return crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt: salt, - iterations: 100000, - hash: 'SHA-256', - }, - baseKey, - { name: 'AES-GCM', length: 256 }, - false, - ['encrypt', 'decrypt'] - ); - } - - // ============================================================ - // Base64 Helpers (binary-safe, no deprecated btoa/unescape) - // ============================================================ - - _bytesToBase64(bytes) { - // Works in all modern browsers + WebX11 - const bin = Array.from(bytes, (b) => String.fromCharCode(b)).join(''); - return btoa(bin); - } - - _base64ToBytes(b64) { - const bin = atob(b64); - return Uint8Array.from(bin, (c) => c.charCodeAt(0)); - } -} - -// ================================================================ -// EXPORTS — uncomment the appropriate line for your loader: -// ================================================================ -// window.CryptoGzipModule = CryptoGzipModule; // global registration -// export { CryptoGzipModule }; // ES module |