diff --git a/openssl-configurations/domain-certificate-signing-requests.conf b/openssl-configurations/domain-certificate-signing-requests.conf index 4196ba2..2290920 100644 --- a/openssl-configurations/domain-certificate-signing-requests.conf +++ b/openssl-configurations/domain-certificate-signing-requests.conf @@ -21,5 +21,4 @@ subjectAltName = @subject_alt_names subjectKeyIdentifier = hash [ subject_alt_names ] -DNS.1 = *.<%= domain %> -DNS.2 = <%= domain %> \ No newline at end of file +<%= subjectAltNames %> diff --git a/openssl-configurations/domain-certificates.conf b/openssl-configurations/domain-certificates.conf index 48eb5d6..0def6a3 100644 --- a/openssl-configurations/domain-certificates.conf +++ b/openssl-configurations/domain-certificates.conf @@ -35,5 +35,4 @@ extendedKeyUsage = serverAuth subjectAltName = @subject_alt_names [ subject_alt_names ] -DNS.1 = *.<%= domain %> -DNS.2 = <%= domain %> \ No newline at end of file +<%= subjectAltNames %> diff --git a/package.json b/package.json index 3ca28cc..b7c80d9 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": {} } diff --git a/src/certificates.ts b/src/certificates.ts index 78fe1c3..563f38f 100644 --- a/src/certificates.ts +++ b/src/certificates.ts @@ -15,7 +15,8 @@ const debug = createDebug('devcert:certificates'); * individual domain certificates are signed by the devcert root CA (which was * added to the OS/browser trust stores), they are trusted. */ -export default async function generateDomainCertificate(domain: string): Promise { +export default async function generateDomainCertificate(domains: string | string[]): Promise { + const domain = Array.isArray(domains) ? domains[0] : domains; mkdirp(pathForDomain(domain)); debug(`Generating private key for ${ domain }`); @@ -43,4 +44,4 @@ export function generateKey(filename: string): void { debug(`generateKey: ${ filename }`); openssl(`genrsa -out "${ filename }" 2048`); chmod(filename, 400); -} \ No newline at end of file +} diff --git a/src/constants.ts b/src/constants.ts index 70b74ff..5ac3e83 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,29 +16,44 @@ export const configDir = applicationConfigPath('devcert'); export const configPath: (...pathSegments: string[]) => string = path.join.bind(path, configDir); export const domainsDir = configPath('domains'); -export const pathForDomain: (domain: string, ...pathSegments: string[]) => string = path.join.bind(path, domainsDir) +export const pathForDomain: (domain: string | string[], ...pathSegments: string[]) => string = path.join.bind(path, domainsDir) export const caVersionFile = configPath('devcert-ca-version'); export const opensslSerialFilePath = configPath('certificate-authority', 'serial'); export const opensslDatabaseFilePath = configPath('certificate-authority', 'index.txt'); export const caSelfSignConfig = path.join(__dirname, '../openssl-configurations/certificate-authority-self-signing.conf'); -export function withDomainSigningRequestConfig(domain: string, cb: (filepath: string) => void) { +function generatesubjectAltNames(domains: string[]) { + return domains + .flatMap(domain => [domain, `*.${domain}`]) + .map((domain, index) => `DNS.${index + 1} = ${domain}`) + .join("\r\n"); +} + +export function withDomainSigningRequestConfig(domains: string | string[], cb: (filepath: string) => void) { + const domainList = typeof domains === "string" ? [domains] : domains; + const domain = domainList[0]; + const subjectAltNames = generatesubjectAltNames(domainList); + let tmpFile = mktmp(); let source = readFile(path.join(__dirname, '../openssl-configurations/domain-certificate-signing-requests.conf'), 'utf-8'); let template = makeTemplate(source); - let result = template({ domain }); + let result = template({ domain, subjectAltNames }); writeFile(tmpFile, eol.auto(result)); cb(tmpFile); rm(tmpFile); } -export function withDomainCertificateConfig(domain: string, cb: (filepath: string) => void) { +export function withDomainCertificateConfig(domains: string | string[], cb: (filepath: string) => void) { + const domainList = typeof domains === "string" ? [domains] : domains; + const domain = domainList[0]; + const subjectAltNames = generatesubjectAltNames(domainList); + let tmpFile = mktmp(); let source = readFile(path.join(__dirname, '../openssl-configurations/domain-certificates.conf'), 'utf-8'); let template = makeTemplate(source); let result = template({ - domain, + subjectAltNames, serialFile: opensslSerialFilePath, databaseFile: opensslDatabaseFilePath, domainDir: pathForDomain(domain) diff --git a/src/index.ts b/src/index.ts index 92893ce..8f7500b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,8 +35,9 @@ export interface Options { * are Buffers with the contents of the certificate private key and certificate * file, respectively */ -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) }`); +export async function certificateFor(domains: string | string[], options: Options = {}) { + const domain = Array.isArray(domains) ? domains[0] : domains; + debug(`Certificate requested for ${ domains }. Skipping certutil install: ${ Boolean(options.skipCertutilInstall) }. Skipping hosts file: ${ Boolean(options.skipHostsFile) }`); if (options.ui) { Object.assign(UI, options.ui); @@ -59,12 +60,18 @@ 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 ...`); - await generateDomainCertificate(domain); + debug(`Can't find certificate file for ${ domains }, so it must be the first request for ${ domains }. Generating and caching ...`); + await generateDomainCertificate(domains); } if (!options.skipHostsFile) { - await currentPlatform.addDomainToHostFileIfMissing(domain); + if (Array.isArray(domains)) { + domains.forEach(async (domain) => { + await currentPlatform.addDomainToHostFileIfMissing(domain); + }) + } else { + await currentPlatform.addDomainToHostFileIfMissing(domain); + } } debug(`Returning domain certificate`); @@ -84,4 +91,4 @@ export function configuredDomains() { export function removeDomain(domain: string) { return rimraf.sync(pathForDomain(domain)); -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 2710aa5..32cf451 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,9 @@ "compileOnSave": false, "compilerOptions": { "declaration": true, + "lib": [ + "esnext" + ], "module": "commonjs", "moduleResolution": "node", "target": "ES2016", diff --git a/yarn.lock b/yarn.lock index 2f4d478..92fdf79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1230,17 +1230,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"