From a722307a4ec5c578008a807326327595bbd1a3be Mon Sep 17 00:00:00 2001 From: zhengbaoyang Date: Thu, 30 Jun 2016 18:59:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=94=AF=E6=8C=81https?= =?UTF-8?q?=E6=8A=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache.go | 48 ++++++ config.go | 53 +++++++ dump.go | 71 +++++++++ genKey.go | 311 +++++++++++++++++++++++++++++++++++++ gomitmproxy-ca-cert.pem | 19 +++ gomitmproxy-ca-pk.pem | 27 ++++ gomitmproxy.go | 330 +++++----------------------------------- listener.go | 28 ++++ mitm.go | 245 +++++++++++++++++++++++++++++ clcolor.go => tcolor.go | 0 10 files changed, 844 insertions(+), 288 deletions(-) create mode 100644 cache.go create mode 100644 config.go create mode 100644 dump.go create mode 100644 genKey.go create mode 100644 gomitmproxy-ca-cert.pem create mode 100644 gomitmproxy-ca-pk.pem create mode 100644 listener.go create mode 100644 mitm.go rename clcolor.go => tcolor.go (100%) diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..0a1c08e --- /dev/null +++ b/cache.go @@ -0,0 +1,48 @@ +// package cache implements a really primitive cache that associates expiring +// values with string keys. This cache never clears itself out. +package main + +import ( + "sync" + "time" +) + +// Cache is a cache for binary data +type Cache struct { + entries map[string]*entry + mutex sync.RWMutex +} + +// entry is an entry in a Cache +type entry struct { + data interface{} + expiration time.Time +} + +// NewCache creates a new Cache +func NewCache() *Cache { + return &Cache{entries: make(map[string]*entry)} +} + +// Get returns the currently cached value for the given key, as long as it +// hasn't expired. If the key was never set, or has expired, found will be +// false. +func (cache *Cache) Get(key string) (val interface{}, found bool) { + cache.mutex.RLock() + defer cache.mutex.RUnlock() + entry := cache.entries[key] + if entry == nil { + return nil, false + } else if entry.expiration.Before(time.Now()) { + return nil, false + } else { + return entry.data, true + } +} + +// Set sets a value in the cache with an expiration of now + ttl. +func (cache *Cache) Set(key string, data interface{}, ttl time.Duration) { + cache.mutex.Lock() + defer cache.mutex.Unlock() + cache.entries[key] = &entry{data, time.Now().Add(ttl)} +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..6198be5 --- /dev/null +++ b/config.go @@ -0,0 +1,53 @@ +package main + +import ( + "crypto/tls" + "time" +) + +const ( + ONE_DAY = 24 * time.Hour + TWO_WEEKS = ONE_DAY * 14 + ONE_MONTH = 1 + ONE_YEAR = 1 +) + +type Cfg struct { + Port *string + Raddr *string + Log *string + Monitor *bool + Tls *bool +} + +type TlsConfig struct { + PrivateKeyFile string + CertFile string + Organization string + CommonName string + ServerTLSConfig *tls.Config +} + +func NewTlsConfig(pk, cert, org, cn string) *TlsConfig { + return &TlsConfig{ + PrivateKeyFile: pk, + CertFile: cert, + Organization: org, + CommonName: cn, + ServerTLSConfig: &tls.Config{ + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_RSA_WITH_RC4_128_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, + PreferServerCipherSuites: true, + }, + } +} diff --git a/dump.go b/dump.go new file mode 100644 index 0000000..d1a6290 --- /dev/null +++ b/dump.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "bytes" + "compress/flate" + "compress/gzip" + "fmt" + "io/ioutil" + "math" + "net/http" + "strconv" +) + +func httpDump(req *http.Request, resp *http.Response) { + defer resp.Body.Close() + var respStatusStr string + respStatus := resp.StatusCode + respStatusHeader := int(math.Floor(float64(respStatus / 100))) + switch respStatusHeader { + case 2: + respStatusStr = Green("<--" + strconv.Itoa(respStatus)) + case 3: + respStatusStr = Yellow("<--" + strconv.Itoa(respStatus)) + case 4: + respStatusStr = Magenta("<--" + strconv.Itoa(respStatus)) + case 5: + respStatusStr = Red("<--" + strconv.Itoa(respStatus)) + } + fmt.Println(Green("Request:")) + fmt.Printf("%s %s %s\n", Blue(req.Method), req.RequestURI, respStatusStr) + for headerName, headerContext := range req.Header { + fmt.Printf("%s: %s\n", Blue(headerName), headerContext) + } + fmt.Println(Green("Response:")) + for headerName, headerContext := range resp.Header { + fmt.Printf("%s: %s\n", Blue(headerName), headerContext) + } + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + logger.Println("func httpDump read resp body err:", err) + } else { + acceptEncode := resp.Header["Content-Encoding"] + var respBodyBin bytes.Buffer + w := bufio.NewWriter(&respBodyBin) + w.Write(respBody) + w.Flush() + for _, compress := range acceptEncode { + switch compress { + case "gzip": + r, err := gzip.NewReader(&respBodyBin) + if err != nil { + logger.Println("gzip reader err:", err) + } else { + defer r.Close() + respBody, _ = ioutil.ReadAll(r) + } + break + case "deflate": + r := flate.NewReader(&respBodyBin) + defer r.Close() + respBody, _ = ioutil.ReadAll(r) + break + } + } + fmt.Printf("%s\n", string(respBody)) + } + + fmt.Printf("%s%s%s\n", Black("####################"), Cyan("END"), Black("####################")) +} diff --git a/genKey.go b/genKey.go new file mode 100644 index 0000000..2f961f5 --- /dev/null +++ b/genKey.go @@ -0,0 +1,311 @@ +// Package keyman provides convenience APIs around Go's built-in crypto APIs. +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "time" + + "listome.com/log" +) + +const ( + PEM_HEADER_PRIVATE_KEY = "RSA PRIVATE KEY" + PEM_HEADER_PUBLIC_KEY = "RSA PRIVATE KEY" + PEM_HEADER_CERTIFICATE = "CERTIFICATE" +) + +var ( + tenYearsFromToday = time.Now().AddDate(10, 0, 0) +) + +// PrivateKey is a convenience wrapper for rsa.PrivateKey +type PrivateKey struct { + rsaKey *rsa.PrivateKey +} + +// Certificate is a convenience wrapper for x509.Certificate +type Certificate struct { + cert *x509.Certificate + derBytes []byte +} + +/******************************************************************************* + * Private Key Functions + ******************************************************************************/ + +// GeneratePK generates a PrivateKey with a specified size in bits. +func GeneratePK(bits int) (key *PrivateKey, err error) { + var rsaKey *rsa.PrivateKey + rsaKey, err = rsa.GenerateKey(rand.Reader, bits) + if err == nil { + key = &PrivateKey{rsaKey: rsaKey} + } + return +} + +// LoadPKFromFile loads a PEM-encoded PrivateKey from a file +func LoadPKFromFile(filename string) (key *PrivateKey, err error) { + privateKeyData, err := ioutil.ReadFile(filename) + if err != nil { + if os.IsNotExist(err) { + return nil, err + } + return nil, fmt.Errorf("Unable to read private key file from file %s: %s", filename, err) + } + block, _ := pem.Decode(privateKeyData) + if block == nil { + return nil, fmt.Errorf("Unable to decode PEM encoded private key data: %s", err) + } + rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("Unable to decode X509 private key data: %s", err) + } + return &PrivateKey{rsaKey: rsaKey}, nil +} + +// PEMEncoded encodes the PrivateKey in PEM +func (key *PrivateKey) PEMEncoded() (pemBytes []byte) { + return pem.EncodeToMemory(key.pemBlock()) +} + +// WriteToFile writes the PEM-encoded PrivateKey to the given file +func (key *PrivateKey) WriteToFile(filename string) (err error) { + keyOut, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("Failed to open %s for writing: %s", filename, err) + } + if err := pem.Encode(keyOut, key.pemBlock()); err != nil { + return fmt.Errorf("Unable to PEM encode private key: %s", err) + } + if err := keyOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + return +} + +func (key *PrivateKey) pemBlock() *pem.Block { + return &pem.Block{Type: PEM_HEADER_PRIVATE_KEY, Bytes: x509.MarshalPKCS1PrivateKey(key.rsaKey)} +} + +/******************************************************************************* + * Certificate Functions + ******************************************************************************/ + +/* +Certificate() generates a certificate for the Public Key of the given PrivateKey +based on the given template and signed by the given issuer. If issuer is nil, +the generated certificate is self-signed. +*/ +func (key *PrivateKey) Certificate(template *x509.Certificate, issuer *Certificate) (*Certificate, error) { + return key.CertificateForKey(template, issuer, &key.rsaKey.PublicKey) +} + +/* +CertificateForKey() generates a certificate for the given Public Key based on +the given template and signed by the given issuer. If issuer is nil, the +generated certificate is self-signed. +*/ +func (key *PrivateKey) CertificateForKey(template *x509.Certificate, issuer *Certificate, publicKey interface{}) (*Certificate, error) { + var issuerCert *x509.Certificate + if issuer == nil { + // Note - for self-signed certificates, we include the host's external IP address + issuerCert = template + } else { + issuerCert = issuer.cert + } + derBytes, err := x509.CreateCertificate( + rand.Reader, // secure entropy + template, // the template for the new cert + issuerCert, // cert that's signing this cert + publicKey, // public key + key.rsaKey, // private key + ) + if err != nil { + return nil, err + } + return bytesToCert(derBytes) +} + +// TLSCertificateFor generates a certificate useful for TLS use based on the +// given parameters. These certs are usable for key encipherment and digital +// signatures. +// +// organization: the org name for the cert. +// name: used as the common name for the cert. If name is an IP +// address, it is also added as an IP SAN. +// validUntil: time at which certificate expires +// isCA: whether or not this cert is a CA +// issuer: the certificate which is issuing the new cert. If nil, the +// new cert will be a self-signed CA certificate. +// +func (key *PrivateKey) TLSCertificateFor( + organization string, + name string, + validUntil time.Time, + isCA bool, + issuer *Certificate) (cert *Certificate, err error) { + + template := &x509.Certificate{ + SerialNumber: new(big.Int).SetInt64(int64(time.Now().UnixNano())), + Subject: pkix.Name{ + Organization: []string{organization}, + CommonName: name, + }, + NotBefore: time.Now().AddDate(0, -1, 0), + NotAfter: validUntil, + + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + } + + // If name is an ip address, add it as an IP SAN + ip := net.ParseIP(name) + if ip != nil { + template.IPAddresses = []net.IP{ip} + } + + isSelfSigned := issuer == nil + if isSelfSigned { + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} + } + + // If it's a CA, add certificate signing + if isCA { + template.KeyUsage = template.KeyUsage | x509.KeyUsageCertSign + template.IsCA = true + } + + cert, err = key.Certificate(template, issuer) + return +} + +// LoadCertificateFromFile loads a Certificate from a PEM-encoded file +func LoadCertificateFromFile(filename string) (*Certificate, error) { + certificateData, err := ioutil.ReadFile(filename) + if err != nil { + if os.IsNotExist(err) { + return nil, err + } + return nil, fmt.Errorf("Unable to read certificate file from disk: %s", err) + } + return LoadCertificateFromPEMBytes(certificateData) +} + +// LoadCertificateFromPEMBytes loads a Certificate from a byte array in PEM +// format +func LoadCertificateFromPEMBytes(pemBytes []byte) (*Certificate, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, fmt.Errorf("Unable to decode PEM encoded certificate") + } + return bytesToCert(block.Bytes) +} + +// LoadCertificateFromX509 loads a Certificate from an x509.Certificate +func LoadCertificateFromX509(cert *x509.Certificate) (*Certificate, error) { + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Headers: nil, + Bytes: cert.Raw, + }) + return LoadCertificateFromPEMBytes(pemBytes) +} + +// X509 returns the x509 certificate underlying this Certificate +func (cert *Certificate) X509() *x509.Certificate { + return cert.cert +} + +// PEMEncoded encodes the Certificate in PEM +func (cert *Certificate) PEMEncoded() (pemBytes []byte) { + return pem.EncodeToMemory(cert.pemBlock()) +} + +// WriteToFile writes the PEM-encoded Certificate to a file. +func (cert *Certificate) WriteToFile(filename string) (err error) { + certOut, err := os.Create(filename) + if err != nil { + return fmt.Errorf("Failed to open %s for writing: %s", filename, err) + } + defer func() { + if err := certOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + }() + return pem.Encode(certOut, cert.pemBlock()) +} + +func (cert *Certificate) WriteToTempFile() (name string, err error) { + // Create a temp file containing the certificate + tempFile, err := ioutil.TempFile("", "tempCert") + if err != nil { + return "", fmt.Errorf("Unable to create temp file: %s", err) + } + name = tempFile.Name() + err = cert.WriteToFile(name) + if err != nil { + return "", fmt.Errorf("Unable to save certificate to temp file: %s", err) + } + return +} + +// WriteToDERFile writes the DER-encoded Certificate to a file. +func (cert *Certificate) WriteToDERFile(filename string) (err error) { + certOut, err := os.Create(filename) + if err != nil { + return fmt.Errorf("Failed to open %s for writing: %s", filename, err) + } + defer func() { + if err := certOut.Close(); err != nil { + log.Debugf("Unable to close file: %v", err) + } + }() + _, err = certOut.Write(cert.derBytes) + return err +} + +// PoolContainingCert creates a pool containing this cert. +func (cert *Certificate) PoolContainingCert() *x509.CertPool { + pool := x509.NewCertPool() + pool.AddCert(cert.cert) + return pool +} + +// PoolContainingCerts constructs a CertPool containing all of the given certs +// (PEM encoded). +func PoolContainingCerts(certs ...string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + for _, cert := range certs { + c, err := LoadCertificateFromPEMBytes([]byte(cert)) + if err != nil { + return nil, err + } + pool.AddCert(c.cert) + } + return pool, nil +} + +func (cert *Certificate) ExpiresBefore(time time.Time) bool { + return cert.cert.NotAfter.Before(time) +} + +func bytesToCert(derBytes []byte) (*Certificate, error) { + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + return nil, err + } + return &Certificate{cert, derBytes}, nil +} + +func (cert *Certificate) pemBlock() *pem.Block { + return &pem.Block{Type: PEM_HEADER_CERTIFICATE, Bytes: cert.derBytes} +} diff --git a/gomitmproxy-ca-cert.pem b/gomitmproxy-ca-cert.pem new file mode 100644 index 0000000..714be03 --- /dev/null +++ b/gomitmproxy-ca-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIIFFyG4pw+r+kwDQYJKoZIhvcNAQELBQAwLzEXMBUGA1UE +ChMOZ29taXRtcHJveHkxLjAxFDASBgNVBAMTC2dvbWl0bXByb3h5MB4XDTE2MDUy +OTEwMjQ0NloXDTE3MDYyOTEwMjQ0NlowLzEXMBUGA1UEChMOZ29taXRtcHJveHkx +LjAxFDASBgNVBAMTC2dvbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA5i5e0Ioniij/zx4uP+pEwIfCPuqRHpmtxnylXtDJNS7Mibsl57cC +pKDymhGtehXKzt/7lv7Zh4PQyHUAPJQqTp6Q/bQRcnCEKoY0jSCDy7ZYxz+6YMth +JD9CG67jCio78hQDyYQc8RpUJ8FyH+HWE8xyPkixnGC9tZSG6bAtP2ORtn9AsKXX +8Hj9zWKrQqWFMbQvOp8SGVkqNsO/ZvIO3Rf5pBPlPTUTsjXMFFN2PipoMgZnF43C +eXt3de+PxFfs+nN2IIPbLTmRHW8qrworkJ0TBJWYMiT6mFOEiqlF92pzdLNDyR3e +xednS3We07jZBAbvQGmy1eenZORXZsDuRQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBAH1Gj0AtwzwbM0+NPrGsv2B0z/BLLDHu9MIN +EhVIPgcGj9E9e3uP2uzxBBfFIeIk9w2GyuzqHqq/NTOJSf3CpL0RJYI9gS8qMg4x +wn5wkSPVb8cp36jS3rNvpNx6nuz+P+H+t1x4+KWk12GM2gz7g/YSmkXOtntQzpOW +Gl9ydNYWbd0ad5k+kQhBp2nsq1NByg3fwEd1jgvSnxKB+P76OK01pSxRczLpCp0F ++1gpD6ukiPIC1RZCUiHk4UeY8jmpBNgNW4OJmxgA+LFclB3hytkxN2qbnk5xfA33 +1JciXh8y0ZD1wXB7y14PAB1TB53Rgjvk38OrPpBf3nETxlqP5zQ= +-----END CERTIFICATE----- diff --git a/gomitmproxy-ca-pk.pem b/gomitmproxy-ca-pk.pem new file mode 100644 index 0000000..5d49d37 --- /dev/null +++ b/gomitmproxy-ca-pk.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA5i5e0Ioniij/zx4uP+pEwIfCPuqRHpmtxnylXtDJNS7Mibsl +57cCpKDymhGtehXKzt/7lv7Zh4PQyHUAPJQqTp6Q/bQRcnCEKoY0jSCDy7ZYxz+6 +YMthJD9CG67jCio78hQDyYQc8RpUJ8FyH+HWE8xyPkixnGC9tZSG6bAtP2ORtn9A +sKXX8Hj9zWKrQqWFMbQvOp8SGVkqNsO/ZvIO3Rf5pBPlPTUTsjXMFFN2PipoMgZn +F43CeXt3de+PxFfs+nN2IIPbLTmRHW8qrworkJ0TBJWYMiT6mFOEiqlF92pzdLND +yR3exednS3We07jZBAbvQGmy1eenZORXZsDuRQIDAQABAoIBAQC1pxKccOsTVZfL +1H2bYz4u0vSwwl0UUSOa6PN5CDxCBFTWvSME4qDrWzkZ7amCF4CeEpVybF0vXQ9/ +oxY0uJlKwkiab2j1b2ZeSNtgJLDeK2GUnO5xRQ+8k0+QmUwD0aDNQo5akjBk9epE +FZ4DGy7fIRFnpebFCnOswTJXafnLc0FeNpBgWaGgmc+PI2yZy6ed8nB8CVvhnEQu +EbIuw9JEwAYbvqXJ9E0gkwUTkkaMPIM+lQ+uY94btWtTCMpl2HY3uufE1Zhfoivr +hsATWsPGOSCaKEK82PlpXZb5dK0agRx688Z5xavXfjqUoqDvXsgHOCvIdwh22jgV +H7tSsQUBAoGBAOyiYSQkyWdniHrIV0Q75npbJbJXQ9uMQGVN370QsBaWJN0XZrOa +aDOmzpGfEIBpY6zaHwZl/IAc+ffC0s4DN9k7jmGOuOv0BmHHf88XyXzKKMpBrR1F +ofPDN8k6D2VL9QG3/B25hLpzeLt3aqOIqF2Aykkb2b5MybJ7iojKHQ95AoGBAPkE +yyk5F3hdPv5xA1h31h7yJWcoDKbBuLc8po1StCo1CkMZYjpD6d2Gl2QVFOXDBXUU +3PjZ3EZc4mZGjeaMKzk+bxTO8d96KtoNp92ox8t6j35mcWKgB7AkJopMJecWstNx +y4jUf2pEDEzHgRMKwkecGnYRYg6FssVHUUBxk2YtAoGBAMw8SgNcDMjGvzDaARfP +a1lnmd2XG+Y94saMs/MGmDSmWiLIs8eIGS+bZOailyd9zp29lNF87LXWTJje+fr9 +JBLGKFljSY+9ClUSTul66lheU15d2QBOvT0a3Oc8yVTwfgKBvYQaPsh+KVID+3Ix +3L73DaCT/RcWR29Y2XS+nN5hAoGAUJ0cp9uznWOSYQx06M7iomIBecOdVN6hza/0 +uwtrB77j85TkF7iknPJVUDEC8t6PhbebUXQ1uxGWuUVd6qS12XI8w5+67X2/IZgK +H9fj1ht3GJRRYHwuAPezAlJkcssGWS0dls4z8VLWKITtZBy3iDcg2dgGxmzB0tuj +khkyfoECgYALp8DFdyYhqKIRSOu+FhIvkYo+x2qjoetTBlArdHzfCzzLk1908rel +XUQ2Sh5Y3DseJK0oXA5CQqu3qu2LCX3Djmj9w9oxvGqDBd0gTas088kTTOcsWurG +CcTadlQWS/CeCDQx2E6sfvu920G6/BhBn0a6Jtzs+HGA13zBFuBcRA== +-----END RSA PRIVATE KEY----- diff --git a/gomitmproxy.go b/gomitmproxy.go index 5af60e7..a20166a 100644 --- a/gomitmproxy.go +++ b/gomitmproxy.go @@ -1,330 +1,84 @@ -/* -Author:shepbao -Time:2016-06-01 -*/ +// This example shows a proxy server that uses go-mitm to man-in-the-middle +// HTTPS connections opened with CONNECT requests package main import ( - "bufio" - "bytes" - "compress/flate" - "compress/gzip" - "encoding/json" - "errors" "flag" - "fmt" - "io" - "io/ioutil" - "math" - "net" + "log" "net/http" - "net/http/httputil" - "net/url" "os" - "regexp" - "strconv" + "sync" "time" - - "log" ) const ( - MaxOutstanding = 10 + Version = "1.1" ) -var Version string = "1.0" -var sem = make(chan int, MaxOutstanding) - -type AccessStatistics struct { - AllCount int64 - HostCount map[string]int64 -} - -var transport = &http.Transport{ - ResponseHeaderTimeout: 30 * time.Second, -} - -//config json -type Config struct { - Port string `json :"port"` //"8080" - Raddr string `json:"raddr,omitempty"` //"localhost:8888" -} - -var port = flag.String("port", "8080", "Listen port") -var raddr = flag.String("raddr", "", "Remote addr") -var monitor = flag.Bool("m", false, "monitor mode") - -var cfg = flag.String("conf", "./config", "config file") -var cf *Config +var ( + wg sync.WaitGroup +) var logFile *os.File var logger *log.Logger func main() { - var err error - logFile, err = os.Create("error.log") - if err != nil { - log.Fatalln("fail to create log file!") - } + var conf Cfg - logger = log.New(logFile, "[gomitmproxy]", log.LstdFlags|log.Llongfile) + conf.Port = flag.String("port", "8080", "Listen port") + conf.Raddr = flag.String("raddr", "", "Remote addr") + conf.Log = flag.String("log", "./error.log", "log file path") + conf.Monitor = flag.Bool("m", false, "monitor mode") + conf.Tls = flag.Bool("tls", false, "tls connect") flag.Parse() - cf, err = ParseConfig(*cfg) - if err != nil { - logger.Println("ParseConfig err:", err) - } else { - if len(cf.Port) == 0 { - log.Fatal("Miss Port") - } - *port = cf.Port - - if len(cf.Raddr) == 0 { - log.Fatal("Miss Raddr") - } - *raddr = cf.Raddr - } - - log.Println("Listen port: ", *port) - if len(*raddr) != 0 { - log.Println("Connect to Raddr: ", *raddr) - } - - ln, err := net.Listen("tcp", ":"+*port) + var err error + logFile, err = os.Create(*conf.Log) if err != nil { - logger.Println("listen err:", err) + log.Fatalln("fail to create log file!") } - defer ln.Close() - for { - conn, err := ln.Accept() - if err != nil { - logger.Println("Accept err:", err) - continue - } - go handleConn(conn) + logger = log.New(logFile, "[gomitmproxy]", log.LstdFlags|log.Llongfile) - } + wg.Add(1) + gomitmproxy(&conf) + wg.Wait() } -//处理连接 -func handleConn(conn net.Conn) { - reader := bufio.NewReader(conn) - req, err := http.ReadRequest(reader) - if err != nil { - logger.Println("readRequest err:", err) - conn.Close() - return - } +func gomitmproxy(conf *Cfg) { + tlsConfig := NewTlsConfig("gomitmproxy-ca-pk.pem", "gomitmproxy-ca-cert.pem", "", "") - host := req.Host - matched, _ := regexp.MatchString(":[0-9]+$", host) - if !matched { - host += ":80" - } - conn_proxy, err := Connect(host) + handler, err := Mitm(conf, tlsConfig) if err != nil { - return + logger.Fatalf("Unable to start http proxy: %s", err) } - defer conn_proxy.Close() - if req.Method == "CONNECT" { - b := []byte("HTTP/1.1 200 Connection Established\r\n" + - "Proxy-Agent: golang_proxy/" + Version + "\r\n\r\n") - _, err := conn.Write(b) - if err != nil { - logger.Println("Write Connect err:", err) - return - } - } else { - req.Header.Del("Proxy-Connection") - req.Header.Set("Connection", "Keep-Alive") - if err = req.Write(conn_proxy); err != nil { - logger.Println("send to server err", err) - return - } + server := &http.Server{ + Addr: ":" + *conf.Port, + Handler: handler, + ReadTimeout: 1 * time.Hour, + WriteTimeout: 1 * time.Hour, + TLSConfig: tlsConfig.ServerTLSConfig, } - if *monitor && req.Method != "CONNECT" { + go func() { + log.Printf("proxy listening port:%s", *conf.Port) - resp, err := http.ReadResponse(bufio.NewReader(conn_proxy), req) - if err != nil { - logger.Println("read response err:", err) - return + if *conf.Tls { + err = server.ListenAndServeTLS("gomitmproxy-ca-cert.pem", "gomitmproxy-ca-pk.pem") + } else { + log.Println("ListenAndServe") + err = server.ListenAndServe() } - - respDump, err := httputil.DumpResponse(resp, true) if err != nil { - logger.Println("respDump err:", err) + logger.Fatalf("Unable to start HTTP proxy: %s", err) } - _, err = conn.Write(respDump) - if err != nil { - logger.Println("conn write err:", err) - } - // reqDump, _ := httputil.DumpRequest(req, false) - // log.Println(string(reqDump), string(respDump)) - go httpDump(req, resp) + wg.Done() - } else { - err = Transport(conn, conn_proxy) - if err != nil { - logger.Println("Transport err:", err) - } - } -} - -//连接真实的主机 -func Connect(host string) (net.Conn, error) { - if len(*raddr) == 0 { - conn, err := net.DialTimeout("tcp", host, time.Second*30) - if err != nil { - // logger.Println("connect", host, "err", err) - return nil, err - } - return conn, nil - } - - conn_proxy, err := net.DialTimeout("tcp", *raddr, time.Second*30) - if err != nil { - logger.Println("connect proxy err", err) - return nil, err - } - err = connectProxyServer(conn_proxy, host) - if err != nil { - logger.Println("connectServer err:", err) - return nil, err - } - return conn_proxy, nil -} - -//连接代理服务器 -func connectProxyServer(conn net.Conn, addr string) error { - - req := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Host: addr}, - Host: addr, - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - } - req.Header.Set("Proxy-Connection", "keep-alive") - - if err := req.Write(conn); err != nil { - return err - } - - resp, err := http.ReadResponse(bufio.NewReader(conn), req) - if err != nil { - return err - } - if resp.StatusCode != http.StatusOK { - return errors.New(resp.Status) - } - return nil -} - -//两个io口的连接 -func Transport(conn1, conn2 net.Conn) (err error) { - rChan := make(chan error, 1) - wChan := make(chan error, 1) - - go MyCopy(conn1, conn2, wChan) - go MyCopy(conn2, conn1, rChan) - - select { - case err = <-wChan: - case err = <-rChan: - } + log.Printf("gomitmproxy stop!!!!") + }() return } - -func MyCopy(src io.Reader, dst io.Writer, ch chan<- error) { - _, err := io.Copy(dst, src) - ch <- err -} - -//配置文件解析 -func ParseConfig(cfg string) (*Config, error) { - var conf Config - configContext, err := ioutil.ReadFile(cfg) - if err != nil { - return nil, err - } - err = json.Unmarshal(configContext, &conf) - if err != nil { - return nil, err - } - return &conf, nil -} - -//复制http头 -func copyHeader(src, dst http.Header) { - for k, vv := range src { - for _, v := range vv { - dst.Add(k, v) - } - } -} - -//打印http请求和响应 -func httpDump(req *http.Request, resp *http.Response) { - defer resp.Body.Close() - var respStatusStr string - respStatus := resp.StatusCode - respStatusHeader := int(math.Floor(float64(respStatus / 100))) - switch respStatusHeader { - case 2: - respStatusStr = Green("<--" + strconv.Itoa(respStatus)) - case 3: - respStatusStr = Yellow("<--" + strconv.Itoa(respStatus)) - case 4: - respStatusStr = Magenta("<--" + strconv.Itoa(respStatus)) - case 5: - respStatusStr = Red("<--" + strconv.Itoa(respStatus)) - } - fmt.Println(Green("Request:")) - fmt.Printf("%s %s %s\n", Blue(req.Method), req.RequestURI, respStatusStr) - for headerName, headerContext := range req.Header { - fmt.Printf("%s: %s\n", Blue(headerName), headerContext) - } - fmt.Println(Green("Response:")) - for headerName, headerContext := range resp.Header { - fmt.Printf("%s: %s\n", Blue(headerName), headerContext) - } - - respBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - logger.Println("read resp body err:", err) - } else { - acceptEncode := resp.Header["Content-Encoding"] - var respBodyBin bytes.Buffer - w := bufio.NewWriter(&respBodyBin) - w.Write(respBody) - w.Flush() - for _, compress := range acceptEncode { - switch compress { - case "gzip": - r, err := gzip.NewReader(&respBodyBin) - if err != nil { - logger.Println("gzip reader err:", err) - } else { - defer r.Close() - respBody, _ = ioutil.ReadAll(r) - } - break - case "deflate": - r := flate.NewReader(&respBodyBin) - defer r.Close() - respBody, _ = ioutil.ReadAll(r) - break - } - } - fmt.Printf("%s\n", string(respBody)) - } - - fmt.Printf("%s%s%s\n", Black("####################"), Cyan("END"), Black("####################")) -} diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..cb852d8 --- /dev/null +++ b/listener.go @@ -0,0 +1,28 @@ +package main + +import ( + "io" + "net" +) + +type mitmListener struct { + conn net.Conn +} + +func (listener *mitmListener) Accept() (net.Conn, error) { + if listener.conn != nil { + conn := listener.conn + listener.conn = nil + return conn, nil + } else { + return nil, io.EOF + } +} + +func (listener *mitmListener) Close() error { + return nil +} + +func (listener *mitmListener) Addr() net.Addr { + return nil +} diff --git a/mitm.go b/mitm.go new file mode 100644 index 0000000..a4efe86 --- /dev/null +++ b/mitm.go @@ -0,0 +1,245 @@ +package main + +import ( + "bufio" + "crypto/tls" + "fmt" + "io" + "log" + "net" + "net/http" + "net/http/httputil" + "regexp" + "strings" + "sync" + "time" +) + +type HandlerWrapper struct { + MyConfig *Cfg + tlsConfig *TlsConfig + wrapped http.Handler + pk *PrivateKey + pkPem []byte + issuingCert *Certificate + issuingCertPem []byte + serverTLSConfig *tls.Config + dynamicCerts *Cache + certMutex sync.Mutex + https bool +} + +func (hw *HandlerWrapper) GenerateCertForClient() (err error) { + if hw.tlsConfig.Organization == "" { + hw.tlsConfig.Organization = "gomitmproxy" + Version + } + if hw.tlsConfig.CommonName == "" { + hw.tlsConfig.CommonName = "gomitmproxy" + } + if hw.pk, err = LoadPKFromFile(hw.tlsConfig.PrivateKeyFile); err != nil { + hw.pk, err = GeneratePK(2048) + if err != nil { + return fmt.Errorf("Unable to generate private key: %s", err) + } + hw.pk.WriteToFile(hw.tlsConfig.PrivateKeyFile) + } + hw.pkPem = hw.pk.PEMEncoded() + hw.issuingCert, err = LoadCertificateFromFile(hw.tlsConfig.CertFile) + if err != nil || hw.issuingCert.ExpiresBefore(time.Now().AddDate(0, ONE_MONTH, 0)) { + hw.issuingCert, err = hw.pk.TLSCertificateFor( + hw.tlsConfig.Organization, + hw.tlsConfig.CommonName, + time.Now().AddDate(ONE_YEAR, 0, 0), + true, + nil) + if err != nil { + return fmt.Errorf("Unable to generate self-signed issuing certificate: %s", err) + } + hw.issuingCert.WriteToFile(hw.tlsConfig.CertFile) + } + hw.issuingCertPem = hw.issuingCert.PEMEncoded() + return +} + +func (hw *HandlerWrapper) FakeCertForName(name string) (cert *tls.Certificate, err error) { + kpCandidateIf, found := hw.dynamicCerts.Get(name) + if found { + return kpCandidateIf.(*tls.Certificate), nil + } + + hw.certMutex.Lock() + defer hw.certMutex.Unlock() + kpCandidateIf, found = hw.dynamicCerts.Get(name) + if found { + return kpCandidateIf.(*tls.Certificate), nil + } + + //create certificate + certTTL := TWO_WEEKS + generatedCert, err := hw.pk.TLSCertificateFor( + hw.tlsConfig.Organization, + name, + time.Now().Add(certTTL), + false, + hw.issuingCert) + if err != nil { + return nil, fmt.Errorf("Unable to issue certificate: %s", err) + } + keyPair, err := tls.X509KeyPair(generatedCert.PEMEncoded(), hw.pkPem) + if err != nil { + return nil, fmt.Errorf("Unable to parse keypair for tls: %s", err) + } + + cacheTTL := certTTL - ONE_DAY + hw.dynamicCerts.Set(name, &keyPair, cacheTTL) + return &keyPair, nil +} + +func (hw *HandlerWrapper) DumpHTTPAndHTTPs(resp http.ResponseWriter, req *http.Request) { + req.Header.Del("Proxy-Connection") + req.Header.Set("Connection", "Keep-Alive") + + // handle connection + connIn, _, err := resp.(http.Hijacker).Hijack() + if err != nil { + logger.Println("hijack error:", err) + } + defer connIn.Close() + + var respOut *http.Response + host := req.Host + + matched, _ := regexp.MatchString(":[0-9]+$", host) + + if !hw.https { + if !matched { + host += ":80" + } + + connOut, err := net.DialTimeout("tcp", host, time.Second*30) + if err != nil { + logger.Println("dial to", host, "error:", err) + } + respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) + if err != nil && err != io.EOF { + logger.Println("read response error:", err) + } + + } else { + if !matched { + host += ":443" + } + + connOut, err := tls.Dial("tcp", host, hw.tlsConfig.ServerTLSConfig) + + if err != nil { + logger.Panicln("tls dial to", host, "error:", err) + } + if err = req.Write(connOut); err != nil { + logger.Println("send to server error", err) + } + + respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) + if err != nil && err != io.EOF { + logger.Println("read response error:", err) + } + + } + + if respOut == nil { + log.Println("respOut is nil") + return + } + + respDump, err := httputil.DumpResponse(respOut, true) + if err != nil { + logger.Println("respDump error:", err) + } + + _, err = connIn.Write(respDump) + if err != nil { + logger.Println("connIn write error:", err) + } + + if *hw.MyConfig.Monitor { + go httpDump(req, respOut) + } + +} + +func (hw *HandlerWrapper) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + if req.Method == "CONNECT" { + hw.https = true + hw.InterceptHTTPs(resp, req) + } else { + hw.DumpHTTPAndHTTPs(resp, req) + } +} + +func (hw *HandlerWrapper) InterceptHTTPs(resp http.ResponseWriter, req *http.Request) { + addr := req.Host + host := strings.Split(addr, ":")[0] + + cert, err := hw.FakeCertForName(host) + if err != nil { + msg := fmt.Sprintf("Could not get mitm cert for name: %s\nerror: %s", host, err) + respBadGateway(resp, msg) + return + } + + // handle connection + connIn, _, err := resp.(http.Hijacker).Hijack() + if err != nil { + msg := fmt.Sprintf("Unable to access underlying connection from client: %s", err) + respBadGateway(resp, msg) + return + } + tlsConfig := copyTlsConfig(hw.tlsConfig.ServerTLSConfig) + tlsConfig.Certificates = []tls.Certificate{*cert} + tlsConnIn := tls.Server(connIn, tlsConfig) + + listener := &mitmListener{tlsConnIn} + + handler := http.HandlerFunc(func(resp2 http.ResponseWriter, req2 *http.Request) { + req2.URL.Scheme = "https" + req2.URL.Host = req2.Host + hw.DumpHTTPAndHTTPs(resp2, req2) + + }) + + go func() { + err = http.Serve(listener, handler) + if err != nil && err != io.EOF { + log.Printf("Error serving mitm'ed connection: %s", err) + } + }() + + connIn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) +} + +func Mitm(conf *Cfg, tlsConfig *TlsConfig) (*HandlerWrapper, error) { + hw := &HandlerWrapper{ + MyConfig: conf, + tlsConfig: tlsConfig, + dynamicCerts: NewCache(), + } + err := hw.GenerateCertForClient() + if err != nil { + return nil, err + } + return hw, nil +} + +func copyTlsConfig(template *tls.Config) *tls.Config { + tlsConfig := &tls.Config{} + if template != nil { + *tlsConfig = *template + } + return tlsConfig +} + +func respBadGateway(resp http.ResponseWriter, msg string) { + log.Println(msg) + resp.WriteHeader(502) + resp.Write([]byte(msg)) +} diff --git a/clcolor.go b/tcolor.go similarity index 100% rename from clcolor.go rename to tcolor.go