From 30322803d860d71208fd64a7e8f8ab1ee79d278d Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Tue, 15 Oct 2019 11:23:56 -1000 Subject: [PATCH 1/9] Fixes typescript runtime errors --- package.json | 6 +++--- yarn.lock | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f2893aa..c5e8699 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "homepage": "https://github.com/davewasmer/devcert#readme", "devDependencies": { "standard-version": "^4.3.0", - "typescript": "^2.7.0" + "typescript": "^2.9.2" }, "dependencies": { "@types/configstore": "^2.1.1", @@ -52,7 +52,7 @@ "rimraf": "^2.6.2", "sudo-prompt": "^8.2.0", "tmp": "^0.0.33", - "tslib": "^1.8.1" + "tslib": "^1.10.0" }, "optionalDependencies": {} -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9d83f5a..3a5385e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1231,17 +1231,19 @@ trim-off-newlines@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" -tslib@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" +tslib@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.7.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" +typescript@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" + integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== uglify-js@^2.6: version "2.8.18" From 9ce6762f0587d4b0e59cda62a342f3f4f92fd1f8 Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Tue, 15 Oct 2019 11:25:07 -1000 Subject: [PATCH 2/9] Encryption/chmod not required for certificate, only key --- src/certificate-authority.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/certificate-authority.ts b/src/certificate-authority.ts index 12de656..e3ad11d 100644 --- a/src/certificate-authority.ts +++ b/src/certificate-authority.ts @@ -35,7 +35,6 @@ export default async function installCertificateAuthority(options: Options = {}) debug(`Generating a root certificate authority`); let rootKeyPath = mktmp(); - let rootCertPath = mktmp(); debug(`Generating the OpenSSL configuration needed to setup the certificate authority`); seedConfigFiles(); @@ -44,13 +43,13 @@ export default async function installCertificateAuthority(options: Options = {}) generateKey(rootKeyPath); debug(`Generating a CA certificate`); - openssl(`req -new -x509 -config "${ caSelfSignConfig }" -key "${ rootKeyPath }" -out "${ rootCertPath }" -days 7000`); + openssl(`req -new -x509 -config "${caSelfSignConfig}" -key "${rootKeyPath}" -out "${rootCACertPath}" -days 7000`); debug('Saving certificate authority credentials'); - await saveCertificateAuthorityCredentials(rootKeyPath, rootCertPath); + await saveCertificateAuthorityCredentials(rootKeyPath); debug(`Adding the root certificate authority to trust stores`); - await currentPlatform.addToTrustStores(rootCertPath, options); + await currentPlatform.addToTrustStores(rootCACertPath, options); } /** @@ -71,7 +70,7 @@ function scrubOldInsecureVersions() { } // Delete the root certificate keys, as well as the generated app certificates - debug(`Checking ${ configDir } for legacy files ...`); + debug(`Checking ${configDir} for legacy files ...`); [ path.join(configDir, 'openssl.conf'), path.join(configDir, 'devcert-ca-root.key'), @@ -80,7 +79,7 @@ function scrubOldInsecureVersions() { path.join(configDir, 'certs') ].forEach((filepath) => { if (exists(filepath)) { - debug(`Removing legacy file: ${ filepath }`) + debug(`Removing legacy file: ${filepath}`) rimraf(filepath); } }); @@ -101,20 +100,14 @@ function seedConfigFiles() { export async function withCertificateAuthorityCredentials(cb: ({ caKeyPath, caCertPath }: { caKeyPath: string, caCertPath: string }) => Promise | void) { debug(`Retrieving devcert's certificate authority credentials`); let tmpCAKeyPath = mktmp(); - let tmpCACertPath = mktmp(); let caKey = await currentPlatform.readProtectedFile(rootCAKeyPath); - let caCert = await currentPlatform.readProtectedFile(rootCACertPath); writeFile(tmpCAKeyPath, caKey); - writeFile(tmpCACertPath, caCert); - await cb({ caKeyPath: tmpCAKeyPath, caCertPath: tmpCACertPath }); + await cb({ caKeyPath: tmpCAKeyPath, caCertPath: rootCACertPath }); rm(tmpCAKeyPath); - rm(tmpCACertPath); } -async function saveCertificateAuthorityCredentials(keypath: string, certpath: string) { +async function saveCertificateAuthorityCredentials(keypath: string) { debug(`Saving devcert's certificate authority credentials`); let key = readFile(keypath, 'utf-8'); - let cert = readFile(certpath, 'utf-8'); await currentPlatform.writeProtectedFile(rootCAKeyPath, key); - await currentPlatform.writeProtectedFile(rootCACertPath, cert); } From 44ca0767d6a24a21259c770df6c8c2e7903020e2 Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Tue, 15 Oct 2019 11:26:07 -1000 Subject: [PATCH 3/9] Add run options & branch that returns caPath/caCert Buffer --- src/index.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 92893ce..d9f3e7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,8 @@ import { isWindows, pathForDomain, domainsDir, - rootCAKeyPath + rootCAKeyPath, + rootCACertPath } from './constants'; import currentPlatform from './platforms'; import installCertificateAuthority from './certificate-authority'; @@ -18,6 +19,7 @@ import UI, { UserInterface } from './user-interface'; const debug = createDebug('devcert'); export interface Options { + returnCa?: true | 'read', skipCertutilInstall?: true, skipHostsFile?: true, ui?: UserInterface @@ -34,16 +36,19 @@ export interface Options { * Returns a promise that resolves with { key, cert }, where `key` and `cert` * are Buffers with the contents of the certificate private key and certificate * file, respectively + * + * If `returnCa` is `true`, include path to CA cert as `ca` in return value. + * If `returnCa` is `read`, include Buffer with contents of CA cert in return value. */ export async function certificateFor(domain: string, options: Options = {}) { - debug(`Certificate requested for ${ domain }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`); + debug(`Certificate requested for ${domain}. Skipping certutil install: ${Boolean(options.skipCertutilInstall)}. Skipping hosts file: ${Boolean(options.skipHostsFile)}`); if (options.ui) { Object.assign(UI, options.ui); } if (!isMac && !isLinux && !isWindows) { - throw new Error(`Platform not supported: "${ process.platform }"`); + throw new Error(`Platform not supported: "${process.platform}"`); } if (!commandExists('openssl')) { @@ -59,7 +64,7 @@ export async function certificateFor(domain: string, options: Options = {}) { } if (!exists(pathForDomain(domain, `certificate.crt`))) { - debug(`Can't find certificate file for ${ domain }, so it must be the first request for ${ domain }. Generating and caching ...`); + debug(`Can't find certificate file for ${domain}, so it must be the first request for ${domain}. Generating and caching ...`); await generateDomainCertificate(domain); } @@ -68,6 +73,13 @@ export async function certificateFor(domain: string, options: Options = {}) { } debug(`Returning domain certificate`); + if (options.returnCa) { + return { + ca: options.returnCa === 'read' ? readFile(rootCACertPath) : rootCACertPath, + key: readFile(domainKeyPath), + cert: readFile(domainCertPath) + } + } return { key: readFile(domainKeyPath), cert: readFile(domainCertPath) From ecc8e18ef34b06aba33909f569f2152ea3227474 Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Tue, 15 Oct 2019 12:00:51 -1000 Subject: [PATCH 4/9] Reverse formatting changes --- src/certificate-authority.ts | 6 +++--- src/index.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/certificate-authority.ts b/src/certificate-authority.ts index e3ad11d..0abf47e 100644 --- a/src/certificate-authority.ts +++ b/src/certificate-authority.ts @@ -43,7 +43,7 @@ export default async function installCertificateAuthority(options: Options = {}) generateKey(rootKeyPath); debug(`Generating a CA certificate`); - openssl(`req -new -x509 -config "${caSelfSignConfig}" -key "${rootKeyPath}" -out "${rootCACertPath}" -days 7000`); + openssl(`req -new -x509 -config "${ caSelfSignConfig }" -key "${ rootKeyPath }" -out "${ rootCACertPath }" -days 7000`); debug('Saving certificate authority credentials'); await saveCertificateAuthorityCredentials(rootKeyPath); @@ -70,7 +70,7 @@ function scrubOldInsecureVersions() { } // Delete the root certificate keys, as well as the generated app certificates - debug(`Checking ${configDir} for legacy files ...`); + debug(`Checking ${ configDir } for legacy files ...`); [ path.join(configDir, 'openssl.conf'), path.join(configDir, 'devcert-ca-root.key'), @@ -79,7 +79,7 @@ function scrubOldInsecureVersions() { path.join(configDir, 'certs') ].forEach((filepath) => { if (exists(filepath)) { - debug(`Removing legacy file: ${filepath}`) + debug(`Removing legacy file: ${ filepath }`) rimraf(filepath); } }); diff --git a/src/index.ts b/src/index.ts index d9f3e7b..3ec6ba4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,14 +41,14 @@ export interface Options { * If `returnCa` is `read`, include Buffer with contents of CA cert in return value. */ export async function certificateFor(domain: string, options: Options = {}) { - debug(`Certificate requested for ${domain}. Skipping certutil install: ${Boolean(options.skipCertutilInstall)}. Skipping hosts file: ${Boolean(options.skipHostsFile)}`); + debug(`Certificate requested for ${ domain }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`); if (options.ui) { Object.assign(UI, options.ui); } if (!isMac && !isLinux && !isWindows) { - throw new Error(`Platform not supported: "${process.platform}"`); + throw new Error(`Platform not supported: "${ process.platform }"`); } if (!commandExists('openssl')) { @@ -64,7 +64,7 @@ export async function certificateFor(domain: string, options: Options = {}) { } if (!exists(pathForDomain(domain, `certificate.crt`))) { - debug(`Can't find certificate file for ${domain}, so it must be the first request for ${domain}. Generating and caching ...`); + debug(`Can't find certificate file for ${ domain }, so it must be the first request for ${ domain }. Generating and caching ...`); await generateDomainCertificate(domain); } From 3443dc5efac262f1baf3476593338a5f7d841df6 Mon Sep 17 00:00:00 2001 From: Jeremy Albright <1935258+Js-Brecht@users.noreply.github.com> Date: Thu, 21 Nov 2019 07:54:37 -1000 Subject: [PATCH 5/9] separate API concerns Separates `returnCa` into `getCaPath` and `getCaBuffer` Co-Authored-By: James Zetlen --- src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3ec6ba4..73e1d9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,8 @@ import UI, { UserInterface } from './user-interface'; const debug = createDebug('devcert'); export interface Options { - returnCa?: true | 'read', + getCaPath?: boolean, + getCaBuffer?: boolean, skipCertutilInstall?: true, skipHostsFile?: true, ui?: UserInterface @@ -96,4 +97,4 @@ export function configuredDomains() { export function removeDomain(domain: string) { return rimraf.sync(pathForDomain(domain)); -} \ No newline at end of file +} From b7c3857173b3dd66dea93bf83a808e4d5c37552c Mon Sep 17 00:00:00 2001 From: Jeremy Albright <1935258+Js-Brecht@users.noreply.github.com> Date: Thu, 21 Nov 2019 07:56:00 -1000 Subject: [PATCH 6/9] Add support for both `getCaPath` and `getCaBuffer` API interfaces Co-Authored-By: James Zetlen --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 73e1d9c..9a744d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,7 +76,8 @@ export async function certificateFor(domain: string, options: Options = {}) { debug(`Returning domain certificate`); if (options.returnCa) { return { - ca: options.returnCa === 'read' ? readFile(rootCACertPath) : rootCACertPath, + ca: options.getCaBuffer && readFile(rootCACertPath), + caPath: options.getCaPath && rootCACertPath, key: readFile(domainKeyPath), cert: readFile(domainCertPath) } From a8948d924c571cce7a93e7f5ebf548bf759f8962 Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Thu, 21 Nov 2019 10:59:28 -1000 Subject: [PATCH 7/9] TSDoc update --- src/index.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9a744d1..c31279c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,11 +18,16 @@ import UI, { UserInterface } from './user-interface'; const debug = createDebug('devcert'); -export interface Options { - getCaPath?: boolean, - getCaBuffer?: boolean, - skipCertutilInstall?: true, - skipHostsFile?: true, +export interface Options /* extends Partial */{ + /** Return the CA certificate data? */ + getCaBuffer?: boolean; + /** Return the path to the CA certificate? */ + getCaPath?: boolean; + /** If `certutil` is not installed already (for updating nss databases; e.g. firefox), do not attempt to install it */ + skipCertutilInstall?: boolean, + /** Do not update your systems host file with the domain name of the certificate */ + skipHostsFile?: boolean, + /** User interface hooks */ ui?: UserInterface } @@ -37,9 +42,6 @@ export interface Options { * Returns a promise that resolves with { key, cert }, where `key` and `cert` * are Buffers with the contents of the certificate private key and certificate * file, respectively - * - * If `returnCa` is `true`, include path to CA cert as `ca` in return value. - * If `returnCa` is `read`, include Buffer with contents of CA cert in return value. */ export async function certificateFor(domain: string, options: Options = {}) { debug(`Certificate requested for ${ domain }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`); From 609a69367d9c169b652ea1fd06a821826e22dd85 Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Thu, 21 Nov 2019 11:14:27 -1000 Subject: [PATCH 8/9] Update conditions / return type --- src/index.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index c31279c..4a1b9a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,20 @@ export interface Options /* extends Partial */{ ui?: UserInterface } +interface ICaBuffer { + ca: Buffer; +} +interface ICaPath { + caPath: string; +} +interface IDomainData { + key: Buffer; + cert: Buffer; +} +type IReturnCa = O['getCaBuffer'] extends true ? ICaBuffer : false; +type IReturnCaPath = O['getCaPath'] extends true ? ICaPath : false; +type IReturnData = (IDomainData) & (IReturnCa) & (IReturnCaPath); + /** * Request an SSL certificate for the given app name signed by the devcert root * certificate authority. If devcert has previously generated a certificate for @@ -43,7 +57,7 @@ export interface Options /* extends Partial */{ * are Buffers with the contents of the certificate private key and certificate * file, respectively */ -export async function certificateFor(domain: string, options: Options = {}) { +export async function certificateFor(domain: string, options: O = {} as O): Promise> { debug(`Certificate requested for ${ domain }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`); if (options.ui) { @@ -76,18 +90,15 @@ export async function certificateFor(domain: string, options: Options = {}) { } debug(`Returning domain certificate`); - if (options.returnCa) { - return { - ca: options.getCaBuffer && readFile(rootCACertPath), - caPath: options.getCaPath && rootCACertPath, - key: readFile(domainKeyPath), - cert: readFile(domainCertPath) - } - } - return { + + const ret = { key: readFile(domainKeyPath), cert: readFile(domainCertPath) - }; + } as IReturnData; + if (options.getCaBuffer) (ret as ICaBuffer).ca = readFile(rootCACertPath); + if (options.getCaPath) (ret as ICaPath).caPath = rootCACertPath; + + return ret; } export function hasCertificateFor(domain: string) { From e87fb6d7fd9c7453e62559b7050e5e7bd59753fd Mon Sep 17 00:00:00 2001 From: Jeremy Albright Date: Thu, 21 Nov 2019 11:44:51 -1000 Subject: [PATCH 9/9] Add additional flags to main function TSDoc --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index 4a1b9a4..7c9afed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,6 +56,12 @@ type IReturnData = (IDomainData) & (IReturnCa) & (IRe * Returns a promise that resolves with { key, cert }, where `key` and `cert` * are Buffers with the contents of the certificate private key and certificate * file, respectively + * + * If `options.getCaBuffer` is true, return value will include the ca certificate data + * as { ca: Buffer } + * + * If `options.getCaPath` is true, return value will include the ca certificate path + * as { caPath: string } */ export async function certificateFor(domain: string, options: O = {} as O): Promise> { debug(`Certificate requested for ${ domain }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`);