-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 967e4c1
Showing
6 changed files
with
486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
[ ca ] | ||
# `man ca` | ||
default_ca = CA_default | ||
|
||
[ CA_default ] | ||
default_md = sha256 | ||
name_opt = ca_default | ||
cert_opt = ca_default | ||
policy = policy_loose | ||
database = index.txt | ||
serial = serial | ||
prompt = no | ||
|
||
[ policy_loose ] | ||
# Only require minimal information for development certificates | ||
commonName = supplied | ||
|
||
[ req ] | ||
# Options for the `req` tool (`man req`). | ||
default_bits = 2048 | ||
distinguished_name = req_distinguished_name | ||
string_mask = utf8only | ||
|
||
# SHA-1 is deprecated, so use SHA-2 instead. | ||
default_md = sha256 | ||
|
||
# Extension to add when the -x509 option is used. | ||
x509_extensions = v3_ca | ||
|
||
[ req_distinguished_name ] | ||
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>. | ||
commonName = Common Name | ||
|
||
[ v3_ca ] | ||
# Extensions for a typical CA (`man x509v3_config`). | ||
subjectKeyIdentifier = hash | ||
authorityKeyIdentifier = keyid:always,issuer | ||
basicConstraints = critical, CA:true | ||
keyUsage = critical, digitalSignature, cRLSign, keyCertSign | ||
|
||
[ server_cert ] | ||
# Extensions for server certificates (`man x509v3_config`). | ||
basicConstraints = CA:FALSE | ||
nsCertType = server | ||
nsComment = "OpenSSL Generated Server Certificate" | ||
subjectKeyIdentifier = hash | ||
authorityKeyIdentifier = keyid,issuer:always | ||
keyUsage = critical, digitalSignature, keyEncipherment | ||
extendedKeyUsage = serverAuth |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"name": "devcert", | ||
"version": "1.0.0", | ||
"description": "Generate trusted local SSL/TLS certificates for local SSL development", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/davewasmer/devcert.git" | ||
}, | ||
"keywords": [ | ||
"ssl", | ||
"certificate", | ||
"openssl", | ||
"trust" | ||
], | ||
"author": "Dave Wasmer", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/davewasmer/devcert/issues" | ||
}, | ||
"homepage": "https://github.com/davewasmer/devcert#readme", | ||
"devDependencies": { | ||
"typescript": "^2.2.2" | ||
}, | ||
"dependencies": { | ||
"@types/configstore": "^2.1.1", | ||
"@types/es6-promise": "^0.0.32", | ||
"@types/get-port": "^0.0.4", | ||
"@types/glob": "^5.0.30", | ||
"@types/node": "^7.0.11", | ||
"@types/tmp": "^0.0.32", | ||
"command-exists": "^1.2.2", | ||
"configstore": "^3.0.0", | ||
"get-port": "^3.0.0", | ||
"glob": "^7.1.1", | ||
"tmp": "^0.0.31" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import { | ||
readFileSync, | ||
readdirSync, | ||
writeFileSync, | ||
unlinkSync, | ||
chmodSync, | ||
existsSync | ||
} from 'fs'; | ||
import * as path from 'path'; | ||
import * as getPort from 'get-port'; | ||
import * as http from 'http'; | ||
import { execSync } from 'child_process'; | ||
import * as tmp from 'tmp'; | ||
import * as glob from 'glob'; | ||
import * as Configstore from 'configstore'; | ||
import { sync as commandExists } from 'command-exists'; | ||
|
||
const isMac = process.platform === 'darwin'; | ||
const isLinux = process.platform === 'linux'; | ||
const isWindows = process.platform === 'win32'; | ||
|
||
// use %LOCALAPPDATA%/Yarn on Windows otherwise use ~/.config/yarn | ||
let configDir: string; | ||
if (isWindows && process.env.LOCALAPPDATA) { | ||
configDir = path.join(process.env.LOCALAPPDATA, 'Yarn', 'config'); | ||
} else { | ||
let uid = process.getuid && process.getuid(); | ||
let userHome = (isLinux && uid === 0) ? path.resolve('/usr/local/share') : require('os').homedir(); | ||
path.join(userHome, '.config', 'yarn'); | ||
} | ||
const configPath = path.join.bind(path, configDir); | ||
|
||
const opensslConfPath = path.join(__dirname, '..', 'openssl.conf'); | ||
const rootKeyPath = configPath('devcert-ca-root.key'); | ||
const rootCertPath = configPath('devcert-ca-root.crt'); | ||
|
||
interface Options { | ||
installCertutil?: boolean; | ||
} | ||
|
||
interface Certificate { | ||
key: string; | ||
cert: string; | ||
} | ||
|
||
export default function devcert(appName: string, options: Options = {}) { | ||
|
||
// Fail fast on unsupported platforms (PRs welcome!) | ||
if (!isMac && !isLinux && !isWindows) { | ||
throw new Error(`devcert: "${ process.platform }" platform not supported`); | ||
} | ||
if (!commandExists('openssl')) { | ||
throw new Error('Unable to find openssl - make sure it is installed and available in your PATH'); | ||
} | ||
|
||
if (!existsSync(configPath('devcert-ca-root.key'))) { | ||
installCertificateAuthority(options.installCertutil); | ||
} | ||
|
||
// Load our root CA and sign a new app cert with it. | ||
let appKeyPath = generateKey(appName); | ||
let appCertificatePath = generateSignedCertificate(appName, appKeyPath); | ||
|
||
return { | ||
keyPath: appKeyPath, | ||
certificatePath: appCertificatePath, | ||
key: readFileSync(appKeyPath), | ||
certificate: readFileSync(appCertificatePath) | ||
}; | ||
|
||
} | ||
|
||
// Install the once-per-machine trusted root CA. We'll use this CA to sign per-app certs, allowing | ||
// us to minimize the need for elevated permissions while still allowing for per-app certificates. | ||
function installCertificateAuthority(installCertutil) { | ||
let rootKeyPath = generateKey('devcert-ca-root'); | ||
execSync(`openssl req -config ${ opensslConfPath } -key ${ rootKeyPath } -out ${ rootCertPath } -new -subj '/CN=devcert' -x509 -days 7000 -extensions v3_ca`); | ||
addCertificateToTrustStores(installCertutil); | ||
} | ||
|
||
// Generate a cryptographic key, used to sign certificates or certificate signing requests. | ||
function generateKey(name: string): string { | ||
let filename = configPath(`${ name }.key`); | ||
execSync(`openssl genrsa -out ${ filename } 2048`); | ||
chmodSync(filename, 400); | ||
return filename; | ||
} | ||
|
||
// Generate a certificate signed by the devcert root CA | ||
function generateSignedCertificate(name: string, keyPath: string): string { | ||
let csrFile = configPath(`${ name }.csr`) | ||
execSync(`openssl req -config ${ opensslConfPath } -subj '/CN=${ name }' -key ${ keyPath } -out ${ csrFile } -new`); | ||
let certPath = configPath(`${ name }.crt`); | ||
execSync(`openssl ca -config ${ opensslConfPath } -in ${ csrFile } -out ${ certPath } -keyfile ${ rootKeyPath } -cert ${ rootCertPath } -notext -md sha256 -days 7000 -extensions server_cert`) | ||
return certPath; | ||
} | ||
|
||
// Add the devcert root CA certificate to the trust stores for this machine. Adds to OS level trust | ||
// stores, and where possible, to browser specific trust stores | ||
async function addCertificateToTrustStores(installCertutil: boolean): Promise<void> { | ||
|
||
if (isMac) { | ||
// Chrome, Safari, system utils | ||
execSync(`sudo security add-trusted-cert -r trustRoot -k /Library/Keychains/System.keychain -p ssl "${ rootCertPath }"`); | ||
// Firefox | ||
try { | ||
// Try to use certutil to install the cert automatically | ||
addCertificateToNSSCertDB('~/Library/Application Support/Firefox/Profiles/*', installCertutil); | ||
} catch (e) { | ||
// Otherwise, open the cert in Firefox to install it | ||
await openCertificateInFirefox('/Applications/Firefox.app/Contents/MacOS/firefox'); | ||
} | ||
|
||
} else if (isLinux) { | ||
// system utils | ||
execSync(`sudo cp ${ rootCertPath } /usr/local/share/ca-certificates/devcert.cer && update-ca-certificates`); | ||
// Firefox | ||
try { | ||
// Try to use certutil to install the cert automatically | ||
addCertificateToNSSCertDB('~/.mozilla/firefox/*', installCertutil); | ||
} catch (e) { | ||
// Otherwise, open the cert in Firefox to install it | ||
await openCertificateInFirefox('firefox'); | ||
} | ||
// Chrome | ||
// No try..catch, since there's no alternative here. Chrome won't prompt to add a cert to the | ||
// store if opened as a URL | ||
addCertificateToNSSCertDB('~/.pki/nssdb', installCertutil); | ||
|
||
// Windows | ||
} else if (isWindows) { | ||
// IE, Chrome, system utils | ||
execSync(`certutil -addstore -user root ${ rootCertPath }`); | ||
// Firefox (don't even try NSS certutil, no easy install for Windows) | ||
await openCertificateInFirefox('start firefox'); | ||
} | ||
|
||
} | ||
|
||
// Try to use certutil to add the root cert to an NSS database | ||
function addCertificateToNSSCertDB(nssDirGlob: string, installCertutil: boolean): void { | ||
let certutilPath = lookupOrInstallCertutil(installCertutil); | ||
if (!certutilPath) { | ||
throw new Error('certutil not available, and `installCertutil` was false'); | ||
} | ||
glob.sync(nssDirGlob).forEach((potentialNSSDBDir) => { | ||
if (existsSync(path.join(potentialNSSDBDir, 'cert8.db'))) { | ||
execSync(`${ certutilPath } -A -d ${ potentialNSSDBDir } -t 'C,,' -i ${ rootCertPath }`); | ||
} else if (existsSync(path.join(potentialNSSDBDir, 'cert9.db'))) { | ||
execSync(`${ certutilPath } -A -d sql:${ potentialNSSDBDir } -t 'C,,' -i ${ rootCertPath }`); | ||
} | ||
}); | ||
} | ||
|
||
// Launch a web server and open the root cert in Firefox. Useful for when certutil isn't available | ||
async function openCertificateInFirefox(firefoxPath: string) { | ||
let port = await getPort(); | ||
let server = http.createServer((req, res) => { | ||
res.writeHead(200, { 'Content-type': 'application/x-x509-ca-cert' }); | ||
res.write(readFileSync(rootCertPath)); | ||
res.end(); | ||
}).listen(port); | ||
execSync(`${ firefoxPath } http://localhost:${ port }`); | ||
await new Promise((resolve) => { | ||
process.stdin.resume(); | ||
process.stdin.on('data', resolve); | ||
}); | ||
} | ||
|
||
// Try to install certutil if it's not already available, and return the path to the executable | ||
function lookupOrInstallCertutil(options: Options): boolean | string { | ||
if (isMac) { | ||
if (commandExists('brew')) { | ||
let nssPath: string; | ||
try { | ||
return path.join(execSync('brew --prefix nss').toString(), 'bin', 'certutil'); | ||
} catch (e) { | ||
if (options.installCertutil) { | ||
execSync('brew install nss'); | ||
return path.join(execSync('brew --prefix nss').toString(), 'bin', 'certutil'); | ||
} | ||
} | ||
} | ||
} else if (isLinux) { | ||
if (!commandExists('certutil')) { | ||
if (options.installCertutil) { | ||
execSync('sudo apt install libnss3-tools'); | ||
} else { | ||
return false; | ||
} | ||
} | ||
return execSync('which certutil').toString(); | ||
} | ||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"compileOnSave": false, | ||
"compilerOptions": { | ||
"declaration": true, | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"target": "ES2016", | ||
"noImplicitAny": true, | ||
"sourceMap": false, | ||
"importHelpers": true, | ||
"inlineSourceMap": true, | ||
"inlineSources": true, | ||
"outDir": "dist", | ||
"baseUrl": ".", | ||
"skipLibCheck": true, | ||
"sourceRoot": ".", | ||
} | ||
} |
Oops, something went wrong.