_ = require("lodash") fs = require("fs-extra") os = require("os") path = require("path") Forge = require("node-forge") Promise = require("bluebird") fs = Promise.promisifyAll(fs) pki = Forge.pki generateKeyPairAsync = Promise.promisify(pki.rsa.generateKeyPair) ipAddressRe = /^[\d\.]+$/ asterisksRe = /\*/g CAattrs = [{ name: "commonName", value: "CypressProxyCA" }, { name: "countryName", value: "Internet" }, { shortName: "ST", value: "Internet" }, { name: "localityName", value: "Internet" }, { name: "organizationName", value: "Cypress.io" }, { shortName: "OU", value: "CA" }] CAextensions = [{ name: "basicConstraints", cA: true }, { name: "keyUsage", keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true }, { name: "extKeyUsage", serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true }, { name: "nsCertType", client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true }, { name: "subjectKeyIdentifier" }] ServerAttrs = [{ name: "countryName", value: "Internet" }, { shortName: "ST", value: "Internet" }, { name: "localityName", value: "Internet" }, { name: "organizationName", value: "Cypress Proxy CA" }, { shortName: "OU", value: "Cypress Proxy Server Certificate" }] ServerExtensions = [{ name: "basicConstraints", cA: false }, { name: "keyUsage", keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: true, dataEncipherment: true }, { name: "extKeyUsage", serverAuth: true, clientAuth: true, codeSigning: false, emailProtection: false, timeStamping: false }, { name: "nsCertType", client: true, server: true, email: false, objsign: false, sslCA: false, emailCA: false, objCA: false }, { name: "subjectKeyIdentifier" }] class CA randomSerialNumber: -> ## generate random 16 bytes hex string sn = "" for i in [1..4] sn += ("00000000" + Math.floor(Math.random()*Math.pow(256, 4)).toString(16)).slice(-8) sn generateCA: -> generateKeyPairAsync({bits: 512}) .then (keys) => cert = pki.createCertificate() cert.publicKey = keys.publicKey cert.serialNumber = @randomSerialNumber() cert.validity.notBefore = new Date() cert.validity.notAfter = new Date() cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10) cert.setSubject(CAattrs) cert.setIssuer(CAattrs) cert.setExtensions(CAextensions) cert.sign(keys.privateKey, Forge.md.sha256.create()) @CAcert = cert @CAkeys = keys Promise.all([ fs.writeFileAsync(path.join(@certsFolder, "ca.pem"), pki.certificateToPem(cert)) fs.writeFileAsync(path.join(@keysFolder, "ca.private.key"), pki.privateKeyToPem(keys.privateKey)) fs.writeFileAsync(path.join(@keysFolder, "ca.public.key"), pki.publicKeyToPem(keys.publicKey)) ]) loadCA: -> Promise.props({ certPEM: fs.readFileAsync(path.join(@certsFolder, "ca.pem"), "utf-8") keyPrivatePEM: fs.readFileAsync(path.join(@keysFolder, "ca.private.key"), "utf-8") keyPublicPEM: fs.readFileAsync(path.join(@keysFolder, "ca.public.key"), "utf-8") }) .then (results) => @CAcert = pki.certificateFromPem(results.certPEM) @CAkeys = { privateKey: pki.privateKeyFromPem(results.keyPrivatePEM) publicKey: pki.publicKeyFromPem(results.keyPublicPEM) } .return(undefined) generateServerCertificateKeys: (hosts) -> hosts = [].concat(hosts) mainHost = hosts[0] keysServer = pki.rsa.generateKeyPair(1024) certServer = pki.createCertificate() certServer.publicKey = keysServer.publicKey certServer.serialNumber = @randomSerialNumber() certServer.validity.notBefore = new Date certServer.validity.notAfter = new Date certServer.validity.notAfter.setFullYear(certServer.validity.notBefore.getFullYear() + 2) attrsServer = _.clone(ServerAttrs) attrsServer.unshift({ name: "commonName", value: mainHost }) certServer.setSubject(attrsServer) certServer.setIssuer(@CAcert.issuer.attributes) certServer.setExtensions(ServerExtensions.concat([{ name: "subjectAltName", altNames: hosts.map (host) -> if host.match(ipAddressRe) {type: 7, ip: host} else {type: 2, value: host} }])) certServer.sign(@CAkeys.privateKey, Forge.md.sha256.create()) certPem = pki.certificateToPem(certServer) keyPrivatePem = pki.privateKeyToPem(keysServer.privateKey) keyPublicPem = pki.publicKeyToPem(keysServer.publicKey) dest = mainHost.replace(asterisksRe, "_") Promise.all([ fs.writeFileAsync(path.join(@certsFolder, dest + ".pem"), certPem) fs.writeFileAsync(path.join(@keysFolder, dest + ".key"), keyPrivatePem) fs.writeFileAsync(path.join(@keysFolder, dest + ".public.key"), keyPublicPem) ]) .return([certPem, keyPrivatePem]) getCertificateKeysForHostname: (hostname) -> dest = hostname.replace(asterisksRe, "_") Promise.all([ fs.readFileAsync(path.join(@certsFolder, dest + ".pem")) fs.readFileAsync(path.join(@keysFolder, dest + ".key")) ]) getCACertPath: -> path.join(@certsFolder, "ca.pem") @create = (caFolder) -> ca = new CA if not caFolder caFolder = path.join(os.tmpdir(), 'cy-ca') ca.baseCAFolder = caFolder ca.certsFolder = path.join(ca.baseCAFolder, "certs") ca.keysFolder = path.join(ca.baseCAFolder, "keys") Promise.all([ fs.ensureDirAsync(ca.baseCAFolder) fs.ensureDirAsync(ca.certsFolder) fs.ensureDirAsync(ca.keysFolder) ]) .then -> fs.statAsync(path.join(ca.certsFolder, "ca.pem")) .bind(ca) .then(ca.loadCA) .catch(ca.generateCA) .return(ca) module.exports = CA