@adobe/ccweb-add-on-devcert
Version:
Generate trusted local SSL/TLS certificates for local SSL development
130 lines (111 loc) • 4.82 kB
text/typescript
import createDebug from 'debug';
import crypto from 'crypto';
import { existsSync as exists, writeFileSync as write, readFileSync as read } from 'fs';
import { sync as rimraf } from 'rimraf';
import { Options } from '../index';
import { assertNotTouchingFiles, openCertificateInFirefox } from './shared';
import { Platform } from '.';
import { run, sudo } from '../utils';
import UI from '../user-interface';
const debug = createDebug('devcert:platforms:windows');
let encryptionKey: string;
export default class WindowsPlatform implements Platform {
private HOST_FILE_PATH = 'C:\\Windows\\System32\\Drivers\\etc\\hosts';
/**
* Windows is at least simple. Like macOS, most applications will delegate to
* the system trust store, which is updated with the confusingly named
* `certutil` exe (not the same as the NSS/Mozilla certutil). Firefox does it's
* own thing as usual, and getting a copy of NSS certutil onto the Windows
* machine to try updating the Firefox store is basically a nightmare, so we
* don't even try it - we just bail out to the GUI.
*/
async addToTrustStores(certificatePath: string, options: Options = {}): Promise<void> {
// IE, Chrome, system utils
debug('adding devcert root to Windows OS trust store')
try {
run('certutil', ['-addstore', '-user', 'root', certificatePath]);
} catch (e) {
e.output.map((buffer: Buffer) => {
if (buffer) {
console.log(buffer.toString());
}
});
}
debug('adding devcert root to Firefox trust store')
// Firefox (don't even try NSS certutil, no easy install for Windows)
try {
await openCertificateInFirefox('start firefox', certificatePath);
} catch {
debug('Error opening Firefox, most likely Firefox is not installed');
}
}
removeFromTrustStores(certificatePath: string) {
debug('removing devcert root from Windows OS trust store');
try {
console.warn('Removing old certificates from trust stores. You may be prompted to grant permission for this. It\'s safe to delete old devcert certificates.');
run('certutil', ['-delstore', '-user', 'root', 'devcert']);
} catch (e) {
debug(`failed to remove ${ certificatePath } from Windows OS trust store, continuing. ${ e.toString() }`)
}
}
async addDomainToHostFileIfMissing(domain: string) {
if (!exists(this.HOST_FILE_PATH)) {
console.warn('Could not locate the host file in your system.');
console.warn('Please ensure to have:');
console.log(`127.0.0.1 ${domain}`);
console.warn("entry in your system's host file.");
return;
}
let hostsFileContents = read(this.HOST_FILE_PATH, 'utf8');
if (!hostsFileContents.includes(domain)) {
await sudo(`echo 127.0.0.1 ${ domain } >> ${ this.HOST_FILE_PATH }`);
}
}
deleteProtectedFiles(filepath: string) {
assertNotTouchingFiles(filepath, 'delete');
rimraf(filepath);
}
async readProtectedFile(filepath: string): Promise<string> {
assertNotTouchingFiles(filepath, 'read');
if (!encryptionKey) {
encryptionKey = await UI.getWindowsEncryptionPassword();
}
// Try to decrypt the file
try {
return this.decrypt(read(filepath, 'utf8'), encryptionKey);
} catch (e) {
// If it's a bad password, clear the cached copy and retry
if (e.message.indexOf('bad decrypt') >= -1) {
encryptionKey = null;
return await this.readProtectedFile(filepath);
}
throw e;
}
}
async writeProtectedFile(filepath: string, contents: string) {
assertNotTouchingFiles(filepath, 'write');
if (!encryptionKey) {
encryptionKey = await UI.getWindowsEncryptionPassword();
}
let encryptedContents = this.encrypt(contents, encryptionKey);
write(filepath, encryptedContents);
}
private encrypt(text: string, key: string): string {
const algorithm = 'aes-256-cbc';
const iv = crypto.randomBytes(16);
const keyBuffer = crypto.createHash('sha256').update(key).digest();
const cipher = crypto.createCipheriv(algorithm, keyBuffer, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
private decrypt(encryptedText: string, key: string): string {
const algorithm = 'aes-256-cbc';
const keyBuffer = crypto.createHash('sha256').update(key).digest();
const [ivHex, encryptedHex] = encryptedText.split(':');
const iv = Buffer.from(ivHex, 'hex');
const encryptedBuffer = Buffer.from(encryptedHex, 'hex');
const decipher = crypto.createDecipheriv(algorithm, keyBuffer, iv);
const decrypted = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
return decrypted.toString('utf8');
}
}