UNPKG

@commit451/salamander

Version:

Never be AFK

123 lines 4.6 kB
import { CryptoService } from './crypto.js'; import { StorageService } from '../utils/storage.js'; export class KeyManagerService { static KEYS_STORAGE_KEY = 'salamander_runner_keys'; static KEY_EXPIRY_DAYS = 30; /** * Initialize keys for a new runner */ static async initializeRunnerKeys(runnerId) { const ecdhKeyPair = CryptoService.generateECDHKeyPair(); const runnerKeys = { ecdhKeyPair, createdAt: Date.now() }; await this.storeRunnerKeys(runnerId, runnerKeys); return ecdhKeyPair; } /** * Complete the key exchange with the Flutter app's public key */ static async completeKeyExchange(runnerId, flutterPublicKey) { const runnerKeys = await this.getRunnerKeys(runnerId); if (!runnerKeys) { throw new Error(`No keys found for runner ${runnerId}`); } // Derive shared secret using our private key and Flutter's public key const sharedSecret = CryptoService.deriveSharedSecret(runnerKeys.ecdhKeyPair.privateKey, flutterPublicKey); // Update stored keys with shared secret runnerKeys.sharedSecret = sharedSecret; runnerKeys.keyHash = CryptoService.createKeyHash(sharedSecret); await this.storeRunnerKeys(runnerId, runnerKeys); return sharedSecret; } /** * Get the shared secret for encryption/decryption */ static async getSharedSecret(runnerId) { const runnerKeys = await this.getRunnerKeys(runnerId); if (!runnerKeys?.sharedSecret) { return null; } // Check if keys are expired const daysSinceCreation = (Date.now() - runnerKeys.createdAt) / (1000 * 60 * 60 * 24); if (daysSinceCreation > this.KEY_EXPIRY_DAYS) { console.warn(`Keys for runner ${runnerId} have expired. Consider key rotation.`); } return runnerKeys.sharedSecret; } /** * Get the CLI's public key for a runner */ static async getPublicKey(runnerId) { const runnerKeys = await this.getRunnerKeys(runnerId); return runnerKeys?.ecdhKeyPair.publicKey ?? null; } /** * Check if keys exist and are properly initialized for a runner */ static async hasValidKeys(runnerId) { const runnerKeys = await this.getRunnerKeys(runnerId); return !!(runnerKeys?.ecdhKeyPair && runnerKeys?.sharedSecret); } /** * Rotate keys for a runner (generate new key pair) */ static async rotateKeys(runnerId) { console.log(`Rotating keys for runner ${runnerId}`); return await this.initializeRunnerKeys(runnerId); } /** * Remove keys for a runner (when runner is deleted) */ static async removeRunnerKeys(runnerId) { const allKeys = await this.getAllStoredKeys(); delete allKeys[runnerId]; await StorageService.set(this.KEYS_STORAGE_KEY, allKeys); } /** * Get all runners that have encryption keys */ static async getEncryptedRunnerIds() { const allKeys = await this.getAllStoredKeys(); return Object.keys(allKeys).filter(runnerId => { const keys = allKeys[runnerId]; return keys?.sharedSecret; }); } /** * Cleanup expired keys */ static async cleanupExpiredKeys() { const allKeys = await this.getAllStoredKeys(); const now = Date.now(); let cleanedCount = 0; const updatedKeys = {}; for (const [runnerId, keys] of Object.entries(allKeys)) { const daysSinceCreation = (now - keys.createdAt) / (1000 * 60 * 60 * 24); if (daysSinceCreation <= this.KEY_EXPIRY_DAYS) { updatedKeys[runnerId] = keys; } else { cleanedCount++; console.log(`Cleaned up expired keys for runner ${runnerId}`); } } await StorageService.set(this.KEYS_STORAGE_KEY, updatedKeys); return cleanedCount; } // Private helper methods static async getRunnerKeys(runnerId) { const allKeys = await this.getAllStoredKeys(); return allKeys[runnerId] ?? null; } static async storeRunnerKeys(runnerId, keys) { const allKeys = await this.getAllStoredKeys(); allKeys[runnerId] = keys; await StorageService.set(this.KEYS_STORAGE_KEY, allKeys); } static async getAllStoredKeys() { return (await StorageService.get(this.KEYS_STORAGE_KEY)) ?? {}; } } //# sourceMappingURL=key-manager.js.map