From da8ebd53f523ff0c8e07f7d2fdfb92fedb8b9e55 Mon Sep 17 00:00:00 2001 From: Charles Date: Mon, 31 Dec 2018 23:02:24 +0800 Subject: [PATCH] add mitm to https --- ca-cert.pem | 19 ++++ ca-pk.pem | 27 +++++ cache.go | 51 +++++++++ config.go | 43 +++++++ keyman.go | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++ listener.go | 28 +++++ main.go | 42 ++----- mitmproxy.go | 25 ++++ mitmserver.go | 290 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 804 insertions(+), 30 deletions(-) create mode 100644 ca-cert.pem create mode 100644 ca-pk.pem create mode 100644 cache.go create mode 100644 config.go create mode 100644 keyman.go create mode 100644 listener.go create mode 100644 mitmproxy.go create mode 100644 mitmserver.go diff --git a/ca-cert.pem b/ca-cert.pem new file mode 100644 index 0000000..e7b7305 --- /dev/null +++ b/ca-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIIFXVvcHwf0pAwDQYJKoZIhvcNAQELBQAwLzEXMBUGA1UE +ChMOZ29taXRtcHJveHkxLjExFDASBgNVBAMTC2dvbWl0bXByb3h5MB4XDTE4MTIw +MTEzNTYxNVoXDTE5MTIzMTEzNTYxNVowLzEXMBUGA1UEChMOZ29taXRtcHJveHkx +LjExFDASBgNVBAMTC2dvbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAvJt45t6GtcMOPtLmpGLHJg4VxA3v5nV/T6adaqgz+pBA5Pbw1MLo +5wWhdda254asqI9laKPk0QgluySw/IyP3dJ3EMB0jmdlXy+BRQobt7ls16/AW+If +fIt8OmUF75b5iPdJq6DvM8xNOVHGyEvWjpCNwZhDFsqRelssH6wO2CndxqHe9R1k +ffKGxhaeG21/5ung1+oZPiP4CccfJoDWtR2SvQhvl3Y5Aaqv824c1UbPnTATBEPK +iYpnQvzAwwi15Z8bfnWW2CvAaQJ0TW8YGHaRTehN7YidAHk1WXUl192sihiDTfGI +dCpXTuICCD3xkvRUJeHdw99Kv4CBG/qRtwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBABUw6XQR1lQSTiRxAuzm9gWo5NKuW7vTSf9Z +hQtb7+OVetj/GqI+AUqA5JpWAngWDOcaJi/bjWbEvasdh98Ho93yNslyKLRbI8Co +rXJCVhC2aQxZ7fQlIKKH5S39yVgLR4/LAl8Yuug9gynCvYCuUfYL1dhlntgGm/m9 +gK4XOucimqnhsoWSDYq4xU80oetTykTlmy8Ms2hJo16eNcvDQGcMVxjsHGOScUl3 +qo+7nylKV/vHj1oCtHKOviUJ3F0JLjaEffOUZb8Gq99jE/Mu0OmS6LWWo0Lhh/mC +syjLfCrIUE4aSdn30jzJKwoqVakjhaZIpF4UF/dfcO0HtzBwy+g= +-----END CERTIFICATE----- diff --git a/ca-pk.pem b/ca-pk.pem new file mode 100644 index 0000000..3f0e2f8 --- /dev/null +++ b/ca-pk.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvJt45t6GtcMOPtLmpGLHJg4VxA3v5nV/T6adaqgz+pBA5Pbw +1MLo5wWhdda254asqI9laKPk0QgluySw/IyP3dJ3EMB0jmdlXy+BRQobt7ls16/A +W+IffIt8OmUF75b5iPdJq6DvM8xNOVHGyEvWjpCNwZhDFsqRelssH6wO2CndxqHe +9R1kffKGxhaeG21/5ung1+oZPiP4CccfJoDWtR2SvQhvl3Y5Aaqv824c1UbPnTAT +BEPKiYpnQvzAwwi15Z8bfnWW2CvAaQJ0TW8YGHaRTehN7YidAHk1WXUl192sihiD +TfGIdCpXTuICCD3xkvRUJeHdw99Kv4CBG/qRtwIDAQABAoIBAAe90e6X/6uCQBlw +CquAqm/zcA1GDSzGcd1RF3EDSm7kcOV1BVmWTnz9DC2MDKHgM0IP8Lek0oWJ8hKG +ZQVYt6cBNoXngGE/+Tc4+fNdgNm/sKoBusGKvIQeLZEwCFhXOBLZI5r51heoMRtD +MgeSpVHguRDTMR3EXshI6by7ETjqHPguZNTvhAKVZPiTBpG3oaTGVUJ4n8LXyXe+ +Q7+G3dW9xdexAF0bJ9B9WRp0Y39ifGd6QxFuk/jNm08z2IZ+8bm707MT3ToiZsrz +StyJrDYSOzwUSlTOWyB7RSWkqwkb9oFiW1OnoVKogRaqEl+N8Kr93FGtCGYXQ8W6 +w/i4JQECgYEA3d/mt9VBOxBhBcln+YECCiBdFux/6TkVxdryLpT1xy3i0dFBJFDM +b0Cm11cEU+/xtd2vBXOD4hV6IUhTZQhgx3niHTz/Ul/JTjRtT256fmn0+ZAwSVVC +OKiCn3evxHdbXAYzs0xU3kclJEQ1A+KRs1eCDbR4KMnggDXyL0sVd8ECgYEA2Z20 +A5h/wnKS4Osr9vKGjGMB11tXs/jqw6Ssox9IsdNmkfWYd2xlLhgT33tX5dknD0h7 +SoBqH7WUKh0AjrTOW5u+wphLyzouNMn8tDWBwd3bfyiKreNkR+dwDeVnq9GpwB0H +9O4bVaPON3+e/+VPVrJa7ftG3lzPmehKlsQhp3cCgYAAg7xtQLleAbO357euBHaU +v2531ySUzeUXCGL3aLEJPToBcpLMGsPeni/E4BbKewv8d/8n7BSqOZKG8TwAR/yx +eb7kgne1ITO+UrDONltiX6yJx6PVB7WXnAxxgj2OHxi/JeSIzeyEd3XGl8HnuGbO +G63ZikmdDprOsVFWer4aAQKBgQDFg4Cuvsw+1bUAVhMaWXfLldVG/Q32OJgEKMPU +boGRfTmcgtf2lLilBI+NCKt34Ae2tGfK8BDiZPFs49PssqiVtx+GCarJcy4bFbW6 +btIt/srJeCeeukP7i1FGG5Bj/7FdwDvHkBHZQgrajofm5N+E5e7oD2h8TKrUm2SP +wMZ28wKBgQCCLPNxueLu4f2xyBmt2Qc/DCfh1sj9CwcvLuwalhQKkDKoB15sR0PL +hH1wNKWxJ8zvTPUA5zvT/jCHfYBhEKuI4DpKxU1z3fjWK5Df1Iqx1OFriCK8AQ+S +uGCsfQBHdb9pZyqt/HwNMybAzAVzTLo9PX7szpeT6Qghdt90reZeuw== +-----END RSA PRIVATE KEY----- diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..0f73a50 --- /dev/null +++ b/cache.go @@ -0,0 +1,51 @@ +// 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.Mutex +} + +// entry is an entry in a Cache +type entry struct { + data interface{} + expiration time.Time +} + +// NewCache creates a new Cache +func NewCache() *Cache { + c := &Cache{} + c.entries = make(map[string]*entry) + c.mutex = &sync.Mutex{} + return c +} + +// 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.Lock() + defer cache.mutex.Unlock() + 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..f55cd6c --- /dev/null +++ b/config.go @@ -0,0 +1,43 @@ +package main + +import "crypto/tls" + +// TLSConfig Configuration for TLS +type TLSConfig struct { + PrivateKeyFile string + CertFile string + Organization string + CommonName string + ServerTLSConfig *tls.Config +} + +// NewTLSConfig init the TlsConfig +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_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_ECDSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + }, + PreferServerCipherSuites: true, + InsecureSkipVerify: true, + }, + } +} diff --git a/keyman.go b/keyman.go new file mode 100644 index 0000000..0a54766 --- /dev/null +++ b/keyman.go @@ -0,0 +1,309 @@ +// 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" +) + +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 { + logger.Printf("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 { + logger.Printf("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 { + logger.Printf("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/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/main.go b/main.go index 037e7f9..7f2dd88 100644 --- a/main.go +++ b/main.go @@ -4,32 +4,27 @@ import ( "flag" "fmt" "log" - "net" - "net/http" - "time" + "os" "github.com/cxjava/gscan" - "github.com/elazarl/goproxy" ) var ( - cdnChan = make(chan string, 10) - verbose = flag.Bool("v", false, "Should every proxy request be logged to stdout") - addr = flag.String("a", ":8888", "The proxy listen address") - size = flag.Int("s", 20, "The fastest number of IP") - dialTimeout = flag.Int("dt", 1, "The timeout(Second) of dial") - deadlineTimeout = flag.Int("dlt", 1, "The timeout(Second) of deadline") + logger *log.Logger + cdnChan = make(chan string, 10) + addr = flag.String("a", ":8888", "The proxy listen address") + size = flag.Int("s", 20, "The fastest number of IP") ) func init() { go addCDN() + logger = log.New(os.Stdout, "[AutoChange12306CDN]", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile) } func addCDN() { fastestCDN := make(map[string]string, *size) ips := gscan.ScanIP("./iprange.conf", gscanConf) - t := 0 for _, v := range ips { if t >= *size { @@ -39,6 +34,8 @@ func addCDN() { fastestCDN[v] = v + ":443" fmt.Println(v) } + logger.Println("CDN query is done!") + logger.Println("Server is listening at ", *addr) go func() { for { @@ -51,25 +48,10 @@ func addCDN() { func main() { flag.Parse() - proxy := goproxy.NewProxyHttpServer() - proxy.ConnectDial = func(network, addr string) (c net.Conn, err error) { - if addr == "kyfw.12306.cn:443" { - c, err = net.DialTimeout(network, <-cdnChan, time.Duration(*dialTimeout)*time.Second) - if c, ok := c.(*net.TCPConn); err == nil && ok { - c.SetKeepAlive(false) - c.SetDeadline(time.Now().Add(time.Duration(*deadlineTimeout) * time.Second)) - } - return - } - c, err = net.Dial(network, addr) - if c, ok := c.(*net.TCPConn); err == nil && ok { - c.SetKeepAlive(true) - } - return - } - proxy.Verbose = *verbose - log.Println("start listen on " + *addr) - log.Fatal(http.ListenAndServe(*addr, proxy)) + + ch := make(chan bool) + Gomitmproxy(*addr, ch) + <-ch } var gscanConf = ` diff --git a/mitmproxy.go b/mitmproxy.go new file mode 100644 index 0000000..7262b75 --- /dev/null +++ b/mitmproxy.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + "time" +) + +// Gomitmproxy create a mitm proxy and start it +func Gomitmproxy(addr string, ch chan bool) { + tlsConfig := NewTLSConfig("ca-pk.pem", "ca-cert.pem", "", "") + handler := InitConfig(tlsConfig) + server := &http.Server{ + Addr: addr, + ReadTimeout: 1 * time.Hour, + WriteTimeout: 1 * time.Hour, + Handler: handler, + } + + go func() { + server.ListenAndServe() + ch <- true + }() + + return +} diff --git a/mitmserver.go b/mitmserver.go new file mode 100644 index 0000000..6f9a40c --- /dev/null +++ b/mitmserver.go @@ -0,0 +1,290 @@ +package main + +import ( + "bufio" + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "regexp" + "strings" + "sync" + "time" +) + +const ( + Version = "1.1" + ONE_DAY = 24 * time.Hour + TWO_WEEKS = ONE_DAY * 14 + ONE_MONTH = 1 + ONE_YEAR = 1 +) + +// HandlerWrapper wrapper of handler for http server +type HandlerWrapper struct { + wrapped http.Handler + tlsConfig *TLSConfig + pk *PrivateKey + pkPem []byte + issuingCert *Certificate + issuingCertPem []byte + dynamicCerts *Cache + certMutex sync.Mutex + https bool +} + +// InitConfig init HandlerWrapper +func InitConfig(tlsconfig *TLSConfig) *HandlerWrapper { + handler := &HandlerWrapper{ + tlsConfig: tlsconfig, + dynamicCerts: NewCache(), + } + err := handler.GenerateCertForClient() + if err != nil { + return nil + } + return handler +} + +// ServeHTTP the main function interface for http handler +func (handler *HandlerWrapper) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + if req.Method == "CONNECT" { + handler.https = true + handler.InterceptHTTPS(resp, req) + } else { + handler.https = false + handler.DumpHTTPAndHTTPS(resp, req) + } +} + +type myDialer struct { + d *net.Dialer + ConnectDial func(network string, addr string) (net.Conn, error) +} + +func (d *myDialer) Dial(network, address string) (net.Conn, error) { + return d.ConnectDial(network, address) +} + +// DumpHTTPAndHTTPS function to dump the HTTP request header and body +func (handler *HandlerWrapper) DumpHTTPAndHTTPS(resp http.ResponseWriter, req *http.Request) { + req.Header.Del("Proxy-Connection") + req.Header.Set("Connection", "Keep-Alive") + + var reqDump []byte + ch := make(chan bool) + // handle connection + go func() { + reqDump, _ = httputil.DumpRequestOut(req, true) + ch <- true + }() + + connHj, _, err := resp.(http.Hijacker).Hijack() + if err != nil { + logger.Println("Hijack fail to take over the TCP connection from client's request") + } + defer connHj.Close() + + host := req.Host + + matched, _ := regexp.MatchString(":[0-9]+$", host) + + var connOut net.Conn + if !handler.https { + if !matched { + host += ":80" + } + connOut, err = net.DialTimeout("tcp", host, time.Second*30) + if err != nil { + logger.Println("Dial to", host, "error:", err) + return + } + } else { + if !matched { + host += ":443" + } + if host == "kyfw.12306.cn:443" && strings.HasPrefix(req.RequestURI, "/otn/leftTicket/query") { + host = <-cdnChan + logger.Println("current CDN is:", host) + } + + connOut, err = tls.DialWithDialer(&net.Dialer{ + Timeout: 1 * time.Second, + KeepAlive: 0, + }, "tcp", host, handler.tlsConfig.ServerTLSConfig) + if err != nil { + logger.Println("Dial to", host, "error:", err) + connHj.Write([]byte("HTTP/1.0 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "Body here\n")) + return + } + } + + // Write writes an HTTP/1.1 request, which is the header and body, in wire format. This method consults the following fields of the request: + /* + Host + URL + Method (defaults to "GET") + Header + ContentLength + TransferEncoding + Body + */ + if err = req.Write(connOut); err != nil { + logger.Println("send to server error", err) + return + } + + respFromRemote, err := http.ReadResponse(bufio.NewReader(connOut), req) + if err != nil && err != io.EOF { + logger.Println("Fail to read response from remote server.", err) + } + + respDump, err := httputil.DumpResponse(respFromRemote, true) + if err != nil { + logger.Println("Fail to dump the response.", err) + } + // Send remote response back to client + _, err = connHj.Write(respDump) + if err != nil { + logger.Println("Fail to send response back to client.", err) + } + + <-ch + // why write to reqDump, and in httpDump resemble to req again + // in test, i find that the req may be destroyed by sth i currently dont know + // so while parsing req in httpDump directly, it will raise execption + // so dump its content to reqDump first. + //go httpDump(reqDump, respFromRemote) +} + +// InterceptHTTPS to dump data in HTTPS +func (handler *HandlerWrapper) InterceptHTTPS(resp http.ResponseWriter, req *http.Request) { + addr := req.Host + host := strings.Split(addr, ":")[0] + + cert, err := handler.FakeCertForName(host) + if err != nil { + logger.Println("Could not get mitm cert for name: %s\nerror: %s", host, err) + respBadGateway(resp) + return + } + + connIn, _, err := resp.(http.Hijacker).Hijack() + if err != nil { + logger.Println("Unable to access underlying connection from client: %s", err) + respBadGateway(resp) + return + } + + tlsConfig := copyTlsConfig(handler.tlsConfig.ServerTLSConfig) + tlsConfig.Certificates = []tls.Certificate{*cert} + tlsConnIn := tls.Server(connIn, tlsConfig) + listener := &mitmListener{tlsConnIn} + httpshandler := http.HandlerFunc(func(resp2 http.ResponseWriter, req2 *http.Request) { + req2.URL.Scheme = "https" + req2.URL.Host = req2.Host + handler.DumpHTTPAndHTTPS(resp2, req2) + }) + + go func() { + server := &http.Server{Handler: httpshandler} + server.SetKeepAlivesEnabled(false) + err = server.Serve(listener) + if err != nil && err != io.EOF { + logger.Printf("Error serving mitm'ed connection: %s", err) + } + }() + + connIn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) +} + +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 copyTlsConfig(template *tls.Config) *tls.Config { + tlsConfig := &tls.Config{} + if template != nil { + *tlsConfig = *template + } + return tlsConfig +} + +func copyHTTPRequest(template *http.Request) *http.Request { + req := &http.Request{} + if template != nil { + *req = *template + } + return req +} + +func respBadGateway(resp http.ResponseWriter) { + resp.WriteHeader(502) +}