Files
cypress/packages/https-proxy/lib/ca.coffee
Zach Bloomquist c1a345dce2 Improved proxy support (#3531)
* https-proxy: unused file

* server: wrap all https requests that use a proxy

* server: use request lib in ensureUrl if proxy is in use. this makes runs tab work behind a proxy

* electron: pass --proxy-server to app itself, so the embedded github login page works

* cli: first attempt at env vars from windows registry

* cli: api cleanup

* cli: lint

* cli: fix crash on no proxy, add tests

* add desktop-gui watch to terminals.json

* cli: pass along --proxy-source

* electron: pass --proxy-bypass-list too

* server: whitelist proxy* args

* cli: better wording

* desktop-gui: display proxy settings

* extension: force proxy [wip]

* extension: finally, i am victorious over coffeescript

* extension: add -loopback to bypasslist

* extension: revert changes

Revert "extension: force proxy [wip]"

This reverts commit 3ab6ba42a763f25ee65f12eb8b79eb597efc9b11.

* desktop-gui: skip proxysettings if there aren't any

* https-proxy, server: proxy directConnections using https-proxy-agent

* https-agent: pool httpsAgents

* https-proxy: work when they're not on a proxy

* https-proxy: ci - use agent 1.0

* https-proxy: tests

* desktop-gui: hide proxy settings when not using proxy

* https-proxy: pass req through to https-proxy-agent callback

* cli: use get-windows-proxy

* desktop-gui: always show proxy settings

* server: use get-windows-proxy

* electron, server: supply electron proxy config when window launched

* server: fix

* https-proxy: cleanup

* server: clean up ensureUrl

* https-proxy: cleanup

* cli: fix

* cli: fix destructuring

* server: enable ForeverAgent to pool HTTPS/HTTP connections

#3192

* server: updating snapshot

* https-proxy: don't crash, do error if proxy unreachable

* https-proxy:

* get-windows-proxy@1.0.0

* https-proxy: use proxy-from-env to decide on a proxy for a url

* server: fallback to HTTP_PROXY globally if HTTPS_PROXY not set

* server: proxy args test

* cli: add proxy tests

* cli: add test that loadSystemProxySettings is called during download

* cli, server: account for the fact that CI has some proxy vars set

* https-proxy: ""

* cli, https-proxy, server: ""

* desktop-gui: update settings gui

* desktop-gui: cypress tests for proxy settings

* server: strict undefined check

* cli, server: move get-windows-proxy to scope, optionalDeps

* server, cli: use new and improved get-windows-proxy

* cli, server: 1.5.0

* server: re-check for proxy since cli may have failed to load the lib

* server, cli: 1.5.1

* server: NO_PROXY=localhost by default, clean up

* https-proxy: disable Nagle's on proxy sockets

\#3192

* https-proxy: use setNoDelay on upstream, cache https agent

* https-proxy: test basic auth

* https-proxy: add todo: remove this

* server: add custom HTTP(s) Agent implementation w keepalive, tunneling

* server: typescript for agent

* add ts to zunder

* server: more ts

* ts: add missing Agent type declaration

* server: create CombinedAgent

* server: use agent in more places

* ts: more declarations

* server: make script work even if debug port not supplied

* server: begin some testing

* server, ts: agent, tests

* server: test

* server: agent works with websockets now

* server: update snapshot

* server: work out some more bugs with websockets

* server: more websockets

* server: add net_profiler

* https-proxy: fix dangling socket on direct connection

* server: fix potential 'headers have already been sent'

* https-proxy: nab another dangler

* server: update test to expect agent

* https-proxy: fix failing test

* desktop-gui: change on-link

* server: add tests for empty response case

* server: tests

* server: send keep-alive with requests

* server: make net profiler hook on socket.connect

* server: only hook profiler once

* server: update tests, add keep-alive test

* server: only regen headers if needed

* server: move http_overrides into CombinedAgent, make it proxy-proof

for #112

* server: update snapshot

* server: undo

* server: avoid circular dependency

* https-proxy, server: use our Agent instead of https-proxy-agent

* server: add dependency back

* cli: actually use proxy for download

* server, launcher, ts: typescript

* Revert "server, launcher, ts: typescript"

This reverts commit d3f8b8bbb6.

* Revert "Revert "server, launcher, ts: typescript""

This reverts commit 818dfdfd00.

* ts, server: respond to PR

* server, ts: types

* ts: really fix types

* https-proxy, server: export CA from https-proxy

* agent, server, https-proxy: move agent to own package

* agent => networking, move connect into networking

* fix tests

* fix test

* networking: respond to PR changes, add more unit tests

* rename ctx

* networking, ts: add more tests

* server: add ensureUrl tests

* https-proxy: remove https-proxy-agent

* server: use CombinedAgent for API

* server: updates

* add proxy performance tests

* add perf tests to workflow

* circle

* run perf tests with --no-sandbox

* networking, ts: ch-ch-ch-ch-changes

* server, networking: pr changes

* run networking tests in circle

* server: fix performance test

* https-proxy: test that sockets are being closed

* https-proxy: write, not emit

* networking: fix test

* networking: bubble err in connect

* networking: style

* networking: clean p connect error handling

* networking => network

* server: make perf tests really work

* server: really report

* server: use args from browser

* server: use AI to determine max run time

* server: load electron only when needed


Co-authored-by: Brian Mann <brian@cypress.io>
2019-03-31 23:39:10 -04:00

241 lines
5.9 KiB
CoffeeScript

_ = 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