diff --git a/src/platforms/win32.ts b/src/platforms/win32.ts index 94a78d7..59a16e4 100644 --- a/src/platforms/win32.ts +++ b/src/platforms/win32.ts @@ -9,6 +9,17 @@ import { run, sudo } from '../utils'; import UI from '../user-interface'; const debug = createDebug('devcert:platforms:windows'); +const ALG_NAME = 'aes-256-gcm'; +const ALG_SIZES = { + NONCE: 16, + TAG: 16, + KEY: 16, +}; +const PBKDF2 = { + NAME: 'sha256', + SALT_SIZE: 16, + ITER: 32767, +}; let encryptionKey: string; @@ -44,7 +55,7 @@ export default class WindowsPlatform implements Platform { debug('Error opening Firefox, most likely Firefox is not installed'); } } - + removeFromTrustStores(certificatePath: string) { debug('removing devcert root from Windows OS trust store'); try { @@ -61,7 +72,7 @@ export default class WindowsPlatform implements Platform { await sudo(`echo 127.0.0.1 ${ domain } >> ${ this.HOST_FILE_PATH }`); } } - + deleteProtectedFiles(filepath: string) { assertNotTouchingFiles(filepath, 'delete'); rimraf(filepath); @@ -94,14 +105,68 @@ export default class WindowsPlatform implements Platform { write(filepath, encryptedContents); } - private encrypt(text: string, key: string) { - let cipher = crypto.createCipher('aes256', new Buffer(key)); - return cipher.update(text, 'utf8', 'hex') + cipher.final('hex'); - } + private getPbkdf2 = (password: string, salt: Buffer): Buffer => { + return crypto.pbkdf2Sync( + Buffer.from(password, 'utf8'), + salt, + PBKDF2.ITER, + ALG_SIZES.KEY, + PBKDF2.NAME + ); + }; - private decrypt(encrypted: string, key: string) { - let decipher = crypto.createDecipher('aes256', new Buffer(key)); - return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8'); - } + private getCryptoKey = (key: Buffer) => + crypto.createHash('sha256').update(key).digest(); + + private encrypt = (plainText: string, password: string): string => { + // Generate a 128-bit salt using a PBKDF2. + const salt = crypto.randomBytes(PBKDF2.SALT_SIZE); + + // Derive a key using PBKDF2. + const key = this.getCryptoKey(this.getPbkdf2(password, salt)); + + // Generate a 128-bit nonce using a CSPRNG. + const nonce = crypto.randomBytes(ALG_SIZES.NONCE); + + // Create the cipher instance. + const cipher = crypto.createCipheriv(ALG_NAME, key, nonce); + + // Encrypt and prepend salt. + return Buffer.concat([ + salt, + nonce, + cipher.update(plainText, 'utf8'), + cipher.final(), + cipher.getAuthTag(), + ]).toString('base64'); + }; + + private decrypt = ( + base64EncryptedContent: string, + password: string + ): string => { + // Decode the base64. + const encCipher = Buffer.from(base64EncryptedContent, 'base64'); + + // Create buffers of salt, nonce, encryption cipher, and auth tag + let position = 0; + const salt = encCipher.slice(position, (position += PBKDF2.SALT_SIZE)); + const nonce = encCipher.slice(position, (position += ALG_SIZES.NONCE)); + const encContent = encCipher.slice( + position, + encCipher.length - ALG_SIZES.TAG + ); + const tag = encCipher.slice(encCipher.length - ALG_SIZES.TAG); + + // Derive the key using PBKDF2. + const key = this.getCryptoKey(this.getPbkdf2(password, salt)); + + const decipher = crypto.createDecipheriv(ALG_NAME, key, nonce); + decipher.setAuthTag(tag); -} \ No newline at end of file + return Buffer.concat([ + decipher.update(encContent), + decipher.final(), + ]).toString('utf8'); + }; +}